Featured image of post SpringBoot学习及整合常用开发工具

SpringBoot学习及整合常用开发工具

SpringBoot入门及整合MyBatis、MyBatis-Plus、Redis、Thymeleaf

SpringBoot

基础入门

快速开始

导入依赖

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<!--父项目-->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.6</version>
</parent>
<!--web工程依赖-->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

编写主类

1
2
3
4
5
6
7
8
//声明这是一个SpringBoot工程
@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        //运行main方法即可让整个工程运行 不再需要配置tomcat服务器
        SpringApplication.run(Main.class, args);
    }
}

编写业务

1
2
3
4
5
6
7
8
//声明一个Controll类 类中所有方法的返回值都会原样返回给客户端
@RestController
public class HelloController {
    @RequestMapping("/hello")
    public String hello(){
        return "hello springboot";
    }
}

配置文件

SpringBoot的配置文件在classpath路径下使用application.properties或application.yml表示

所有可用的配置可以在官方文档中查看

1
2
server:
  port: 8088

快速部署

使用SpringBoot提供的maven插件可以快速将工程打为jar包,直接运行即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
    		 <configuration>
    			<!--设置主类-->
    			<mainClass>com.xinnn.boot.Main</mainClass>
    		</configuration>
            <executions>
                <execution>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

自动配置

依赖管理

SpringBoot使用父项目进行依赖管理,在父项目的pom文件中,声明了常用的开发中所需插件的版本号。在子项目需要引用这些插件时,无需写版本号。

1
2
3
4
5
6
7
8
<!--
	自定义版本号
	1.在 spring-boot-dependencies 里面查看规定该插件版本使用的 key
	2.在当前pom文件中重写
-->
<properties>
    <mysql.version>8.0.1</mysql.version>
</properties>

**starter场景启动器 **

  1. spring-boot-starter-* ,*号表示某种场景
  2. 引入starter后,该场景下的所有依赖会自动导入
  3. SpringBoot官方的starter列表
  4. *-spring-boot-starter 是第三方提供的
  5. 所有场景启动器都依赖于spring-boot-starter

自动配置组件

  • 自动配置Tomcat

  • 自动配置SpringMVC

  • 自动配置Web开发的常用功能(字符编码、视图解析、文件上传解析等)

  • 默认的包结构

    • 主程序所在的包及子包中的所有组件都会被扫描
    • 指定扫描路径 @SpringBootApplication(scanBasePackages = "com.xinnn")
  • 配置拥有默认值

  • 按需加载所有自动配置项

    • 所有的自动配置功能都在spring-boot-autoconfigure里面

容器功能

组件添加

1. @Configuration
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
 * 声明这是一个配置类
 * @Configuration(proxyBeanMethods = true)
 * proxyBeanMethods:代理bean的方法
 *      等于 true 时,是full模式,每个bean方法返回的对象都是单例的
 *      等于 false 时,是lite模式,每个bean方法返回的对象都是新创建的
 *		类组件之间无依赖关系使用lite模式,加速启动;有依赖使用full模式 确保得到的是同一个
 */
@Configuration(proxyBeanMethods = true)
public class SpringConfig {

    @Bean //在容器中添加组件 组件的id等于方法的名字 类型是方法的返回类型
    public User user(){
        User user = new User("张三", 18);
        //user组件依赖于cat组件 设置proxyBeanMethods=true 确保依赖的组件是同一个。
        user.setCat = (cat());
        return user;
    }
    @Bean("tom") //自定义id cat方法在容器中的id为 tom
    public Cat cat(){
        return new Cat("小黑");
    }
}
//测试代码
//scanBasePackages 指定扫描组件的包 默认为主类同级和子包
@SpringBootApplication(scanBasePackages = "com.xinnn")
public class Main {
    public static void main(String[] args) {
        //获取IOC容器
        ConfigurableApplicationContext applicationContext = SpringApplication.run(Main.class, args);
        //获取容器中所有组件的id
        for (String beanDefinitionName : applicationContext.getBeanDefinitionNames()) {
            System.out.println(beanDefinitionName);
        }
        //获取user组件
        User user = applicationContext.getBean("user", User.class);
        //获取cat组件
        Cat cat = applicationContext.getBean("tom", Cat.class);
    }
}
2.@import
1
2
//在容器中创建这两个类型的组件,默认id是类的全类名
@Import({User.class, Date.class}) 
3.@Conditional
1
2
3
4
//条件装配 条件满足时才执行 这是根注解 有非常多的派生注解 可以被标识在方法和类上
@Conditional
//容器中存在名称为tom的组件时才会执行被标识的方法或类
@ConditionalOnBean(name = "tom")
4.@ImportResource
1
2
//通过配置文件导入bean
@ImportResource("classpath:spring.xml")

配置绑定

1.@Component + @ConfigurationProperties

1
2
3
4
5
6
7
8
//声明一个组件
@Component
//配置绑定 properties文件中配置的前缀 car.name=byd
@ConfigurationProperties(prefix = "car")
public class Car{
    //自动赋值为 byd
    private String name;
}

2.@EnableConfigurationProperties + @ConfigurationProperties

1
2
3
4
5
//在配置类上声明 Car类会自动注册为容器中的组件并开始配置绑定
@EnableConfigurationProperties(Car.class)

//在Car类上声明 用法同上
@ConfigurationProperties(prefix = "car")

自动配置原理

引导加载自动配置类

1
2
3
4
5
@SpringBootApplication
//这个注解可以被拆分为
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
1.@SpringBootConfiguration

表示这是一个SpringBoot的配置类

