一些单词的意思:Entity实体 annotation注解 artifact产品 Repository仓库 schema纲要图解模式 stereotype刻板印象 hibernate休眠 invoke调用 wrapper包装 qualifier限定 broker中间人 Aspect层面 actuator执行器 🍑以下内容都是从网上搜罗的,主要是跟着知乎@汤太咸的学习文章学习同时搜索相关知识资料,侵删。。。。。。 包含@RestController、@Configuration、spring-security-config、@Repository、JPA、RestTemplate、ehcache、RabbitMQ、kafka、swagger3、读取properties、Logback、@Aspect、Actuator、单元测试UT、 ---------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------- Spring Framework是一个强大的开发框架,它大大简化了Java应用程序的开发和管理。org.springframework.stereotype 注解家族是Spring的核心注解之一,它们允许开发人员定义和标识不同类型的组件,以便Spring能够更好地管理和协调它们。 stereotype:翻译成中文是刻板印象,机翻的比较晦涩和抽象,在Spring Framework的stereotype包中,定义了许多常用的注解,这些注解的功能是:在Spring服务启动时,通过定义在元数据中的“印象”,让Spring容器了解它们的基础信息,并“刻板”的存储在Spring的容器池中。 为什么org.springframework.stereotype注解很重要?这些注解的重要性在于它们为Spring提供了有关应用程序组件的关键信息,从而实现了以下几个关键目标: 自动化配置:Spring可以自动创建这些组件的实例,并处理它们的依赖关系,减少了手动配置的需求。 依赖注入:通过将 @Autowired注解与org.springframework.stereotype注解一起使用,可以轻松实现依赖注入,使组件之间的协作变得容易。 组件扫描:Spring可以自动扫描应用程序的类路径以查找这些注解,并自动创建Bean,这使得组件的添加和移除变得非常简单。 下面是一个关于Spring Framework中stereotype包的形象例子: 假设一个叫Spring的人开了一家工厂。在这个过程中,你可能会遇到以下几种角色: @Component(员工) @Service(对外服务员) @Repository(库管员) @Controller(组长) @Configuration(技术总监) @Aspect(质检员) 当然Spring本身就是厂长啦。。。。。。 SpringBoot 基于 Spring 开发。SpringBoot 本身并不提供 Spring 框架的核心特性以及扩展功能,也就是说,它并不是用来替代 Spring 的解决方案,而是和 Spring 框架紧密结合用于提升 Spring 开发者体验的工具。 Spring Boot 的设计初衷是解决 Spring 各版本配置工作过于繁重的问题,简化初始搭建流程、降低开发难度,使开发人员只需要专注应用程序的功能和业务逻辑实现,而不用在配置上花费太多时间。Spring Boot 使部署变得简单,其本身内嵌启动容器,仅仅需要一个命令即可启动项目,结合Jenkins、Docker 自动化运维非常容易实现。 SpringBoot 官方推荐的构建应用的方式是使用 Spring Initializr,直接在网页上选择好构建工具、语言、SpringBoot 版本,填好自己的项目名和初始依赖,然后点Generate 按钮,就能下载一个构建好的工程的zip包,只需要把这个包解压之后导入IDE就可以了。 这已经是一个包含依赖的、完整的、可独立运行的springboot应用了!你所需要做的就是往里面填充自己的业务代码! -------------------------------------------------@RestController----------------------------------------------------------------- 在控制器类里边编写接口程序: import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.DeleteMapping; @RestController public class HelloWorldController {...} 一、GetMaping接口 Get接口主要是用来获取信息的接口,常用来获取列表以及实体信息。通过RequestParam来获取url传递过来的参数。 @GetMapping("/student") public String getStudent(@RequestParam String id){...} 二、PostMapping接口 post接口可以做一些修改或者新增的操作,可以通过RequestBody或者RequestParam获取到传递过来的参数。 @PostMapping("/student") public String addStudent(@RequestParam String id, @RequestParam String name){...} 三、PutMapping接口 主要用来做update修改操作,增加updateStudent方法,逻辑咱们写的和post的代码一致即可。 @PutMapping("/student") public String updateStudent(@RequestParam String id, @RequestParam String name){...} 四、DeleteMapping接口 主要用来做一些删除操作,增加deleteStudent方法,代码如下: @DeleteMapping("/student") public String deleteStudent(@RequestParam String id){ //通过不同的id删除不同的name if("1".equals(id)) {studentName_1 = null;}} 五、RequestMapping接口 其实上面四种还有另外一种写法,那就是RequestMapping,其中的method参数来定义是那种方式,GET/POST/DELETE/PUT等方式都通过RequestMethod这个枚举类型表 @RequestMapping(value = "/student", method = RequestMethod.GET) public String getStudent(@RequestParam String id){...} --------------------------------------------------------------------------------------------------------------------------- --------------------------@Configuration-和-spring-security-config------------------------------------------------------------ Indicates that a class declares one or more @Bean methods and may be processed by the Spring container to generate bean definitions and service requests for those beans at runtime 上是@Configuration源码给出的功能解释,大意是说@Configuration是标记此类里边有spring运行时需要的@Bean的生成方法。 Add this annotation to an @Configuration class to have the Spring Security configuration defined in any WebSecurityConfigurer or more likely by exposing a SecurityFilterChain bean: 上是@EnableWebSecurity源码给出的功能解释,大意是说@EnableWebSecurity用在一个@Configuration类上来获取spring的安全配置(通过WebSecurityConfigurer或者SecurityFilterChain的bean给出的) SpringBoot 3.X版本的 SpringSecurity 配置 org.springframework.boot spring-boot-starter-security org.springframework.security spring-security-config 新增一个java类,用@Configuration和@EnableWebSecurity装饰,配置basicauth账号密码aaa/bbb。 @Configuration @EnableWebSecurity public class SecurityConfig { private final static String ACCOUNT_CLIENT_AUTHORITY = "ADMIN"; //配置basicauth账号密码 @Bean UserDetailsService userDetailsService() { InMemoryUserDetailsManager users = new InMemoryUserDetailsManager(); users.createUser(User.withUsername("aaa") .password(PasswordEncoderFactories.createDelegatingPasswordEncoder().encode("bbb")) .authorities(ACCOUNT_CLIENT_AUTHORITY).build()); return users; } } @Configuration 注解扮演了一个关键角色,它标识一个类作为配置源,这些配置用于定义和管理 Spring 应用程序中的 Bean,代替了传统的 XML 配置文件。主要特性 • 完全的程序控制:允许开发者通过 Java 代码完全控制依赖注入过程。 • 环境集成:可以通过 @Profile 注解与之配合,根据不同的环境定义不同的配置。 • 灵活的依赖注入:通过 @Bean 注解在方法上,可以灵活地定义 Bean,并管理其生命周期和依赖。 @EnableWebSecurity这会使Spring Boot Security Auto-Configuration在应用中加载默认的安全配置。默认的安全配置会激活 HTTP Security Filter 和 Security Filter Chain,并对端点应用 Basic Authentication 认证。 @Bean是Spring框架 3.0 版本的一个注解,用于标识一个方法,该方法将返回一个由 Spring 管理的 Bean 对象。同时,被@Bean注解的方法只会被Spring容器识别一次。在配置类中,通过在方法上标注 @Bean 注解,Spring 容器会在运行时调用该方法,并将方法返回的对象注册为一个 Bean。通过 @Bean 注解,可以将自定义的对象纳入到 Spring 容器的管理范围内,从而可以享受 Spring 提供的依赖注入、AOP 等功能。一般情况下,@Bean 注解可以与 @Configuration 注解一起使用,将一个类标识为配置类,并在该类中声明需要被 Spring 管理的 Bean 对象的创建方法。这些方法可以包含业务逻辑来创建 Bean 实例,并可以设置其属性、依赖关系等。 增加一个Controller类,其中可以通过注解@PreAuthorize("hasAuthority('ADMIN')") 来配置相应接口的权限验证,不配置的也就是不验证权限或者以@EnableWebSecurity类的配置为准(需要在@EnableWebSecurity类中配置SecurityFilterChain或其他方式的@Bean)。 @RestController public class SecAccessController { @PreAuthorize("hasAuthority('ADMIN')") @GetMapping("/helloworld") public ResponseEntity helloworld() { // 确保返回有效的响应 return ResponseEntity.ok("Hello, World!"); } } ------------------------------------------------------------------------------------------------------------------------- ----------------------------------DAO--------@Repository------------------------------------------------------------------ @Repository注解修饰哪个类表明这个类具有对数据库CRUD的功能,用在持久层的接口上。 @Autowired 是 Spring 框架提供的一种依赖注入方式,它可以自动装配 Bean,并将成员变量、方法参数或构造函数中需要的对象注入到对应的位置。它是基于 Java 的反射机制实现的,能够方便地管理对象之间的依赖关系。 我们都知道使用原始的JDBC在操作数据库是比较麻烦的,所以Spring为了提高开发的效率,顺带着就把JDBC封装、改造了一番,而JdbcTemplate就是Spring对原始JDBC封装之后提供的一个操作数据库的工具类。 JdbcTemplate主要提供以下三种类型的方法: executeXxx() : 执行任何SQL语句,对数据库、表进行新建、修改、删除操作 updateXxx() : 执行新增、修改、删除等语句 queryXxx() : 执行查询相关的语句 @Repository public class StudentDao { @Autowired private JdbcTemplate jdbcTemplate; public List getStudent(String id) { List students = jdbcTemplate.queryForList("select * from student where id = ?", id); return students; } } 在properties中配置数据库连接信息给JdbcTemplate对象用 spring: datasource: url: jdbc:mysql://localhost:3306/myjavadb?useUnicode=true&characterEncoding=utf-8&useSSL=false username: zz password: zhuo driver-class-name: com.mysql.cj.jdbc.Driver @RequestParam接收来自RequestHeader中,即请求头,通常用于GET请求。@RequestParam也可用于其它类型的请求,例如:POST请求,由于@RequestParam是用来处理 Content-Type 为 application/x-www-form-urlencoded 编码的内容的,所以在postman中,要选择body的类型为 x-www-form-urlencoded,这样在headers中就自动变为了 Content-Type : application/x-www-form-urlencoded 编码格式。 @RequestBody接收的参数是来自requestBody中,即请求体。一般用于处理非 Content-Type: application/x-www-form-urlencoded编码格式的数据,比如:application/json、application/xml等类型的数据。 可以使用多个@RequestParam获取数据,@RequestBody不可以。 使用@RequestBody传入JSON数据和@RequestMapping返回JSON数据的时候需要注意JACKSON序列化大小写问题。 spring: jackson: property-naming-strategy: SNAKE_CASE #表示前端数据大小写方式为SNAKE_CASE @RestController public class DaoController { @Autowired private StudentDao studentDao; @GetMapping("/daostudent") public Object getStudent(@RequestParam String id){ List student = studentDao.getStudent(id); return student.get(0); } } 测试POST的时候需要先临时关闭CSRF,不然会被拒绝。在@Configuration@EnableWebSecurity注解的配置类里的filterChain方法中设置。 SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http.csrf(csrf -> csrf.disable())...... ------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------JPA--------------------------------------------------------------------- JPA是Java Persistence API的简称,中文名Java持久层API,是JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。 Sun引入新的JPA ORM规范出于两个原因:其一,简化现有Java EE和Java SE应用开发工作;其二,Sun希望整合ORM技术,实现天下归一。对象关系映射(英语:Object Relational Mapping,简称ORM,或O/RM,或O/R mapping)。JPA本身不包含具体的实现细节,而是通过不同的ORM框架来实现,如Hibernate、EclipseLink等‌12。 Spring Data JPA则是基于JPA规范的一个框架,它进一步简化了JPA的使用。Spring Data JPA提供了对CRUD(创建、读取、更新、删除)操作的简化,并且通过约定方法命名规则,使得开发者可以不用编写具体的SQL语句和实现接口,就能完成对数据库的操作。此外,Spring Data JPA还提供了分页、排序、复杂查询等功能,极大地提高了开发效率。 mysql只需要引入spring-boot-starter-data-jpa即可。JPA默认与SQLite不兼容,需要单独引入hibernate4-sqlite-dialect来支持SQLite数据源。 org.springframework.boot spring-boot-starter-data-jpa 2.3.12.RELEASE com.enigmabridge hibernate4-sqlite-dialect 0.1.2 spring: #针对SQLite配置方言,MySQL不需要该配置 jpa: database-platform: com.enigmabridge.hibernate.dialect.SQLiteDialect @Entity注解用于标记一个Java类为JPA实体,这意味着该类的实例可以被转换成数据库中的记录。实体类通常对应数据库中的一张表。 @Table注解允许我们自定义表名、指定表的schema等。当实体类名不符合数据库命名规范或有特殊需求时,使用@Table注解明确指定表名。 @Id注解用于标记实体类中的哪个属性作为数据库表的主键。每个实体必须有一个主键。 @Column:用于细化字段映射,如指定列名、是否允许为空等。 @Repository注解是Spring框架中用于标识数据访问层组件的注解。它不仅提供了@Component注解的所有功能,还增加了数据访问相关的异常处理,使Spring容器能够识别并管理DAO组件,同时自动转换检查型异常为非检查型异常,简化了数据访问层的异常处理。 import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; @Entity//实体 @Table(name = "student") public class Student { @Id @Column(name = "id") private String id; @Column(name = "name") private String name; public Student() { } public Student(String id, String name) { this.id = id; this.name = name; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } 增加StudentRepository.java类,可以看到没有修改和新增的方法,其实JPA默认为我们集成了该方法(接口的默认方法),不需要咱们单独实现了,在接下来Controller中可以看到该方法的使用。另外可以看到findById方法的返回带有Optional,这个是包装器,即使没有查询到,也不会反回null,通过Optional的isPresent()方法可以判断是否存在返回。 import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import java.util.Optional; @Repository public interface StudentRepository extends JpaRepository { } 增加TestRestController类 import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.BindingResult; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.util.Optional; @RestController public class TestRestController { @Autowired private StudentRepository studentRepository; @GetMapping("/student") public Object getStudent(@RequestParam String id){ Optional opt = studentRepository.findById(id); //存在返回的Student对象 if(opt.isPresent()){ return opt.get(); } //不存在返回null return null; } @PostMapping("/student") public String addStudent(@RequestParam String id, @RequestParam String name){ studentRepository.save(new Student(id,name)); return "ok"; } @PutMapping("/student") public String updateStudent(@RequestParam String id, @RequestParam String name){ studentRepository.save(new Student(id,name)); return "ok"; } @DeleteMapping("/student") public String deleteStudent(@RequestParam String id){ studentRepository.deleteById(id); return "ok"; } @PostMapping("/validation") public String validation(@Validated @RequestBody ValidationRequest request, BindingResult results) { //把实体注解中的错误信息返回 if (results.hasErrors()) { return results.getFieldError().getDefaultMessage(); } return "ok"; } } ----------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------RestTemplate-----HttpClient5------------------------------------------------- RestTemplate 简介 RestTemplate 是从 Spring3.0 开始支持的一个 HTTP 请求工具,它提供了常见的REST请求方案的模版,例如 GET 请求、POST 请求、PUT 请求、DELETE 请求以及一些通用的请求执行方法 exchange 以及 execute。RestTemplate 继承自 InterceptingHttpAccessor 并且实现了 RestOperations 接口,其中 RestOperations 接口定义了基本的 RESTful 操作,这些操作在 RestTemplate 中都得到了实现。接下来我们就来看看这些操作方法的使用。 @Configuration 是 Spring 框架中提供的一个核心注解,它指示一个类声明了一个或多个 @Bean 定义方法,这些方法由 Spring 容器管理并执行,以便在运行时为 bean 实例化、配置和初始化对象。博主给出的httpclient配置复杂而且已经有点过时了,改用httpclient5 org.apache.httpcomponents.client5 httpclient5 5.3 增加RestTemplateConfig.java类 import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.client5.http.classic.HttpClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.web.client.RestTemplate; import java.nio.charset.StandardCharsets; @Configuration public class RestTemplateConfig { // 配置并注入RestTemplate,其他类才可以依赖注入RestTemplate @Bean public RestTemplate restTemplate(HttpComponentsClientHttpRequestFactory factory) { RestTemplate restTemplate = new RestTemplate(factory); restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8)); return restTemplate; } @Bean public HttpComponentsClientHttpRequestFactory sslIgnoreFactory() throws Exception { HttpClient httpClient = HttpClients.createDefault();//可以看到httpCient5有默认配置比httpClient简单很多 HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(); requestFactory.setHttpClient(httpClient); requestFactory.setConnectTimeout(15000); return requestFactory; } } 修改TestRestController.java类,增加RestTemplate注入,以及getStudentByRestTemplate和addStudentByRestTemplate方法。这两个方法,都是通过RestTemplate调用自身接口再来操作数据库的。 import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.*; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.validation.BindingResult; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; import java.util.Optional; @RestController public class TestRestController { @Autowired private RestTemplate restTemplate; @Autowired private StudentRepository studentRepository; @Autowired private StudentService studentService; @RequestMapping(value = "/student/cache", method = RequestMethod.GET) public Object getStudentFromCache(@RequestParam String id){ return studentService.getStudent(id); } @GetMapping("/student") public Object getStudent(@RequestParam String id){ Optional opt = studentRepository.findById(id); //存在返回的Student对象 if(opt.isPresent()){ return opt.get(); } //不存在返回null return null; } @PostMapping("/student") public String addStudent(@RequestParam String id, @RequestParam String name){ studentRepository.save(new Student(id,name)); return "ok"; } @PutMapping("/student") public String updateStudent(@RequestParam String id, @RequestParam String name){ studentRepository.save(new Student(id,name)); return "ok"; } @DeleteMapping("/student") public String deleteStudent(@RequestParam String id){ studentRepository.deleteById(id); return "ok"; } @PostMapping("/validation") public String validation(@Validated @RequestBody ValidationRequest request, BindingResult results) { //把实体注解中的错误信息返回 if (results.hasErrors()) { return results.getFieldError().getDefaultMessage(); } return "ok"; } @GetMapping("/getStudentByRestTemplate") public String getStudentByRestTemplate(@RequestParam String id) { // 拼接好url,以及参数 String url = UriComponentsBuilder.fromUriString("http://127.0.0.1:8080/student") .queryParam("id", id) .build() .toUriString(); // 由于获取student接口咱们设置了basicauth,所以需要在请求时配置 HttpHeaders headers = new HttpHeaders(); headers.setBasicAuth("aaa", "bbb"); HttpEntity httpEntity = new HttpEntity<>(headers); ResponseEntity response = null; try { // 通过Get方式调用接口 response = restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class); } catch (Exception e) { e.printStackTrace(); return null; } return response.getBody(); } @PostMapping("/addStudentByRestTemplate") public String addStudentByRestTemplate(@RequestParam String id, @RequestParam String name) { String url = "http://127.0.0.1:8080/student"; // 由于获取student接口咱们设置了basicauth,所以需要在请求时配置 HttpHeaders headers = new HttpHeaders(); headers.setBasicAuth("aaa", "bbb"); // 需要传递FORMDATA的请求参数 headers.setContentType(MediaType.MULTIPART_FORM_DATA); // POST参数通过LinkedMultiValueMap传递过去,通过HashMap会报下面这个错 // No HttpMessageConverter for java.util.HashMap and content type "multipart/form-data" MultiValueMap params = new LinkedMultiValueMap<>(); params.add("id", id); params.add("name", name); HttpEntity httpEntity = new HttpEntity(params, headers); ResponseEntity response = null; try { // 通过POST方式调用接口,如果需要PUT,DELETE方式调用 // 只需要修改为HttpMethod.PUT,HttpMethod.DELETE即可,其他传递参数已经代码一致 response = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class); } catch (Exception e) { e.printStackTrace(); return null; } return response.getBody(); } } ----------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------ehcache--------------------------------------------------------------------- Ehcache 是一个开源的、基于标准的缓存工具,它能提升性能、减轻数据库负载并简化可扩展性。由于其稳健性、经得起考验的特点以及与其他流行框架的集成,成为最广泛使用的基于 Java 的缓存工具。Ehcache 从进程内缓存一直扩展到混合的进程内/进程外部署,可以处理 TB 的数据。 net.sf.ehcache ehcache 2.10.6 org.springframework.boot spring-boot-starter-cache #配置方法注意下,貌似不同版本不同,错了导致没法加载ehcahce。 spring: cache: ehcache: config: classpath:ehcache.xml 新增StudentRepository接口extends JpaRepository import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import java.util.Optional; @Repository public interface StudentRepository extends JpaRepository { //JPA为我们实现了默认的查询等操作。。。。。。 } 新增ehcache.xml配置 增加StudentService.java类 import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import java.util.Optional; //Slf4j的注解就是lombok中的注解,日志输出使用 @Slf4j @Service //cacheNames与ehcache.xml中的名字相同 @CacheConfig(cacheNames = "student") public class StudentService { @Autowired private StudentRepository studentRepository; //注意key,可以配置在cache中的名字,同时可以将参数作为key的一部分存在key中 @Cacheable(key = "'student_'+#id") public Student getStudent(String id) { log.info("只有第一次没有缓存时候才调用,第二次就没有日志了,因为请求就不进入方法里了"); Optional opt = studentRepository.findById(id); //存在返回的Student对象 if(opt.isPresent()){ return opt.get(); } //不存在返回null return null; } } 修改TestRestController.java类,增加StudentService注入,以及getStudentFromCache方法,调用service中的缓存方法。 @Autowired private StudentService studentService; @RequestMapping(value = "/student/cache", method = RequestMethod.GET) public Object getStudentFromCache(@RequestParam String id){ return studentService.getStudent(id); } 修改Student.java的实体类,增加实现序列化接口Serializable,因为缓存到内存中需要序列化操作,否则会缓存失败。 import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; import java.io.Serializable; @Entity @Table(name = "student") public class Student implements Serializable { @Id @Column(name = "id") private String id; @Column(name = "name") private String name; public Student() { } public Student(String id, String name) { this.id = id; this.name = name; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } 还有一个非常重要的,就是在SpringBoot启动类HelloWorldApplication.java增加允许缓存的注解@EnableCaching import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; @EnableCaching @SpringBootApplication public class HelloWorldApplication { public static void main(String[] args) { SpringApplication.run(HelloWorldApplication.class, args); } } ----------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------RabbitMQ--------------------------------------------------------------------- RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)。 springboot依赖和配置: org.springframework.boot spring-boot-starter-amqp spring: rabbitmq: addresses: 修改为自己RabbitMQ服务器地址 username: 修改为自己的RabbitMQ账号 password: 修改为自己的RabbitMQ密码 virtual-host: 修改为自己的RabbitMQ的virtual-host 增加RabbitMQConsumerConfig.java配置消费者连接,以及初始化queue,exchange,routingKey,也就是如果RabbitMQ服务器没有queue,exchange,routingKey,自动创建。 import com.rabbitmq.client.BuiltinExchangeType; import com.rabbitmq.client.Channel; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.core.AcknowledgeMode; import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory; import org.springframework.amqp.rabbit.connection.Connection; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Slf4j //simple log file for java @Configuration public class RabbitMQConsumerConfig { /** * 总之就是注入了一个SimpleRabbitListenerContainerFactory的bean。 * 依赖RabbitMQ自动创建的SimpleRabbitListenerContainerFactoryConfigurer类bean和ConnectionFactory类bean。 */ @Bean(name = "brokerContainerFactory") public SimpleRabbitListenerContainerFactory brokerContainerFactory(SimpleRabbitListenerContainerFactoryConfigurer configurer, ConnectionFactory connectionFactory) { SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); configurer.configure(factory, connectionFactory); factory.setMessageConverter(new Jackson2JsonMessageConverter()); // 手工ACK factory.setAcknowledgeMode(AcknowledgeMode.MANUAL); // 最小的消费者数 factory.setConcurrentConsumers(3); // 消费者最大数 factory.setMaxConcurrentConsumers(5); // 预拉取条数 factory.setPrefetchCount(5); // 初始化并新建 try (Connection connection = connectionFactory.createConnection(); Channel channel = connection.createChannel(false)) { channel.queueDeclare("QUEUE_NAME", true, false, false, null); channel.exchangeDeclare("EXCHANGE_NAME", BuiltinExchangeType.DIRECT); channel.queueBind("QUEUE_NAME", "EXCHANGE_NAME", "ROUTING_KEY"); } catch (Exception e) { log.info("Declare and bind queue error!", e); } return factory; } } 新增MessageDto.java,接收消息实体。(DTO数据传输对象) import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import java.io.Serializable; @Data public class MessageDto implements Serializable { @JsonProperty("content") private String content; } 新增MessageConsumer.java,RabbitMQ消息消费监听类,负责消息的接收处理。 import com.fasterxml.jackson.databind.ObjectMapper; import com.rabbitmq.client.Channel; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; import java.io.IOException; @Slf4j @Component public class MessageConsumer { private static final ObjectMapper mapper = new ObjectMapper(); /** * @RabbitListener注解参数queues和containerFactory指出监听的queue */ @RabbitListener(queues = "QUEUE_NAME", containerFactory = "brokerContainerFactory") public void onMessage(Message message, Channel channel) throws Exception { try { log.info("Consumed message: {}", message); MessageDto dto = parse(new String(message.getBody()), MessageDto.class); log.info("Consumed message content: {}", dto); // 由于之前配置的手动ack,需要手动回调rabbitMQ服务器,通知已经完成消费 channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); } catch(Exception e) { log.error("Consumed erorr,", e); // 由于之前配置的手动ack,需要手动回调rabbitMQ服务器,通知出现问题 channel.basicReject(message.getMessageProperties().getDeliveryTag(), true); } } public static T parse(String json, Class clazz) { try { return mapper.readValue(json, clazz);//总之就是把message json字符串读入到DTO类里边 } catch (IOException e) { log.error("IOException, json = {}, clazz = {}", json, clazz, e); try { return clazz.newInstance(); } catch (InstantiationException | IllegalAccessException e1) { log.error("InstantiationException or IllegalAccessException, clazz = {}", clazz, e1); return null; } } } } 新增RabbitMQProducerConfig.java,生产者配置类,负责创建RabbitTemplate,提供消息发送的工具类。 import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Slf4j @Configuration public class RabbitMQProducerConfig { /**总之就是注入了一个RabbitTemplate的bean。 * 依赖RabbitMQ自动创建的ConnectionFactory类bean。 */ @Bean(name = "pointRabbitTemplate") public RabbitTemplate pointRabbitTemplate(ConnectionFactory connectionFactory) { RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); rabbitTemplate.setExchange("EXCHANGE_NAME"); rabbitTemplate.setRoutingKey("ROUTING_KEY"); rabbitTemplate.setMandatory(true); return rabbitTemplate; } } 新建MessageProducer.java,调用RabbitTemplate的convertAndSend发送方法来发送消息,注意消息发送前需要先序列化再发送。 import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Slf4j @Service public class MessageProducer { private static final ObjectMapper mapper = new ObjectMapper(); @Autowired private RabbitTemplate pointRabbitTemplate; /** * 发送消息 restcontroller @param 的content */ public void sendMessageToMQ(String content) { MessageDto dto = new MessageDto(); dto.setContent(content); pointRabbitTemplate.convertAndSend(this.format(dto)); } public static String format(Object pojo) { try { return mapper.writeValueAsString(pojo);//调用jackson的ObjectMapper序列化 } catch (JsonProcessingException e) { log.error("JsonProcessingException, pojo = {}", pojo, e); return "{}"; } } } 新建RabbitMQController.java,负责提供rest接口,以方便测试消息发送。 import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class RabbitMQController { @Autowired private MessageProducer messageProducer; @PostMapping("/sendMQMessage") public String sendMQMessage(@RequestParam String content) { messageProducer.sendMessageToMQ(content); return "ok"; } } ----------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------kafka--------------------------------------------------------------------- Kafka是由Apache软件基金会开发的一个开源流处理平台,是一种高吞吐量的分布式发布订阅消息系统。 kafka的诞生,是为了解决linkedin的数据管道问题,起初linkedin采用了ActiveMQ来进行数据交换,大约是在2010年前后,那时的ActiveMQ还远远无法满足linkedin对数据传递系统的要求,经常由于各种缺陷而导致消息阻塞或者服务无法正常访问,为了能够解决这个问题,linkedin决定研发自己的消息传递系统,当时linkedin的首席架构师jay kreps便开始组织团队进行消息传递系统的研发。 相关术语: "Broker" Kafka集群包含一个或多个服务器,这种服务器被称为broker。 "Topic" 每条发布到Kafka集群的消息都有一个类别,这个类别被称为Topic。(物理上不同Topic的消息分开存储,逻辑上一个Topic的消息虽然保存于一个或多个broker上但用户只需指定消息的Topic即可生产或消费数据而不必关心数据存于何处)。 "Partition" Partition是物理上的概念,每个Topic包含一个或多个Partition。 "Producer" 负责发布消息到Kafka broker。 "Consumer" 消息消费者,向Kafka broker读取消息的客户端。 "Consumer Group" 每个Consumer属于一个特定的Consumer Group(可为每个Consumer指定group name,若不指定group name则属于默认的group)。 kafka安装配置需要再深入学习一下。安装可以用docker部署kafka+zookeeper。配置相当多,账号密码配置还没学会。。。。。。 依赖 org.springframework.kafka spring-kafka spring配置 kafka: bootstrap-servers: 修改为自己Kafka服务器地址 consumer: group-id: kafka-demo-kafka-group key-deserializer: org.apache.kafka.common.serialization.StringDeserializer #关键字的序列化类 value-deserializer: org.apache.kafka.common.serialization.StringDeserializer #值的序列化类 properties: #账号密码配置 sasl.mechanism: PLAIN security.protocol: SASL_PLAINTEXT sasl.jaas.config: org.apache.kafka.common.security.plain.PlainLoginModule required username="消费者账号" password="密码" producer: group-id: kafka-demo-kafka-group key-serializer: org.apache.kafka.common.serialization.StringSerializer #关键字的序列化类 value-serializer: org.apache.kafka.common.serialization.StringSerializer #值的序列化类 properties: #账号密码配置 session.timeout.ms: 15000 sasl.mechanism: PLAIN security.protocol: SASL_PLAINTEXT sasl.jaas.config: org.apache.kafka.common.security.plain.PlainLoginModule required username="生产者账号" password="密码" 新增KafkaConsumer.java,负责监听从kafka收到的消息,也就是监听到topics消息。 import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.springframework.kafka.annotation.KafkaListener; import org.springframework.stereotype.Component; @Slf4j @Component public class KafkaConsumer { /** * 消费者端:指定监听话题 * 指定监听的topics消息 * @param consumerRecord 监听到数据 */ @KafkaListener(topics = {"spring_test_kafka_topic"}) public void handlerMsg(ConsumerRecord consumerRecord) { log.info("接收到消息:消息值:{} ,消息偏移量:{},Partition:{}", consumerRecord.value(), consumerRecord.offset(), consumerRecord.partition()); } } 新增KafkaProducer.java,负责向具体topic发送消息。 import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.stereotype.Component; @Slf4j @Component public class KafkaProducer { @Autowired private KafkaTemplate kafkaTemplate; public String sendMessage(String content) { // 发送消息到kafka // 需要使用KafkaTemplate // 可以指定partition为0(我的默认就一个,所以是0,如果kafka服务器支持多个可以指定其他数字) String topic = "spring_test_kafka_topic"; kafkaTemplate.send(topic, 0, "spring_test_kafka_topic_key", content); kafkaTemplate.send(topic, 0, "spring_test_kafka_topic_key", content); return "发送成功"; } } 新建KafkaController.java,负责提供rest接口,以方便测试消息发送。 import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @Slf4j @RestController public class KafkaController { @Autowired private KafkaProducer kafkaProducer; @PostMapping("/sendkafkaMessage") public String sendMQMessage(@RequestParam String content) { log.info("KafkaController content: {}", content); kafkaProducer.sendMessage(content); return "ok"; } } ----------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------swagger3--------------------------------------------------------------------- 貌似Swagger已经过时了。。。。。。现在用smart-doc + Torna/yapi/apifox/postman 什么是Swagger?Swagger目前是比较主流的RESTful风格的API文档工具,它提供了一套工具和规范,让开发人员能够更轻松地创建和维护可读性强、易于使用和交互的API文档。官方网站https://swagger.io/为什么用Swagger?以往在没有这样的API文档工具,开发人员需要手动编写和维护功能API的文档。而且,由于API变更往往难以及时更新到文档中,这可能会给依赖文档的开发者带来困惑。 说几个Swagger的特点: 最重要的一点可以根据代码注解自动生成API文档,能生成的绝对不手写,而且API文档与API定义会同步更新。 它提供了一个可执行的Web界面,支持API在线测试,可以直接在界面上直接设置参数测试,不用额外的测试工具或插件。 支持多种编程语言,Java、PHP、Python等语言都支持,喜欢什么语言构建API都行。 总的来说,Swagger可以让我们更多时间在专注于编写代码(摸鱼),而不是花费额外精力来维护文档,实践出真知先跑个demo试试。 OpenAPI阶段的Swagger也被称为Swagger 3.0。在Swagger 2.0后,Swagger规范正式更名为OpenAPI规范,并且根据OpenAPI规范的版本号进行了更新。因此,Swagger 3.0对应的就是OpenAPI 3.0版本,它是Swagger在OpenAPI阶段推出的一个重要版本。与前几个版本相比,Swagger 3.0更加强调对RESTful API的支持和规范化,提供了更丰富和灵活的定义方式,并且可以用于自动生成文档、客户端代码、服务器代码和测试工具等。 org.springframework.boot spring-boot-starter-web org.projectlombok lombok org.springframework.boot spring-boot-starter-security org.springframework.security spring-security-config org.springdoc springdoc-openapi-starter-webmvc-ui 2.2.0 访问http://127.0.0.1:8080/swagger-ui/index.html ----------------------------------------------------------------------------------------------------------------------------- -----------------------------------------spring读取properties---------------------------------------------------------------- 涉及字符编码问题: 针对application.properties:由于此文件为spring框架自己调用,有大神研究是PropertiesLoaderUtils.loadProperties(resource)->fillProperties()函数在加载的时候只针对xml类型文件指定了字符编码为UTF-8,其他类型文件未指定字符编码,因为是字节流,所以默认是ISO-8859-1编码的。所以如果你用UTF-8的application.properties文件定义中文字符内容变量运行的时候就会输出乱码。所以要么你用xml文件,要用properties文件的话就别在里边定义中文字符串变量。非要有这种特殊需求的话,大神给出了终极解决办法:创建一个类继承PropertiesPropertySourceLoader。在resource目录下创建目录META-INF,在META-INF目录下创建文件spring.factories,内容配置org.springframework.boot.env.PropertySourceLoader=org.example.SelfPropertySourceLoader。汗!!! public class SelfPropertySourceLoader extends PropertiesPropertySourceLoader { @Override public PropertySource load(String name, Resource resource, String profile) throws IOException { if (profile == null) { // Properties properties = PropertiesLoaderUtils.loadProperties(resource); Properties properties = loadUseUtf8(resource); if (!properties.isEmpty()) { return new PropertiesPropertySource(name, properties); } } return null; } private Properties loadUseUtf8(Resource resource) throws IOException { Properties props = new Properties(); InputStream is = resource.getInputStream(); try { String filename = resource.getFilename(); if (filename != null && filename.endsWith(".xml")) { props.loadFromXML(is); } else { props.load(new InputStreamReader(is, "utf-8")); } } finally { is.close(); } return props; } } 而针对自定义的properties文件:可以在@Configuration中通过@PropertySource注解的encoding属性告知框架此properties文件的编码为UTF-8。 在IDEA设置里边有个针对属性文件的编辑器勾选项:自动转换成ASCII但显示原生的内容。勾选上这个的话你新建的属性文件都是ASCII/ISO-8859-1编码。 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ application.properties文件中增加: test.properties.value.a=我是a test.properties.value.b=我是b test.properties.value.a1=我是a1 test.properties.value.b1=我是b1 test.properties.value.test-a=我是test-a 增加非默认配置文件test-properties.properties,注意后边java代码部分的@PropertySource只支持默认的properties文件,不支持yaml格式。 test.properties.value.aa1=我是aa1 test.properties.value.bb1=我是bb1 增加TestPropertiesConfig.java,通过@ConfigurationProperties读取配置文件内容。 注意上面的test-a的配置,到java中读取,需要改为testA的驼峰格式。(spring内置JACKSON将SNAKE_CASE转成java) import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * 通过类的方式获取 * @ConfigurationProperties的prefix配置前缀 */ @Component @ConfigurationProperties(prefix = "test.properties.value") public class TestPropertiesConfig { // 与key值(去掉前缀后)对应,配置文件为test-a,java按照驼峰配置testA即可获取 private String a1; private String b1; private String testA; public String getA1() { return a1; } public void setA1(String a1) { this.a1 = a1; } public String getB1() { return b1; } public void setB1(String b1) { this.b1 = b1; } public String getTestA() { return testA; } public void setTestA(String testA) { this.testA = testA; } } 增加OtherPropertiesConfiguration.java,通过@PropertySource注解可以增加其他配置文件。注意如果有中文的话,需要增加encoding格式为UTF-8格式。 import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; @Configuration @PropertySource(value = "classpath:test-properties.properties", encoding = "UTF-8") public class OtherPropertiesConfiguration { } 增加TestPropertiesControllor.java,直接通过 @Value读取配置以及测试TestPropertiesConfig读取的配置。 import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @Slf4j @RestController public class TestPropertiesControllor { @Autowired private TestPropertiesConfig testPropertiesConfig; // 直接通过具体配置文件配置的key获取 @Value("${test.properties.value.a}") private String a; // 直接通过具体配置文件配置的key获取 @Value("${test.properties.value.b}") private String b; @Value("${test.properties.value.bb1}") private String bb1; @GetMapping("/properties") public String getProperties(){ log.info("a={};b={}", a, b); return "a=" + a + ";b=" + b; } @GetMapping("/properties1") public String getProperties1(){ log.info("a1={};b1={};test-a={},bb1={}", testPropertiesConfig.getA1(), testPropertiesConfig.getB1(), testPropertiesConfig.getTestA(), bb1); return "a1=" + testPropertiesConfig.getA1() + ";b1=" + testPropertiesConfig.getB1() + ";test-a=" + testPropertiesConfig.getTestA() + ";bb1=" + bb1; } } ----------------------------------------------------------------------------------------------------------------------------- -----------------------------------------------Logback----------------------------------------------------------------------- 官方网址:https://logback.qos.ch/ Logback是一款为Java应用程序设计的日志框架,旨在提供高性能、灵活性和可扩展性。它是log4j项目的继任者,并被广泛用于Java应用程序的日志记录。 Logback分为三个主要的模块:logback-core、logback-classic、和logback-access。 logback-core提供了基本的日志功能。 logback-classic建立在logback-core之上,兼容SLF4J和log4j API,提供了一套强大的日志框架。 logback-access允许通过servlet容器的访问日志功能来记录HTTP请求。 Spring Boot默认集成了Logback,并用INFO级别输出到控制台。 由于Spring Boot通常使用嵌入式Servlet容器,并且这些容器已经具备了记录访问日志的功能,因此在默认情况下不需要引入logback-access。 新增logback-spring.xml文件,Logback日志格式配置文件。 注意博主原来的配置ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP已经deprecated了!!! 应改用ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy并且不用在ch.qos.logback.core.rolling.TimeBasedRollingPolicy中!!! +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ [%d{yyyy-MM-dd HH:mm:ss}] [%thread] [%level] %logger{50} - %msg%n ${LOG_HOME}/${appName}.log ${LOG_HOME}/${appName}-%d{yyyy-MM-dd}-%i.log 365 100MB [%d{yyyy-MM-dd HH:mm:ss}] [%thread] [%level] %logger{50} - %msg%n ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 增加TestLogbackControllor.java文件,通过接口调用输出不同格式的日志。 import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @Slf4j @RestController public class TestLogbackControllor { @GetMapping("/logback") public String logback(){ log.info("info级别,输出重要的信息,常用"); log.debug("debug级别,调试,实际应用中一般将其作为最低级别"); log.error("error级别,记录错误日志,常用"); log.warn("warn级别,记录告警日志,常用"); log.trace("trace级别,追踪,指明程序运行轨迹。很少用"); int param1 = 1; String param2 = "param2"; // 多个参数写入日志,可以通过{}占位符替换成参数 log.info("传递参数: {}, 第二个参数: {}", param1, param2); return "ok"; } } --------------------------------------------------------------------------------------------------------------------------- --------------------------------------------@Aspect---------------------------------------------------------------------- 面向切面编程(AOP)是面向对象编程的补充,简单来说就是统一处理某一“切面”的问题的编程思想。 如果使用AOP的方式进行日志的记录和处理,所有的日志代码都集中于一处,不需要在每个方法里面都去添加,极大减少了重复代码。 说人话就是在程序运行时,某些接口收到请求后,在进入咱们编写的代码逻辑前/后/周围进行切入,可以对请求或者返回进行一些特殊处理,比如说日志,比如修改一些Request参数(虽然可以这么做,但是极力不推荐),修改Response返回内容(虽然可以这么做,但是极力不推荐)。 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 新增ControllerLogAspect.java类,设置切入的位置 package site.zhangzhuo.JPA; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Slf4j @Aspect @Component public class ControllerLogAspect { //设定PointCut也就是指定切入点位置 //注意execution表达式中的第一个*代表返回的类型,咱们用*表示任何,可以指定具体返回类型 //execution表达式中第二部分为具体的类名方法名,可以写具体包名也可以通过*等匹配 //execution表达式中括号内部表示请求参数..表示不限定也可以指定具体类型参数 //@Pointcut中的execution可以通过||,&&等符号匹配表示或/且的关系 //直白点Point指的是方法函数 @Pointcut("execution(* site.zhangzhuo.JPA.AspectController.*(..))") private void logPointcut() { // 不需要写任何实现,这里是定义一个切入点 } //执行方法前可以截获,可以控制程序不进入方法 @Before("logPointcut()") public void beforeRequest(JoinPoint jp) { log.info("请求之前(beforeRequest)"); } //执行方法后可以截获,可以获取到结果,异常等 @After("logPointcut()") public void afterResponse(JoinPoint jp) { log.info("请求之后,真正返回Response之前(afterResponse)"); } //捕获异常,注意监控的Exception一定要和代码中throw出来的Exception一致 //咱们这里用的RuntimeException @AfterThrowing(pointcut = "execution(* site.zhangzhuo.JPA.AspectController.*(..))" , throwing = "ex") public void logException(RuntimeException ex){ log.info("RuntimeException:" + ex); } //捕获返回结果 @AfterReturning(pointcut = "execution(* site.zhangzhuo.JPA.AspectController.*(..))" , returning = "string") public void logResult(String string){ log.info("result:" + string); } //同时支持进入方法前,执行方法后,捕获异常,捕获返回结果 @Around("logPointcut()") public Object retry(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { log.info("请求之前(retry)"); try { Object response = proceedingJoinPoint.proceed(); log.info("请求之后,真正返回Response之前(retry)"); return response; } catch (Exception e) { log.info("请求之后,出现异常,设定返回null(retry)"); return null; } } } 新增AspectController.java类,为上面的AOP写入具体调用逻辑代码 package site.zhangzhuo.JPA; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @Slf4j @RestController public class AspectController { @GetMapping("/aspect/error") public String error() throws Exception{ throw new RuntimeException("error"); } @GetMapping("/aspect/test") public String test() throws Exception{ log.info("进入test方法"); return "ok"; } } Aspect内部共有五个通知,也都在咱们代码中讲了,咱们再列出来一下: 前置通知(@Before):在目标方法调用之前调用通知 后置通知(@After):在目标方法完成之后调用通知 环绕通知(@Around):在被通知的方法调用之前和调用之后执行自定义的方法 返回通知(@AfterReturning):在目标方法成功执行之后调用通知 异常通知(@AfterThrowing):在目标方法抛出异常之后调用通知 --------------------------------------------------------------------------------------------------------------------------- -----------------------------------------------Actuator------------------------------------------------------------------- 01. 什么是Actuator Spring Boot Actuator,可在您将应用程序投入生产时帮助您监控和管理应用程序。分别支持HTTP和JMX两类端点。 02. 支持哪些监控项 总得来说,分为三类: 1)应用配置类:获取应用程序中加载的应用配置、环境变量、自动化配置报告等与Spring Boot应用密切相关的配置类信息 2)度量指标类:获取应用程序运行过程中用于监控的度量指标,比如:内存信息、线程池信息、HTTP请求统计等 3)操作控制类:提供了对应用的关闭等操作类功能 常用端点如下: 1)auditevents 公开当前应用程序的审核事件信息 2) beans 该端点用来获取应用上下文中创建的所有Bean 3) caches 展示可用的缓存 4)conditions 显示在配置和自动配置类上评估的条件以及它们匹配或不匹配的原因 5)configprops 所有@ConfigurationProperties的配置列表 6)flyway 显示已应用的所有Flyway数据库迁移。 7)env 返回当前的环境变量 8)health 健康状况 9)httptrace 查看HTTP请求响应明细(默认100条) 10)info 展示服务应用信息 11)loggers 查看日志配置,支持动态修改日志级别 12)liquibase 显示已应用的所有Liquibase数据库迁移 13)metrics 显示当前应用程序的“指标”信息 14)mappings 显示所有@RequestMapping请求列表 15)scheduledtasks 显示服务中的所有定时任务 16)sessions 允许从Spring Session支持的会话存储中检索和删除用户会话 17)shutdown 可以使服务优雅的关闭,默认没有开启 18)threaddump 执行线程转储 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ org.springframework.boot spring-boot-starter-actuator management: # SpringBoot后期版本要求和服务不能是同一个端口,如果端口相同则起不来 server.port: 8081 security: enabled: false # 可以指定暴露哪些actuator服务,'*'为全部,注意加上引号,被注释的写法表示只允许health,info endpoints: web: exposure: #include: health,info include: '*' endpoint: # 表示可以通过/actuator/shutdown停止服务 shutdown: enabled: true # 表示health的内容显示的更加详细内容,不光只status health: show-details: always info: # 显示任意的应用信息,默认关闭,如果是更低一些的版本默认是开启的 env: enabled: true # 自定义/actuator/info中的各种内容,可以自定义,也可以取默认的一些系统/服务环境变量 info: app: encoding: "@project.build.sourceEncoding@" java: source: "@java.version@" target: "@java.version@" build: artifact: @project.artifactId@ name: @project.name@ description: @project.description@ pomVersion: @project.version@ # 甚至可以自定义test test: 'ddfff' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 增加MaxMemoryHealthIndicator.java,修改默认/actuator/health接口默认实现,增加自定义的内存不足10G表示服务没有成功启动的逻辑。 import lombok.extern.slf4j.Slf4j; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.Status; import org.springframework.stereotype.Component; @Slf4j @Component public class MaxMemoryHealthIndicator implements HealthIndicator { /** * 自己手动实现actuator/health服务状态 * @return */ @Override public Health health() { // 设置内存小于10G的,手动设置actuator/health的状态为DOWN // 只有大于10G,状态才是UP Long minMemory = 10L*1024L*1024L*1024L; log.info("Runtime max memory: {}MB , min memory: {}MB",Runtime.getRuntime().maxMemory()/1024/1024 , minMemory/1024/1024); boolean invalid = Runtime.getRuntime().maxMemory() < minMemory; Status status = invalid ? Status.DOWN : Status.UP; return Health.status(status).build(); } } 增加ReadinessEndpoint.java可以自定义一些actuator的监控接口,可以在里面自定义一些具体的按照自己业务逻辑或者特殊程序的一些逻辑。 import lombok.extern.slf4j.Slf4j; import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.WriteOperation; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; /** * Endpoint来创建/actuator/readiness */ @Slf4j @Component @Endpoint(id="readiness") public class ReadinessEndpoint { /** * Get方式调用接口/actuator/readiness */ private String ready = "NOT_READY"; @ReadOperation public String getReadiness(){ return ready; } /** * Post方式调用接口/actuator/readiness */ @WriteOperation public String writeOperation(){ return ready+"_WRITE"; } /** * Delete方式调用接口/actuator/readiness */ @DeleteOperation public String deleteOperation(){ return ready+"_DELETE"; } /** * 监听服务启动完毕后,设置服务状态READY */ @EventListener(ApplicationReadyEvent.class) public void setReady(){ log.info("Application start complete setReady"); ready = "READY"; } } +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ http://localhost:8081/actuator/info http://localhost:8081/actuator/health http://localhost:8081/actuator/readiness http://localhost:8081/actuator/metrics http://localhost:8081/actuator/beans --------------------------------------------------------------------------------------------------------------------------- ----------------------------------单元测试UT------------------------------------------------------------------------- 新增StudentEntity.java类,数据库实体类 新增ValidationRequest.java类,validation接口入参实体 新增SpringBootTestRepository.java类,数据库层代码 新增SpringBootTestService.java类,业务层代码 新增SpringBootTestController.java类,接口层代码 新增ControllerTest.java,接口层测试代码,很多人说Controller没必要测试,其实有些简单逻辑有可能写在controller层。 新增ServiceTest.java,业务层测试代码,常见的调用repository以及调用接口代码都在这部分 新增RepositoryTest.java类,数据库持久层测试代码,引入@DataJpaTest 进行测试,@AutoConfigureTestDatabase 引入了测试使用的数据库datasource。 常见的SpringBoot测试基本上就是以上几种情况,在不同的测试情况下需要引入不同的注解,整体来说,分别是@SpringBootTest 测试接口层,@RestClientTest 模拟调用外围接口部分测试Service,@DataJpaTest 测试数据库持久层代码。 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @SpringBootTest @AutoConfigureMockMvc @RunWith(SpringRunner.class) public class ControllerTest { @Autowired protected MockMvc mockMvc; @MockBean RestTemplate restTemplate; @MockBean SpringBootTestService springBootTestService; @MockBean SpringBootTestRepository springBootTestRepository; HttpHeaders httpHeaders = new HttpHeaders(); private static final ObjectMapper mapper = new ObjectMapper(); /** * 由于咱们系统设定了权限控制 * 在Test请求之前设定basicAuth的信息 * @throws Exception */ @Before public void setBasicAuth() throws Exception { // 设置basicAuth String basicAuthString = "Basic " + Base64.getEncoder().encodeToString("aaa:bbb".getBytes()); httpHeaders.set("Authorization", basicAuthString); } @Test public void TestRestController_getStudent() throws Exception { StudentEntity expectStudent = new StudentEntity("1","张三"); // 通过when的方式mockrepository的返回为expectStudent when(springBootTestRepository.findById(any())).thenReturn(Optional.of(expectStudent)); // 通过mockMvc模拟调用/springboottest/student?id=1 // 也就是SpringBootTestController的getStudent方法 // mockMvc.perfrom中传入的get表示GET请求 MvcResult mvcResult = mockMvc.perform(get("/springboottest/student?id=1") .contentType(MediaType.APPLICATION_JSON_VALUE) // 设定basicAuth到请求header中 .headers(httpHeaders)) // 打印详细的请求以及返回内容 .andDo(print()) // 判断HttpStatus是200,如果不是表示失败 .andExpect(status().isOk()) // 返回结果给mvcResult .andReturn(); // 获取mvcResult的body String resutlStr = mvcResult.getResponse().getContentAsString(Charset.defaultCharset()); // 将json格式的resutlStr反序列化为StudentEntity StudentEntity student = mapper.readValue(resutlStr, StudentEntity.class); // 判断结果是否成功 Assert.assertEquals(expectStudent.getName(), student.getName()); } /** * 接口代码中通过@RequestParam接收的 * 因此此处ContentType为MediaType.MULTIPART_FORM_DATA * 通过param设定请求的参数key/value * 分别 * @throws Exception */ @Test public void TestRestController_addStudent() throws Exception { // mockMvc.perfrom中传入的post表示POST请求 MvcResult mvcResult = mockMvc.perform(post("/springboottest/student") .contentType(MediaType.MULTIPART_FORM_DATA) .headers(httpHeaders).param("id", "1").param("name","张三")) .andDo(print()) .andExpect(status().isOk()) .andReturn(); Assert.assertEquals("ok", mvcResult.getResponse() .getContentAsString(Charset.defaultCharset())); } @Test public void TestRestController_updateStudent() throws Exception { // mockMvc.perfrom中传入的put表示PUT请求 MvcResult mvcResult = mockMvc.perform(put("/springboottest/student") .contentType(MediaType.MULTIPART_FORM_DATA) .headers(httpHeaders).param("id", "1").param("name","张三1")) .andDo(print()) .andExpect(status().isOk()) .andReturn(); Assert.assertEquals("ok", mvcResult.getResponse() .getContentAsString(Charset.defaultCharset())); } @Test public void TestRestController_deleteStudent() throws Exception { // mockMvc.perfrom中传入的delete表示DELETE请求 MvcResult mvcResult = mockMvc.perform(delete("/springboottest/student") .contentType(MediaType.MULTIPART_FORM_DATA) .headers(httpHeaders).param("id", "1").param("name","张三1")) .andDo(print()) .andExpect(status().isOk()) .andReturn(); Assert.assertEquals("ok", mvcResult.getResponse() .getContentAsString(Charset.defaultCharset())); } @Test public void TestRestController_validation() throws Exception { // validation接口中通过@RequestBody获取的请求参数 // 因此此时contentType为MediaType.APPLICATION_JSON_VALUE // 请求参数也需要通过mapper把bean序列化成json格式 MvcResult mvcResult = mockMvc.perform(post("/springboottest/validation") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(httpHeaders) .content(mapper.writeValueAsString(ValidationRequest.builder().age("35") .content("12345678901").count(10).name("张三").remark("备注").build()))) .andDo(print()) .andExpect(status().isOk()) .andReturn(); Assert.assertEquals("ok", mvcResult.getResponse() .getContentAsString(Charset.defaultCharset())); } } ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @RunWith(SpringRunner.class) @RestClientTest(SpringBootTestService.class) @AutoConfigureDataJpa @AutoConfigureWebClient(registerRestTemplate=true) public class ServiceTest { // 调用URL的MockBean @Autowired private MockRestServiceServer server; @Autowired private SpringBootTestService springBootTestService; @Autowired private SpringBootTestRepository springBootTestRepository; private static final ObjectMapper mapper = new ObjectMapper(); /** * URL要与调用的路径完全一致,包括后边的?id=1 * andRespond设定返回的body格式 * MediaType.APPLICATION_JSON表示结果json格式 * 最后通过Assert判断结果 * @throws Exception */ @Test public void getStudentByRestTemplate() throws Exception { StudentEntity student = new StudentEntity("1","张三"); this.server.expect(requestTo("http://127.0.0.1:8082/hello-world-new/springboottest/student?id=1")) .andRespond(withSuccess(mapper.writeValueAsString(student), MediaType.APPLICATION_JSON)); String response = springBootTestService.getStudentByRestTemplate("1"); Assert.assertEquals("张三", mapper.readValue(response, StudentEntity.class).getName()); } /** * MediaType.TEXT_PLAIN表示结果是纯文本格式 * 最后通过Assert判断结果 * @throws Exception */ @Test public void addStudentByRestTemplate() throws Exception { this.server.expect(requestTo("http://127.0.0.1:8082/hello-world-new/springboottest/student")) .andRespond(withSuccess("ok", MediaType.TEXT_PLAIN)); Assert.assertEquals("ok", springBootTestService.addStudentByRestTemplate("1", "张三")); } } ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @Slf4j @RunWith(SpringRunner.class) @DataJpaTest// 引入了JPA环境 @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)// 引入了datasource,为JPA提供测试数据源 public class RepositoryTest { @Autowired SpringBootTestRepository repository; @Test public void testFindById() { // 测试查询之前先存入数据 StudentEntity studentEntity = new StudentEntity("2", "张三"); repository.save(studentEntity); // 测试查询结果数据并验证 Optional opt = repository.findById("2"); log.info("testFindById find by id result: {}", opt); Assert.assertEquals("2", opt.get().getId()); } @Test public void testDeleteById() { // 测试查询之前先存入数据 StudentEntity studentEntity = new StudentEntity("1", "张三"); repository.save(studentEntity); // 验证数据库存在可以删除的数据 Optional opt = repository.findById("1"); log.info("testDeleteById find by id result: {}", opt); Assert.assertEquals("1", opt.get().getId()); // 执行删除数据测试 repository.deleteById("1"); // 再次查询数据库中是否还存在数据并验证 opt = repository.findById("1"); log.info("testDeleteById find by id result after delete: {}", opt); Assert.assertFalse(opt.isPresent()); } } ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @SpringBootTest注解用于加载整个 Spring 应用程序上下文,包括你的应用程序配置和所有 Bean。 @AutoConfigureMockMvc注解则是一个测试自动配置注解,它会自动配置MockMvc实例。MockMvc是一个强大的工具,允许你以编程方式模拟 HTTP 请求并验证响应,而无需启动实际的 Servlet 容器。使用@SpringBootTest时,并不要求你必须和@AutoConfigureMockMvc一起启用。这两个注解服务于不同的目的,并且它们的使用是独立的。 @RunWith(SpringRunner.class)的作用 在JUnit 4下,有许多不同的测试运行器(Test Runner)可用于执行单元测试。默认情况下,Junit使用内置的测试运行器(BlockJUnit4ClassRunner)来运行单元测试。但是,对于一些特殊场景下的测试,我们可能需要使用其他测试运行器。而Spring框架提供了一个测试运行器(SpringRunner或SpringJUnit4ClassRunner ), 它继承自 JUnit 的 BlockJUnit4ClassRunner 类。它可以加载 Spring 应用程序上下文并将其注入到测试类中,从而提供对依赖注入功能的支持。当您想要在测试期间使用 Spring 框架中的 beans 时,就需要在测试类上添加@RunWith(SpringRunner.class)注解,并使其运行基于 Spring 的单元测试。通过@RunWith(SpringRunner.class)注解,JUnit会使用Spring的测试运行器来运行测试类中标注了@Test注解的测试方法,并配置Spring环境。整个测试生命周期都使用Spring容器来启动、进行和关闭,和真正运行Spring Boot应用程序并没有本质区别。简而言之,@RunWith(SpringRunner.class)注解是为了在测试期间为您的代码创建一个 Spring ApplicationContext,并确保所有速记标记工作完美运行,如@Autowired 的注入等。这个注解补充JUnit,从而提供了基于Spring的测试功能,使得我们在测试时可以更加轻松地使用Spring的特性,而不需要自己搭建一个应用,提高了测试效率。 @MockBean 是 Spring Boot Test提供的注解,用于在 Spring Boot 测试中创建一个模拟的 Bean 实例,并注入到测试类中的依赖项中。使用 Mock 可以控制被 Mock 对象的行为:自定义返回值、抛出指定异常等,模拟各种可能的情况,提高测试的覆盖率。 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ JUnit是Java中最流行的单元测试框架,用于编写和执行单元测试。 @Test: 标记一个测试方法,用于执行单元测试。 @Before: 在每个测试方法执行之前运行,用于准备测试数据或初始化资源。 @After: 在每个测试方法执行之后运行,用于清理测试数据或释放资源。 @BeforeClass: 在所有测试方法执行前运行,通常用于执行一次性的初始化操作。 @AfterClass: 在所有测试方法执行后运行,通常用于执行一次性的清理操作。 @Ignore: 标记一个测试方法,用于暂时忽略这个测试。 @RunWith(SpringRunner.class)用于运行Spring Boot的测试。 assertEquals:JUnit的断言方法之一,用于验证预期值和实际值是否相等。 assertThrows:JUnit的断言方法之一,用于验证是否抛出了预期的异常。 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Mockito库用于模拟对象,隔离被测类与其他类的依赖关系。 常用注解 @Mock: 标记一个模拟对象。 @InjectMocks: 标记一个被测类,用于注入模拟对象。 @RunWith(MockitoJUnitRunner.class): 指定运行器,用于运行Mockito的测试。 Mockito 提供了一系列方法,用于在单元测试中创建和设置模拟对象的行为: when(mock.method()).thenReturn(value): 设置当 mock 对象的指定方法被调用时,返回预设的值。 any(): 表示任何值,用于 when 或 verify 方法的参数匹配。 doReturn(value).when(mock).method(): 与 when-thenReturn 类似,但适用于无法通过 when-thenReturn 语句模拟的情况,如 void 方法。 doThrow(Exception).when(mock).method(): 用于模拟当 mock 对象的指定方法被调用时,抛出异常。 spy(object): 用于创建一个 spy 对象,它是对真实对象的包装,所有未被 stub 的方法都会调用真实的方法。 Mockito 还提供了一系列的方法,用于验证模拟对象的行为: verify(mock).method(): 验证 mock 对象的指定方法是否被调用。 never(): 验证 mock 对象的指定方法从未被调用。 times(n): 验证 mock 对象的指定方法被调用了 n 次。 atLeast(n): 验证 mock 对象的指定方法至少被调用了 n 次。 atMost(n): 验证 mock 对象的指定方法最多被调用了 n 次。 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @RestClient是Spring6.1使用的http客户端,旨在用来代替RestTemplate发送http,以阻塞方式发送和接收 HTTP 请求和响应。 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @AutoConfigure* org.springframework.boot.test.autoconfigure org.springframework.boot.autoconfigure 接触到一个新的模块后,如果spring-boot会自动配置它,那么我们可以在spring-boot-autoconfigure模块下找到对应的包,然后找到以AutoConfiguration结尾的类,比如flyway的FlywayAutoConfiguration,cassandra的CassandraAutoConfiguration等等,就能很快了解新模块的加载过程。有些爱思考的同学就要问了,你怎么知道要去找以AutoConfiguration结尾的类,这个就引入了另一个话题,spring-boot是如何实现自动配置的,它怎么知道需要加载哪些AutoConfiguration,这个其实是用到了spring-factories机制,我们直接来看一下spring-boot-autoconfigure-2.0.6.RELEASE.jar包下的spring.factories文件。里面有这样的一部分配置,key是org.springframework.boot.autoconfigure.EnableAutoConfiguration,value是各个以逗号隔开的*AutoConfiguration,结合spring-factories的运行原理,我们就可以知道所有的自动配置是从这里开始加载的。 ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------打包到docker------------------------------------------------------------- Springboot构建package的如果是个jar包的话需要在java环境下运行,所以在dockerfile中需要FROM JDK。 FROM openjdk:17 ADD target/hello-world-*.jar /hello-world.jar ENTRYPOINT ["java","-jar","/hello-world.jar"] sudo docker build -t xxx:latest -f xxxx.Dockerfile -------------------------------------------------------------------------------------------------------------------- ------------------------------------------------mongoDB------------------------------------------------------------- 貌似国内用的不多,放后边看吧,消化不过来了。。。。。。 --------------------------------------------------------------------------------------------------------------------