2.@ComponentScan

需要扫描的路径

3.@EnableAutoConfiguration
1
2
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
  1. @AutoConfigurationPackage

    1
    2
    
    @Import({AutoConfigurationPackages.Registrar.class})
    //利用Registrar给容器导入一系列组件
    
  2. @Import({AutoConfigurationImportSelector.class})

    1
    2
    
    protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata){}
    //给容器批量导入组件
    

    SpringBoot会从所有META-INF/spring.factories中扫描需要加载的组件

    spring-boot-autoconfigure-2.3.4.RELEASE.jar下写死了127个场景的的所有配置类

按需加载

通过@Conditional条件装配注解,所有场景的自动配置类会按需加载

修改默认配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
//配置文件上传解析器
@Bean
//容器中有这个组件
@ConditionalOnBean(MultipartResolver.class)
//容器中没有这个名字 multipartResolver 的组件
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) 
public MultipartResolver multipartResolver(MultipartResolver resolver) {
    //给@Bean标注的方法传入了对象参数,这个参数的值就会从容器中找。
    //相当于对原有的对象进行重命名 文件上传解析器的名字需要符合规范
    return resolver;
}

SpringBoot默认在底层配置好了所有组件。如果需要自定义配置时,要么通过@Bean注解对底层的组件进行覆盖,SpringBoot会使用定义的组件,要么修改配置文件里面底层组件需要的值。

便捷开发

Lombok

通过注解提供实体类的Get、Set方法和有参无参构造器

  1. 引入依赖

    1
    2
    3
    4
    
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    
  2. Idea安装lombok插件

  3. 在实体类上加上注解

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    //get,set方法
    @Data
    //全参构造器
    @AllArgsConstructor
    //无参构造器
    @NoArgsConstructor
    //toString方法
    @ToString
    //引入日志
    @Slf4j
    //输出日志
    log.debug("test");
    

dev—tools

引入依赖,当代码或页面发生改动时。使用Ctorl + F9可快速重新部署

1
2
3
4
5
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency>

Spring Initailizr(SpringBoot项目初始化向导)

使用Idea新建一个Spring Initailizr项目,定义项目名、包名、需要的依赖后,Idea会自动创建Springboot项目,同时配置好依赖、项目结构和主类。

核心功能

配置文件 YAML

基本语法

以key: value的形式存储数据,: 号后面有一个空格;大小写敏感;缩进使用空格表示,不能使用tab;一次缩进表示一个层级关系;#表示注释;字符串不需要引号,'' 单引号会将转义字符作为字符串输出,""双引号会将转义字符原样输出

数据类型

  1. 字面量 基本数据类型 单个的值

    1
    2
    3
    
    name: jony
    date: 2022/11/11 12:00:00
    num: 11
    
  2. 对象、Map 多个键值对

    1
    2
    3
    4
    5
    6
    
    # 行内写法
    student: {name:jony,age:18}
    
    student: 
      name: jony
      age: 18
    
  3. 数组、集合

    1
    2
    3
    4
    5
    6
    
    # 行内写法
    names: [jony,jike]
    
    names: 
      - jony
      - jike
    

配置提示

使用插件开启配置文件提示

1
2
3
4
5
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-configuration-processor</artifactId>
  <optional>true</optional>
</dependency>

Web开发

资源访问

静态资源目录

SpringBoot默认规定的静态资源目录有:"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"

当静态资源放在这些目录中时,使用项目地址+静态资源名字即可访问,不用写二级文件夹。

请求先通过DispatcherServlet去控制层里面匹配路径,匹配不到再使用DefaultServlet去匹配静态资源,再匹配不到就抛出错误

修改访问路径
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
spring:
  mvc:
  	# 添加访问前缀
  	# 此时访问静态资源链接为 项目地址 + 访问前缀 + 资源名
    static-path-pattern: /sta/**
  web:
    resources:
      # 自定义静态资源目录 数组类型
      static-locations: classpath:/zystatic
      # 禁用所有静态资源
      add-mappings: false
WebJars

webjars是将常用的静态资源jquery、boostrap等打成jar包的形式,通过maven引用到项目中。/webjars/**的目录会自动映射

欢迎页和网站Favicon

在静态资源目录下放置 index.htmlfavicon.ico文件

如果配置了静态资源的访问前缀,那么会失效

请求参数处理

rest风格使用

rest的使用和SpringMVC中一致,但是不再需要自己去配置HiddenHttpMethodFilter,SpringBoot在底层已经配置好了,只需要在配置文件中启用。

1
2
3
4
5
spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true
普通参数和注解
  1. 方法参数

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    @GetMapping("/test/{id}/{name}")
    public Map<String, Object> test(
        		//获取请求路径参数
                @PathVariable("id")Integer id,
                @PathVariable("name")String name,
        		//将所有请求路径参数封装到Map集合 key为注解中的变量
                @PathVariable Map<String, String> pathMap,
        		//获取客户端标头
                @RequestHeader("User-Agent")String agent,
        		//获取所有标头
                @RequestHeader Map<String, String> headerMap,
        		//获取请求参数
                @RequestParam("user")String user,
                @RequestParam("age")Integer age,
        		//获取所有请求参数
                @RequestParam Map<String, String> requestMap,
        		//获取指定cookie中的值
                @CookieValue("_uname") String uname,
        		//根据key获取Cookie
                @CookieValue("_uname")Cookie cookie
        		//获取post请求体的值
        		@RequestBody String body
                ){}
    
  2. 转发参数

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    @GetMapping("/test")
    public String test(Model model){
        //Model中的数据最后会保存到Request请求中
        model.addAttribute("name", "jony");
        return "forward:/index";
    }
    @GetMapping("/index")
    @ResponseBody
    //@RequestAttribute从请求中获取参数
    public String index(@RequestAttribute("name")String name){
        return name;
    }
    
  3. 矩阵变量

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    
    //矩阵变量使用 ; 号进行分割,参数表现形式为key=value,一个key有多个value的话,value之间用 , 号分隔
    //矩阵变量必须配合路径参数使用!
    //url = /index/1;name=jony;star=lanqiu,zuqiu
    @GetMapping("/index/{id}")
    @ResponseBody
    public Map<String, Object> index(@MatrixVariable("name")String name,
                                     @MatrixVariable("star")List<String> list,
                                     @PathVariable("id")Integer id){
        Map<String, Object> map = new HashMap<>();
        map.put("name", name);
        map.put("star", list);
        map.put("id", id);
        return map;
    }
    //{"star":["zuqiu","lanqiu"],"name":"jony","id":1}
    
    //有多个路径参数时
    // url = /index/10;sname=xiaoming/20;tname=ahua
    @GetMapping("/index/{stuId}/{theId}")
    @ResponseBody
    public Map<String, Object> index(@MatrixVariable(value = "sname", pathVar = "stuId")String sname,
                                     @MatrixVariable(value = "tname", pathVar = "theId")String tname,
                                     @PathVariable("stuId")Integer stuId,
                                     @PathVariable("theId")Integer theId){
        Map<String, Object> map = new HashMap<>();
        map.put("sname", sname);
        map.put("tname", tname);
        map.put("stuId", stuId);
        map.put("theId", theId);
        return map;
    }
    //{"stuId":10,"sname":"xiaoming","tname":"ahua","theId":20}
    

    SpringBoot默认会移除路径 ; 号后面的参数,如果需要使用矩阵变量,需要手动开启

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    //1. 使用@Bean重新声明 WebMvcConfigurer 中的 PathMatchConfigurer
    @Bean
    public PathMatchConfigurer pathMatchConfigurer(){
        PathMatchConfigurer pathMatchConfigurer = new PathMatchConfigurer();
        UrlPathHelper urlPathHelper = new UrlPathHelper();
        //关闭路径截取
        urlPathHelper.setRemoveSemicolonContent(false);
        pathMatchConfigurer.setUrlPathHelper(urlPathHelper);
        return pathMatchConfigurer;
    }
    //2. 继承 WebMvcConfigurer 接口,重写其中的configurePathMatch方法
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        UrlPathHelper urlPathHelper = new UrlPathHelper();
        //关闭路径截取
        urlPathHelper.setRemoveSemicolonContent(false);
        configurer.setUrlPathHelper(urlPathHelper);
    }
    
  4. 封装为POJO类

    当控制器方法的参数为实体类时,SpringBoot可以自动将请求参数封装为实体类的属性,支持实体类的级联封装。需要注意的是请求参数的name需要和实体类属性同名,需要级联封装时请求参数的name命名为实体类的属性名+类中的属性(student.name)

自定义参数解析器 封装实体类

和开启矩阵变量一样,也有两种方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
//WebMvcConfigurer 重写其中的addFormatters方法 添加自定义的参数解析器
@Override
public void addFormatters(FormatterRegistry registry) {
    //Converter负责将请求参数转换为指定的数据类型 将String转为Book类型
    registry.addConverter(new Converter<String, Book>(){
        @Override
        //具体的转换方法
        public Book convert(String source) {
            String[] stu = source.split(",");
            Integer id = Integer.parseInt(stu[0]);
            return new Book(id, stu[1]);
        }
    });
}

//具体的请求 为Post请求 Student类包含了的一个Book类的实体类型 通过一个参数为Book类的两个属性赋值
http://localhost:8088/test/stu?id=1&name=jony&age=18&book=2,语文
数据响应和内容协商

内容协商:客户端会以请求头的方式告诉服务端它能接收什么样的数据,服务端拿到这个请求头之后会根据自身所支持的数据格式,最终决定返回哪种类型格式的数据。

服务器可以根据不同的请求头参数 Accept 返回不同类型的数据

  1. 返回Json格式的数据

    application/json

    只需要在方法上标明@ResponseBody注解或者在控制器类上标明@RestController注解就可自动返回Json数据

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    //返回的数据
    {
        "id": 1,
        "name": "jony",
        "age": 18,
        "book": {
            "id": 2,
            "name": "语文"
        }
    }
    
  2. 返回XML的数据

    application/xml

    引入Jackson的XMl依赖

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
     <dependency>
         <groupId>com.fasterxml.jackson.dataformat</groupId>
         <artifactId>jackson-dataformat-xml</artifactId>
    </dependency>
    <!--返回的数据-->
    <Student>
        <id>1</id>
        <name>jony</name>
        <age>18</age>
        <book>
            <id>2</id>
            <name>语文</name>
        </book>
    </Student>
    

以请求参数方式的内容协商功能

开启以请求参数方式的内容协商功能后,SpringBoot的优先级会以请求参数中format参数指定的格式为准,如果format指定的内容类型不支持,再以Accept标头的为准。

1
2
3
4
5
//开启
spring:
  mvc:
    contentnegotiation:
      favor-parameter: true

自定义MessageConverter

添加自定义的协议支持,可以按照请求协议的不同,分发多种不同格式的数据

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
//添加根据标头请求协议的自定义协议
//WebMvcConfigurer中的extendMessageConverters方法
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    //要定义协议支持的类型 也可以为Object
    converters.add(new HttpMessageConverter<Book>() {
        @Override
        public List<MediaType> getSupportedMediaTypes(Class<?> clazz) {
            return HttpMessageConverter.super.getSupportedMediaTypes(clazz);
        }
        @Override
        //是否可读 用于请求参数的转换
        public boolean canRead(Class<?> clazz, MediaType mediaType) {
            return false;
        }
        @Override
        //是否可写 用于返回参数的转换
        public boolean canWrite(Class<?> clazz, MediaType mediaType) {
            return clazz.isAssignableFrom(Book.class);
        }
        @Override
        //支持的协议
        public List<MediaType> getSupportedMediaTypes() {
            //自定义的协议名称
            return MediaType.parseMediaTypes("application/x-xin");
        }
        @Override
        public Book read(Class<? extends Book> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
            return null;
        }
        @Override
        //转换方法的具体实现
        public void write(Book book, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
            //通过反射获取类中的所有参数
            Class bookClass = book.getClass();
            Field[] bookFields = bookClass.getDeclaredFields();
            StringBuilder msg = new StringBuilder();
            for(Field field : bookFields){
                field.setAccessible(true);
                try {
                    Object bookObj = field.get(book);
                    //将参数拼接起来 中间用;分隔
                    msg.append(String.valueOf(bookObj)).append(";");
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
            }
            //将主体内容返回给客户端
            outputMessage.getBody().write(msg.toString().getBytes());
        }
    });
}

//添加根据请求参数的自定义协议
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    Map<String, MediaType> map = new HashMap<>();
    //添加数据类型 原生支持的json和xml也必须添加
    //设置请求参数的自定义媒体类型处理器时 必须先添加该协议
    map.put("xin", MediaType.parseMediaType("application/x-xin"));
    map.put("json", MediaType.APPLICATION_JSON);
    map.put("xml", MediaType.APPLICATION_XML);
    //根据请求参数的解析器
    ParameterContentNegotiationStrategy parameterContentNegotiationStrategy = new ParameterContentNegotiationStrategy(map);
    //根据标头的解析器 不设置的话无法通过标头进行内容协商
    HeaderContentNegotiationStrategy headerContentNegotiationStrategy = new HeaderContentNegotiationStrategy();
    //把新生成的解析器添加到配置中
    configurer.strategies(Arrays.asList(parameterContentNegotiationStrategy, headerContentNegotiationStrategy));
}
文件上传
1
2
3
4
5
//请求必须为Post 前端form表单要设置 enctype="multipart/form-data"
@PostMapping("/upload")
//@RequestPart("author")文件输入框的name属性值 使用MultipartFile[]接受多个文件
public String uploadFile(@RequestParam("email") String email, @RequestParam("username") String username,
                         @RequestPart("author")MultipartFile author, @RequestPart("imgs") MultipartFile[] imgs) throws IOException {}
1
2
3
4
5
6
7
spring:
  servlet:
    multipart:
     # 单个文件最大大小
      max-file-size: 10MB
      # 单次上传总文件 最大大小
      max-request-size: 100MB

错误处理

默认情况下,对于浏览器SpringBoot提供/error中的错误映射,这时返回的是一个白页;对于Json会响应Json格式的错误消息。

如果需要替换默认的错误页,可以在模板或者静态资源路径下新建/error目录,里面的4xx.html、5xx.html会被自动解析(根据http的错误状态码查找页面,精确查找不到就模糊查找,再查找不到就返回默认页面)

要完全自定义错误行为,可以实现ErrorController接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
//1、声明异常处理类
@ControllerAdvice
public class MyExceptionHandler {
    //该方法能够处理的异常类型
    @ExceptionHandler({NullPointerException.class, ArrayIndexOutOfBoundsException.class})
    public String nullExceptionHandler(){
        return "error";
    }
}

//2、自定义异常处理类 当抛出这个自定义异常时 会返回 value状态码和自定义的消息 message
@ResponseStatus(value = HttpStatus.CONFLICT, reason = "message")
public class ResponeException extends RuntimeException{
    public ResponeException(String message){
        super(message);
    }
}

自定义异常处理器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@Component
//优先级为最高 不然会被默认的异常处理器处理
@Order(value = Ordered.HIGHEST_PRECEDENCE)
public class CustomException implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        try {
            //直接返回错误码和错误消息
            response.sendError(512, "error");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        //返回一个ModelAndView对象 不然上层方法会忽略当前异常处理器并继续执行下一个
        return new ModelAndView();
    }
}

使用原生Servlet组件

  1. 使用@ServletComponentScan注解

    在项目主类上使用@ServletComponentScan注解声明原生Servlet组件所在的包名,SpringBoot会自动扫描。需要配合Servlet原生的注解使用。@WebServlet、@WebFilter、@WebListener

  2. 使用RegistrationBean注册Servlet组件

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    @Configuration
    public class ServletConfig {
        @Bean
        //添加Servlet组件
        public ServletRegistrationBean<MyServlet> myServlet(){
            MyServlet servlet = new MyServlet();
            //路径可以是多个
            return new ServletRegistrationBean<>(servlet, "/servlet", "/ssss");
        }
        @Bean
        //添加Listener组件
        public ServletListenerRegistrationBean<MyContextListener> MyContextListener(){
            MyContextListener myContextListener = new MyContextListener();
            return new ServletListenerRegistrationBean<>(myContextListener);
        }
    }
    // FilterRegistrationBean 添加Filter组件
    

嵌入式Web服务器

  1. 切换Web容器

    SpringBoot默认支持的Web容器有tomcat、jetty、undertow,这3个Web容器在SpringBoot的Web场景启动器里面都有自动配置类,starter-web默认的Web容器是tomcat。如果需要更换Web容器,只需要在starter里面排除之前的Web容器,再引入需要容器的依赖即可。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-undertow</artifactId>
    </dependency>
    
  2. 定制Web容器

    • SpringBoot所有关于Web的配置都在server.前缀下,只需要修改配置文件即可。
    • 实现WebServerFactoryCustomizer接口,这是SpringBoot提供的Web容器工厂自定义类

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      
      @Component
      public class MyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
      
          @Override
          //自定义容器配置的方法
          public void customize(ConfigurableServletWebServerFactory server) {
              server.setPort(9000);
          }
      
      }
      
    • 直接使用ConfigurableServletWebServerFactory

      1
      2
      3
      4
      5
      6
      
      @Bean
      ConfigurableServletWebServerFactory webServerFactory(){
          UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
          factory.setPort(8088);
          return factory;
      }
      

数据访问

SpringBoot关于数据访问都在spring-boot-startrt-data-*场景启动器下。

导入spring-boot-startrt-data-jdbc场景后,需要自己导入数据库驱动。这个场景启动器默认开启了数据源、事务管理器、JdbcTemplate(操作数据库)的自动配置,默认使用的连接池是HikariDataSource

关于数据源的配置在spring-datasource

关于JdbcTemplate的配置在spring-jdbc

使用第三方数据源

导入第三方数据源有两种方式

  1. 自定义数据源的配置类
  2. 使用数据源提供的starter
自定义方式

Druid为例,参照官方文档alibaba/druid

1
2
3
4
5
6
7
8
9
spring:
  datasource:
    url: jdbc:postgresql://120.46.151.166:5432/zhsx
    username: lisang
    password: 00000...
    driver-class-name: org.postgresql.Driver
    max-active: 10
    # 开启Druid的Sql统计和Sql防火墙
    filters: stat, wall
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Configuration
//Druid数据源的配置类
public class DruidDatasourceConfig {
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() throws SQLException {
        DruidDataSource dataSource = new DruidDataSource();
        //开启了配置文件绑定 参数直接写在配置文件中即可
//        dataSource.setUrl("//");
//        dataSource.setMaxActive(10);
//        dataSource.setFilters("stat, wall");
        return dataSource;
    }
    //配置Druid的监控面板
    @Bean
    public ServletRegistrationBean<StatViewServlet> statViewServlet(){
        StatViewServlet servlet = new StatViewServlet();
        ServletRegistrationBean<StatViewServlet> druidServlet = new ServletRegistrationBean<>(servlet, "/druid/*");
        druidServlet.addInitParameter("loginUsername", "admin");
        druidServlet.addInitParameter("loginPassword", "admin");
        return druidServlet;
    }
    //配置Druid的Web统计
    @Bean
    public FilterRegistrationBean<WebStatFilter> webStatFilter(){
        WebStatFilter statFilter = new WebStatFilter();
        FilterRegistrationBean<WebStatFilter> druidWebStatFilter = new FilterRegistrationBean<>(statFilter);
        druidWebStatFilter.addUrlPatterns("/*");
        druidWebStatFilter.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return druidWebStatFilter;
    }
}
使用druid-spring-boot-starter
  1. 导入druid-spring-boot-starter依赖

  2. 编写配置文件,参考官方文档

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    
    spring:
      datasource:
        # 数据源的连接配置
        url: jdbc:postgresql://120.46.151.166:5432/zhsx
        username: lisang
        password: 00000...
        driver-class-name: org.postgresql.Driver
        druid:
          # 配置数据统计 stat sql监控 wall sql防火墙
          filters: stat,wall
          # 连接池中的最大活跃连接数
          max-active: 10
          # 配置web监控
          web-stat-filter:
            enabled: true
            url-pattern: /*
            exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
          # 配置druid后台页面
          stat-view-servlet:
            enabled: true
            url-pattern: /druid/*
            login-password: admin
            login-username: admin
            reset-enable: false
          filter:
            wall:
              config:
              	# 阻止所有delete语句执行
                delete-allow: false
          # 配置spring监控
          aop-patterns: com.xinnn.boot.controller.*
    

整合MyBatis

参考官方文档

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
mybatis:
  # mapper接口配置文件所在的路径
  mapper-locations: classpath:mappers/*.xml
  # config-location 和 configuration 不能同时存在
  # config-location: classpath:mybatis/
  # 驼峰映射
  type-aliases-package: com.xinnn.boot.pojo
  # 配置文件中的配置项
  configuration:
    map-underscore-to-camel-case: true

注解模式,直接将操作数据库的语句写在Mapper接口的方法上。

1
2
3
4
5
6
@Mapper
public interface ScenicMapper {
    @Select("select * from t_scenic where id = #{id}")
    //可以通过 @Options 注解开启一直在sql标签里的配置
    Scenic getScenic(@Param("id") Integer id);
}

混合模式,一个Mapper接口里面,一部分方法使用注解,另一部分方法使用xml文件。

1
2
3
4
@Select("select * from t_scenic where id = #{id}")
Scenic getScenic(@Param("id") Integer id);

void deleteScenicById(@Param("id") Integer id);
1
2
3
<delete id="deleteScenicById">
    delete from t_scenic where id = #{id}
</delete>

整合MyBatis-Plus

参考官方文档

  1. 引入mybatis-plus-boot-starter依赖,依赖里面自带了mybatis

  2. 编写mapper接口和service

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    @Mapper
    //只需要继承 BaseMapper 即可,泛型是这个接口对应的实体类 不用编写Sql语句 mybatis-plus默认封装好了
    public interface UserMapper extends BaseMapper<User> {}
    
    //server接口 一样需要继承 IService 这个顶级接口 
    public interface UserService extends IService<User> {}
    
    @Service
    //service接口的实现类需要继承mybatis-plus提供的 ServiceImpl
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {}
    
  3. 直接在controller层中调用 UserServiceImpl 即可

  4. 开启分页功能

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
    @Configuration
    //mybatis-plus的配置类
    public class MyBatisPlusConfig {
        @Bean
        //配置MyBatisPlus拦截器
        public MybatisPlusInterceptor mybatisPlusInterceptor(){
            MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
            //分页插件的配置类
            PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
            //开启溢出处理
            paginationInnerInterceptor.setOverflow(true);
            //每一页的条数
            paginationInnerInterceptor.setMaxLimit(3L);
            //数据库的类型
            paginationInnerInterceptor.setDbType(DbType.POSTGRE_SQL);
            mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);
            return mybatisPlusInterceptor;
        }
    }
    
    //controller层方法
    //pageNum是需要获取数据的页码
    Page<UserS> page = new Page<>(pageNum, 3);
    //page对象封装了分页的详细内容
    page = userService.page(page, null);
    

整合NOSQL(Redis)

引入spring-boot-starter-data-redis依赖

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
spring:
  # reids的配置都在 spring.redis 下
  redis:
    host: localhost
    password: 00000...
    port: 6379
    database: 0
    # 声明 redisTempletes底层要使用那个客户端操作redis lettuce和jedis是可选的 lettuce默认选择
    # 如果需要使用 jedis 那么还需要导入jedis的依赖
    client-type: lettuce
    lettuce:
      pool:
        max-active: 10
1
2
3
4
//对redis的简单操作
ValueOperations<String, String>  redisStringTemplate = redisTemplate.opsForValue();
redisStringTemplate.set("hello", "world");
String hello = redisStringTemplate.get("hello");

单元测试

整合JUnit5

参考官方文档

SpringBoot默认以JUnit5作为单元测试默认库,想要使用只需要引入spring-boot-starter-boot依赖即可。

JUnit5由3个模块组成:

  • JUnit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。
  • JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部 包含了一个测试引擎,用于在Junit Platform上运行。
  • JUnit Vintage: JUnit Vintage提供了兼容JUnit4.x,Junit3.x的测试引擎。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
//在测试类上标注了 @SpringBootTest 后,这个测试类会以Spring的单元测试驱动运行,可以使用Spring的全部功能
@SpringBootTest
public class MyTest {
    
    @Test
    //测试方法完成后 事务会自动回滚
    @Transactional
    public void redisTest(){
        System.out.println("1");
    }
}

JUnit5的常用注解

  • @Test:表示这是一个测试方法

  • @DisplayName:为测试类和测试方法设置显示的名称

  • @BeforeEach:每个测试方法执行之前执行

  • @AfterEach:每个测试方法执行后执行

  • @BeforeAll:所有测试方法执行之前执行

  • **@AfterAll **:所有测试方法执行之之后执行

  • @Tag:表示单元测试类别

  • @Disable:标注此注解的测试类或者测试方法不执行

  • @Timeout:该测试方法执行超过了指定时间会抛出异常

  • @RepeatedTest:测试方法运行指定次数

  • @ExtendWith:为测试类或测试方法提供扩展类引用(@SpringBootTest底层也使用了该注解)

    1
    2
    
    @BootstrapWith(SpringBootTestContextBootstrapper.class)
    @ExtendWith({SpringExtension.class})
    

断言

断言是测试方法的核心,用来对需要满足的条件进行验证,并且在所有测试运行结束后,会有一个详细的测试报告。

JUnit5的断言方法都在org.junit.jupiter.api.Assertions类里面,都是静态方法。

常用的断言方法

断言方法的格式都是(你需要的值, 需要测试的值, 自定义的消息提示),通过断言方法没有通过,那么后续的代码都不会执行!

  • assertEquals:判断是否相等

  • assertNotEquals:判断是否不相等

  • assertSame:判断两个对象是否是同一个

  • assertNotSame:判断两个对象是否不是同一个

  • assertTrue:判断是否为true

  • assertFalse:判断是否为false

  • assertNull:判断值是否为null

  • assertNotNull:判断值是否不为null

  • assertArrayEquals:判断两个数组的值是否相等

  • assertAll:组合断言,可以通过lambda表达式提供多个断言方法。

    1
    2
    3
    4
    5
    6
    7
    8
    
    @Test
    @DisplayName("组合断言")
    public void testAll(){
        Assertions.assertAll(
            () -> Assertions.assertEquals(1, 1, "返回值不是1"),
            () -> Assertions.assertArrayEquals(new int[]{1,2}, new int[]{1, 3}, "数组不相等")
        );
    }
    
  • assertThrows:异常断言,配置lambda使用。如果在测试方法中抛出了指定的异常,那么断言成功,反之断言失败。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    @Test
    @DisplayName("异常断言测试")
    public void testException(){
        Assertions.assertThrows(ArrayIndexOutOfBoundsException.class,() -> {
                                    int[] nums = new int[2];
        //这里数组下标没有越界 没有抛出ArrayIndexOutOfBoundsException异常 那么断言失败
                                    nums[1] = 10;
                                }, "数组下标越界异常");
    }
    
  • assertTimeout():超时时间,测试方法执行超过指定时间时断言失败

    1
    2
    3
    4
    5
    6
    7
    
    @Test
    @DisplayName("测试方法超时")
    public void testTimeout(){
        Assertions.assertTimeout(Duration.ofMillis(500), () -> {
            Thread.sleep(600);
        }, "方法执行超时");
    }
    
  • fail:通过fail()方法可以让断言可以使测试直接失败

前置条件

前置条件Assumptionsorg.junit.jupiter.api包下,包里面的方法类似与断言,但和断言的区别是:条件不满足时断言会直接失败,而前置条件是让方法不在执行,不会报错。

1
2
3
4
5
6
@Test
@DisplayName("前置条件")
public void test(){
    Assumptions.assumeTrue(1 == 2);
    System.out.println("ok");
}

嵌套测试

通过Java的内部类和@Nested注解实现嵌套测试,内部类可以使用外部的@BeforeEach、@AfterEach注解,而外部类无法使用内部类的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
@DisplayName("A stack")
class TestingAStackDemo {

    Stack<Object> stack;

    @Test
    @DisplayName("is instantiated with new Stack()")
    void isInstantiatedWithNew() {
        new Stack<>();
    }

    @Nested
    @DisplayName("when new")
    class WhenNew {

        @BeforeEach
        //内部类的方法执行时 会先 new 一个 Stack
        void createNewStack() {
            stack = new Stack<>();
        }

        @Test
        @DisplayName("is empty")
        void isEmpty() {
            //因为先 new 了一个 Stack 并且里面现在还没有任何元素 断言通过
            assertTrue(stack.isEmpty());
        }

        @Test
        @DisplayName("throws EmptyStackException when popped")
        void throwsExceptionWhenPopped() {
            //现在 stack 里面没有任何元素 无法调用stack的pop方法弹出一个元素 抛出EmptyStackException异常 断言通过
            assertThrows(EmptyStackException.class, stack::pop);
        }

        @Test
        @DisplayName("throws EmptyStackException when peeked")
        void throwsExceptionWhenPeeked() {
            //同上
            assertThrows(EmptyStackException.class, stack::peek);
        }

        @Nested
        @DisplayName("after pushing an element")
        class AfterPushing {

            String anElement = "an element";

            @BeforeEach
            void pushAnElement() {
                stack.push(anElement);
            }

            @Test
            @DisplayName("it is no longer empty")
            void isNotEmpty() {
                //执行方法会向调用外部的 createNewStack() 在执行内部的 @BeforeEach 这时stack不为空了
                assertFalse(stack.isEmpty());
            }

            @Test
            @DisplayName("returns the element when popped and is empty")
            void returnElementWhenPopped() {
                //stack可以弹出一个元素
                assertEquals(anElement, stack.pop());
                //弹出一个元素后里面又空了
                assertTrue(stack.isEmpty());
            }

            @Test
            @DisplayName("returns the element when peeked but remains not empty")
            void returnElementWhenPeeked() {
                //同上
                assertEquals(anElement, stack.peek());
                assertFalse(stack.isEmpty());
            }
        }
    }
}

参数化测试

测试方法可以根据提供的参数进行多次执行

  • @ValueSource:指定参数来源,支持基本类型和String、Class类型
  • @NullSource:提供一个空参数
  • @EnumSource:提供一个枚举参数
  • @CsvFileSource:读取csv文件作为参数
  • @MethodSource:使用指定方法的返回值作为参数,返回值需要是一个流类型Stream
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@ParameterizedTest
@DisplayName("int入参")
//使用基本类型作为入参
@ValueSource(ints = {1,2,3,4})
public void testValueSource(int i){
    System.out.println(i);
}

//使用方法返回值作为入参
@ParameterizedTest
@DisplayName("method入参")
@MethodSource("getAllUser")
public void testMethodSource(User user){
    System.out.println(user.getUsername());
    System.out.println(user.getPassword());
}
static Stream<User> getAllUser(){
    User user = new User("admin", "admin");
    return Stream.of(user);
}

指标监控

对上线的应用进行监控、追踪和控制等,SpringBoot提供了Actuator场景用来快速的对微服务应用进行监控和审计等操作

使用
  1. 引入spring-boot-starter-actuator依赖

  2. 为监控信息开启http支持

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    # 所有对 actuator 的操作都在 management.** 下
    management:
      endpoints:
      	# 是否开放所有端口信息
        enabled-by-default: true
      # 也可以单独开放某个端口
      # endpoint:
        # health:
          # enabled: true
      endpoint:
        # 让health始终显示详细信息
        health:
          show-details: always
        web:
          exposure:
          	# 以web方式暴露所有端口 默认情况大部分端口都是以jmx暴露的
            include: '*'
    
  3. 访问http://localhost:8080/actuator/,返回的格式为Json。

Actuator Endpoint
ID 描述
auditevents 暴露当前应用程序的审核事件信息。需要一个AuditEventRepository组件
beans 显示应用程序中所有Spring Bean的完整列表。
caches 暴露可用的缓存。
conditions 显示自动配置的所有条件信息,包括匹配或不匹配的原因。
configprops 显示所有@ConfigurationProperties
env 暴露Spring的属性ConfigurableEnvironment
flyway 显示已应用的所有Flyway数据库迁移。 需要一个或多个Flyway组件。
health 显示应用程序运行状况信息。
httptrace 显示HTTP跟踪信息(默认情况下,最近100个HTTP请求-响应)。需要一个HttpTraceRepository组件。
info 显示应用程序信息。
integrationgraph 显示Spring integrationgraph 。需要依赖spring-integration-core
loggers 显示和修改应用程序中日志的配置。
liquibase 显示已应用的所有Liquibase数据库迁移。需要一个或多个Liquibase组件。
metrics 显示当前应用程序的“指标”信息。
mappings 显示所有@RequestMapping路径列表。
scheduledtasks 显示应用程序中的计划任务。
sessions 允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序。
shutdown 使应用程序正常关闭。默认禁用。
startup 显示由ApplicationStartup收集的启动步骤数据。需要使用SpringApplication进行配置BufferingApplicationStartup
threaddump 执行线程转储。

如果是Web应用程序,还可以使用附加端口:

ID 描述
heapdump 返回hprof堆转储文件。
jolokia 通过HTTP暴露JMX bean(需要引入Jolokia,不适用于WebFlux)。需要引入依赖jolokia-core
logfile 返回日志文件的内容(如果已设置logging.file.namelogging.file.path属性)。支持使用HTTPRange标头来检索部分日志文件的内容。
prometheus 以Prometheus服务器可以抓取的格式公开指标。需要依赖micrometer-registry-prometheus

3个常用的Endpoint:

  • Health:监控状况
  • metrics:运行时指标
  • Loggers:日志记录
自定义Health

通过自定义Health,可以添加添加自己想要判断健康状况的组件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//判断网络是否联通
@Component
public class NetworkHealthIndicator extends AbstractHealthIndicator {
    @Override
    protected void doHealthCheck(Health.Builder builder) throws Exception {
        Map<String, String> map = new HashMap<>();
        long startTime = System.currentTimeMillis();
        boolean bl = InetAddress.getByName("120.46.151.166").isReachable(3000);
        long time = System.currentTimeMillis() - startTime;
        if(bl){
            //up表示健康状况良好
            builder.up();
            map.put("msg", "网络连接正常");
            map.put("120.16.151.166", "连接耗时" + time + "毫秒");
        }else{
            //down表示宕机
            builder.down();
            map.put("msg", "网络连接失败");
        }
        //往builder中添加消息体
        builder.withDetails(map);
    }
}
自定义Info
1
2
3
4
5
6
7
8
9
@Component
//需要继承 InfoContributor 接口
public class MyInfoContributor implements InfoContributor {
    @Override
    public void contribute(Info.Builder builder) {
        //添加信息
        builder.withDetail("appName", "admin").withDetail("appVersion", "1.0");
    }
}
自定义Metrics
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@Service
public class ScenicService {
    @Autowired
    private ScenicMapper scenicMapper;
    Counter counter;
    public ScenicService(MeterRegistry meterRegistry){
        //计数的指标监控
        counter = meterRegistry.counter("scenicServer.getScenic.count");
    }
    public Scenic getScenic(Integer id){
        //每次访问这个方法就加1
        counter.increment();
        return scenicMapper.getScenic(id);
    }
}
定制Endpoint
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@Component
//这里的id就是后面访问的路径
@Endpoint(id = "main")
public class MyEndpoint {
    @ReadOperation
    public Map<String, String> getMyEndpoint(){
        Map<String, String> map = new HashMap<>();
        map.put("msg", "ok");
        //map会被序列化为json响应给客户端
        return map;
    }
    @WriteOperation
    public void stopMyEndpoint(){
        System.out.println("stop");
    }
}
可视化面板(spring-boot-admin-starter-server)

参考官方文档

  • 服务端

    引入spring-boot-admin-starter-server依赖

    在程序主类上添加@EnableAdminServer注解,访问项目地址/applications即可

  • 客户端(被监控端)

    引入spring-boot-admin-starter-client依赖

    更改配置文件

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    
    spring:
      boot:
        admin:
          client:
            # 指向服务端的地址
            url: http://localhost:8087
     management:
      endpoints:
        enabled-by-default: true
        # web暴露所有的端点
        web:
          exposure:
            include: '*'
    

原理解析

Profile功能

  1. application-profile

    默认配置文件application.yml任何时候都会被加载

    环境配置文件application-{env}.yml只有在指定在指定使用该环境时才会被加载

    • 配置文件指定

      1
      2
      3
      4
      
      spring:
        profiles:
        	# 指定加载 application-test.yml 配置文件
          active: test
      
    • 命令行指定

      在通过命令行启动jar包的时候,可以通过命名参数指定

      1
      2
      3
      4
      
      java -jar xxx.jar --spring.profile.active=test
      
      # 命令行参数也可以指定配置文件中的任意配置
      # 比如 --server.prot=8081 指定端口为8081 如果与配置文件中的配置项重复 那么命令行的优先
      

    默认配置文件和环境配置会同时加载,如果两个配置文件中有配置项重复,会以环境配置文件中的优先。

  2. @Profile条件装配

    @Profile注解可以标注在类和方法上

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    @Configuration
    //只有使用 prod 环境配置文件的时候,这个组件才会生效
    @Profile("prod")
    public class MyConfig {
        @Bean
        ConfigurableServletWebServerFactory webServerFactory(){
            UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
            factory.setPort(8088);
            return factory;
        }
    }
    
  3. 配置分组

    application.yml配置文件里面新增spring.profiles.group.分组名字[0]=环境配置文件名称配置项

    1
    2
    3
    4
    5
    
    spring.profiles.group.production[0]=proddb
    spring.profiles.group.production[1]=prodmq
    
    # 启动jar包的时候 使用 --spring-profile-active=production 来指定配置文件组
    # 组里面包含的环境配置文件都会生效
    
苟利国家生死以 岂因福祸避趋之
Built with Hugo
主题 StackJimmy 设计