SpringBoot

Spring 技术栈的一站式框架 => SpringBoot

springboot
springboot

快速上手

General Availability (GA) is the release of a product to the general public.

开发中尽可能的选择 SpringBoot General Availability 正式发布版。

HelloWorld

The @SpringBootApplication annotation is equivalent to using @Configuration, @EnableAutoConfiguration, and @ComponentScan with their default attributes.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication { ... }

@ComponentScan without arguments tells Spring to scan the current package and all of its sub-packages.

@ComponentScan 通常与 @Configuration 注解一起使用:

@component 注解在 xml 文件中使用标签的方式为指定类创建 bean。

此处 @ComponentScan 的作用是:扫描 @SpringBootApplication 所在的 Application 类(即 spring-boot 项目的入口类)所在的包 basepackage 下所有的 @component 注解(或拓展了 @component 的注解)标记的 bean,并注册到容器中。

Indicates the contexts in which an annotation type is applicable. The declaration contexts and type contexts in which an annotation type may be applicable are specified in JLS 9.6.4.1, and denoted in source code by enum constants of java.lang.annotation.ElementType.

@Target 注解说明 Annotation 所修饰的对象范围,Annotation 可被用于类、接口、枚举和 Annotation 类型、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。

/* 主程序类 => springboot 应用入口 */
@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class,args);
    }
}
  • 控制器 => 处理交互
// @ResponseBody
// @Controller
@RestController // @Controller + @ResponseBody
public class HelloController {
    @RequestMapping("/hello")
    public String handle01(){
        return "Hello SpringBoot By zs";
    }
}
  • Maven 配置文件

The Spring Boot Maven Plugin provides Spring Boot support in Apache Maven. It allows you to package executable jar or war archives, run Spring Boot applications, generate build information and start your Spring Boot application prior to running integration tests.
Spring Boot Maven 插件在 Apache Maven 中提供 Spring Boot 支持。允许打包可执行 jar 或 war 档案、运行 Spring Boot 应用程序、生成构建信息并在运行集成测试之前启动 Spring Boot 应用程序。

这里说明一下 springboot-maven-plugin 这个插件。虽可直接使用 maven 打包或 IntelliJ Idea 自带的打包,但此插件打包出来的 jar 可作为一个独立的服务直接启动,而 maven 自带的插件打包,大部分场景是作为一个被使用的依赖方法库,不作为可启动的独立的服务。

<!-- pom.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.zszy</groupId>
    <artifactId>boot-01-helloworld</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <!-- 作为父级项目为基于 spring 的应用程序提供默认配置 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.0</version>
    </parent>
    <!-- 获取与Web开发相关的所有依赖关系 -->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <!-- Maven 中提供 Spring Boot 支持 -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${project.parent.version}</version>
            </plugin>
        </plugins>
    </build>
</project>
  • resources 中 application.properties 用于配置属性,查阅点此

  • 打包后终端运行测试

# 默认已执行打包操作
# 具备 tomcat 依赖的打包, 可以做 web 项目直接运行
$ cd /Users/<zszy>/.../boot-01-helloworld/target
$ java -jar boot-01-helloworld-1.0-SNAPSHOT.jar

自动配置原理

  • 依赖管理与版本仲裁

父项目做依赖管理,其内可能声明较多的依赖。子项目在继承父项目后可不必纠结于依赖版本。

spring-boot-dependencies 声明了开发中常用的依赖版本。若需修改其他版本,可于项目 pom.xml 中增加 <properties> 标签指明。自定义修改版本号的前提是查看规定的当前依赖以及重写配置。

<!-- pom.xml -->
...
<properties>
    <mysql.version>5.1.43</mysql.version>
</properties>

开发中所导入的 starter 场景启动器,即是一组依赖的集合描述。

spring-boot-starter-* 中的 * 指某具体场景。只要引入 starter,这个场景中所有的常规依赖都将自动引入。 *-spring-boot-starter 是第三方提供的简化开发的场景启动器。

  • 自动配置

SpringBoot 会自动引入并配置所需资源。

主程序所在包及其下边所有子包内的组件都会被默认扫描。若需改变扫描路径,应在 @SpringBootApplicationscanBasePackages 属性中指定。

@SpringBootApplication(scanBasePackages="com.zszy")

底层注解

@Configuration

@Configuration 在类上标注,表示这是配置类,同时也是一个组件。配置类中可使用 @Bean 标注在方法上给容器注册组件,默认为单实例。

  • 配置类组件之间无依赖关系,可用 Lite 模式加速容器的启动,减少判断。
  • 配置类组件之间有依赖关系,用默认的 Full 模式,方法会被调用得到之前单实例组件。
@Configuration(proxyBeanMethods = true) // 通知 SpringBoot 这是配置类
/*
    Full(proxyBeanMethods = true) => 保证每个 @Bean 方法被调用多少次返回的组件都是单实例的 => 默认
    Lite(proxyBeanMethods = false) => 每个 @Bean 方法被调用多少次返回的组件都是新创建的
*/
public class MyConfig {
    @Bean // 给容器添加组件 => 以方法名作为组件的 id; 返回类型就是组件类型; 返回的值就是组件在容器中保存的实例
    public User user01(){
        User zs = new User("zs",19,"male");
        // User 组件依赖于 Pet 组件
        zs.setPet(pet01());
        return zs;
    }
    @Bean
    public Pet pet01(){
        return new Pet("cat");
    }
}
/* 主程序类 => 这是一个 springboot 应用 */
@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        // 返回 ioc 容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
        // 查看容器组件
        String[] beanDefinitionNames = run.getBeanDefinitionNames();
        for(String name : beanDefinitionNames){
            System.out.println(name); // myConfig、helloController、user01、pet01
        }
        // 从容器中获得组件是单实例的
        User user01 = run.getBean("user01", User.class);
        Pet pet01 = run.getBean("pet01", Pet.class);
        System.out.println(user01.getPet() == pet01); // proxyBeanMethods = true => user 中的 pet 是容器的 pet
        // 配置类也是组件
        MyConfig bean = run.getBean(MyConfig.class);
        System.out.println(bean); // com.zszy.boot.config.MyConfig$$EnhancerBySpringCGLIB$$1fc234a8@736ac09a
    }
}

@Import 导入组件

@Import 的作用是通过导入组件的无参构造器,在容器中自动创建导入的组件对象。此方式默认导入组件名为全类名,和通过 @Bean 导入的名称不一样。

@Import(User.class) // 导入组件名称是全类名
@Configuration(proxyBeanMethods = true) // 通知 SpringBoot 这是配置类
public class MyConfig {...}
@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
        ...
        // 测试不同方式存在于容器的组件名
        String[] beanNamesForType = run.getBeanNamesForType(User.class);
        for(String bNameFType : beanNamesForType){
            System.out.println(bNameFType); // com.zszy.boot.bean.User、user01
        }
    }

@Conditional 条件装配

条件装配是需满足 Conditional 指定的条件,则进行组件注入的操作。

@Import(User.class) // 导入组件名称是全类名
@Configuration(proxyBeanMethods = true) // 通知 SpringBoot 这是配置类
@ConditionalOnMissingBean(name = "ok") // 没有 ok 名字的 Bean 时,MyConfig 类的 Bean 才能生效
public class MyConfig {}
// 测试组件是否根据条件被成功注入 => Conditional => ConditionalOnBean
System.out.println(run.containsBean("user01"));

@ImportResource 导入配置文件

祖传代码很可能仍使用 bean.xml 文件生成配置 bean,若想复用 bean.xml,可通过 @ImportResource 引入 Spring 配置文件。

@ImportResource("classpath:beans.xml")
public class MyConfig { ... }

@ConfigurationProperties 配置绑定

在已存在于容器中的组件,通过使用 Java 读取到 properties 文件内容,并且将其封装到 JavaBean。

  • @Component + @ConfigurationProperties

@ConfigurationPropertiesprefix 表示类中属性和配置文件具体前缀下的所有属性一一绑定。

<!-- application.properties -->
...
mycar.brand=Audi
mycar.price=500000
@Component
@ConfigurationProperties(prefix="mycar")
public class Car {...}
@RestController // @Controller + @ResponseBody
public class HelloController {
    ...
    @Autowired
    Car car;
    @RequestMapping("/car")
    public Car showCar(){
        return car;
    }
}
  • @EnableConfigurationProperties + @ConfigurationProperties

因省略 @Component 注解,Spring 不会自动检测定义的 bean,故此方式需在配置类添加,因配置类是容器中的组件。通过 @EnableConfigurationProperties 激活属性配置功能,并指定开启具体类的属性配置。通常用于不修改第三方包。

@Configuration(proxyBeanMethods = true) // 通知 SpringBoot 这是配置类
...
@EnableConfigurationProperties(Car.class) // 开启 Car 配置绑定功能并将 Car 组件自动注入容器
public class MyConfig { ... }
// @Component
@ConfigurationProperties(prefix="mycar")
public class Car { ... }

配置增强

Lombok

Lombok 是一个库,通过标签方式代替构造器、getter/setter、toString 等方法。

  • spring boot 已管理 Lombok
<dependency>
     <groupId>org.projectlombok</groupId>
     <artifactId>lombok</artifactId>
</dependency>
  • File -> Settings -> Plugins 搜索安装 Lombok 插件
@NoArgsConstructor
@Data
@ToString
@EqualsAndHashCode
public class <class> {}
// ------
@Slf4j
@RestController
public class HelloController {
    @RequestMapping("/hello")
    public String handle01(@RequestParam("name") String name){
        log.info("log info ...");
        return "Hello SpringBoot By zs"+name;
    }
}

Developer Tools

Developer Tools 是在修改代码或者页面后,免去重复启动项目的 Auto restart。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency>

更新代码后通过 cmd + f9 进行编译即可看见反馈。mac m1 芯片需唤醒。

注意此方式外接键盘 => fn + cmd + f9。

yaml|yml 配置文件

yaml 是一种标记语言,适合做以数据为中心的配置文件。properties 和 yaml|yml 配置文件生效有先后顺序,前者优先级更高。

基本语法 => 大小写敏感、缩进表示层级、缩进不允许使用tab,只允许空格、缩进的空格数不重要,只要相同层级的元素左对齐即可、'#'表示注释

注意其他场景中单引号会将转义字符作为字符串输出,双引号会将转义字符转义输出。但是在配置文件是否加单双引号效果一样。

  • 配置提示依赖
<!-- 注释处理器 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>
  • springboot 打包插件避免打包配置处理器
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <!-- 工程打包时不将 spring-boot-configuration-processor 打进包内; 只在编码时候起作用 -->
            <configuration>
                <excludes>
                    <exclude>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-configuration-processor</artifactId>
                     </exclude>
                </excludes>
            </configuration>
        </plugin>
    </plugins>
</build>

web

Spring Boot provides auto-configuration for Spring MVC that works well with most applications. click me.

静态资源

  • 静态资源目录

Spring Boot 项目类路径 classpath 指的是 resources 文件夹。静态资源能识别放在类路径下的 /static、/public、/resources、/META-INF/resources 目录。

静态资源可通过当前项目根路径分隔静态资源名访问,但需要注意是否与控制器中映射处理方法的路径重名。这是由于请求进来首先会去找控制器进行处理,若不能处理才会将请求交给静态资源处理器。

通常静态资源访问会采取改变默认静态资源路径或增加访问前缀的方式。前者会使默认访问路径失效,后者则是需要拼接指定前缀才可正常访问。

# 改变默认静态资源路径
resources:
  static-locations: [classpath:/changedPath/]
# 增加访问前缀
spring:
  mvc:
    static-path-pattern: /prefixRes/**
  • WebJars 原理

原始 Web 开发时,一般都是将静态资源文件放置在 webapp 目录;在 Spring Boot 里,一般是将资源放在 src/main/resources/static 目录;Servlet3 中,允许直接访问 WEB-INF/lib 下 jar 包中的 /META-INF/resources 目录资源。WebJars 就是利用此特性,将所有前端静态文件打成 Jar 包,此方式的引入就会和普通 Jar 引入一致,也能更好的对前端静态资源进行管理。

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.6.0</version>
</dependency>

jQuery 可通过 http://localhost:8080/webjars/jquery/3.6.0/jquery.js 访问。

  • WelcomePage 欢迎页

欢迎页支持静态资源路径下的 index.html。可以配置静态资源路径,但不可以配置静态资源的访问前缀,否则可能导致不能被默认访问。在静态资源目录下设置 favicon.ico 后不能及时更新的原因可能在于此图标是在整个 session 中共享。

请求处理

  • @PathVariable => 处理路径映射中以占位符表示的路径变量
  • @RequestHeader => 获取请求头(可通过参数指定获取)
  • @RequestParam => 获取请求参数(指问号后的参数,url?a=1&b=2)
  • @CookieValue => 获取 Cookie 值(可通过参数指定获取)
  • @RequestAttribute => 获取 request 域属性(页面转发取出数据)
  • @RequestBody => 获取 POST 中的请求体
  • @MatrixVariable => 矩阵变量(绑定在路径变量中)

查询字符串的请求形式通常根据 @RequestParam 获取;在路径变量的大括号里以分号区别的请求形式,称为矩阵变量形式,可通过 @MatrixVariable 获取。

SpringMVC 禁用矩阵变量时,则需手动开启。

// 矩阵变量形式 => 禁用 Cookie 时, 操作 Session 中内容的方法
/xxx/{path;param1=value1,param2=value2a,value2b,value2c}
@Configuration(proxyBeanMethods = false)
public class WebConfig implements WebMvcConfigurer {
    // 创建返回WebMvcConfigurerBean
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper urlPathHelper = new UrlPathHelper();
                // 不移除;后面的内容。矩阵变量功能就可以生效
                urlPathHelper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(urlPathHelper);
            }
        };
    }
//    实现WebMvcConfigurer接口
//    @Override
//    public void configurePathMatch(PathMatchConfigurer configurer) {
//        UrlPathHelper urlPathHelper = new UrlPathHelper();
//        // 不移除;后面的内容。矩阵变量功能就可以生效
//        urlPathHelper.setRemoveSemicolonContent(false);
//        configurer.setUrlPathHelper(urlPathHelper);
//    }
}
<h1>Welcome</h1>
<h3>测试基本注解</h3>
<a href="/car/3/owner/zs?param1=param1&param2=param2&age=21&paramList=guitar&paramList=code">常用参数注解测试</a>
<br />
<form action="/inputPost" method="post">
    <span>测试@RequestBody获取数据</span><br />
    用户名:<input name="userName" /><br />
    邮箱:<input name="email" /><br />
    <input type="submit" value="submit">
</form>
<a href="/cars/sell;lowestPrice=50;brand=bmw,audi,benz">测试矩阵变量1</a>
<a href="/boss/1;age=22/2;age=23">测试矩阵变量2</a>
@RestController
public class ParameterTestController {
    // 常用参数注解测试
    @GetMapping("/car/{id}/owner/{username}")
    public Map<String,Object> getCar(@PathVariable("id") Integer id,
                                     @PathVariable("username") String name,
                                     @PathVariable Map<String,String> pv,
                                     @RequestHeader("User-Agent") String userAgent,
                                     @RequestHeader Map<String,String> header,
                                     @RequestParam Map<String,String> params,
                                     @RequestParam("age") Integer age,
                                     @RequestParam("paramList") List<String> paramList,
                                     @CookieValue("_ga") String _ga,
                                     @CookieValue("_ga") Cookie cookie
                                     ){
        Map<String,Object> map = new HashMap<>();
        map.put("id",id);
        map.put("name",name);
        map.put("pv",pv);
        map.put("userAgent",userAgent);
        map.put("headers",header);
        map.put("age",age);
        map.put("paramList",paramList);
        map.put("params",params);
        map.put("_ga",_ga);
        System.out.println(cookie.getName()+"===>"+cookie.getValue());
        return map;
    }
    // 测试@RequestBody获取数据
    @PostMapping("/inputPost")
    public Map postMethod(@RequestBody String content){
        Map<String,Object> map = new HashMap<>();
        map.put("content",content);
        return map;
    }
    // 测试矩阵变量
    @GetMapping("/cars/{path}")
    public Map carsSell(@MatrixVariable("lowestPrice") Integer lowestPrice,
                        @MatrixVariable("brand") List<String> brand,
                        @PathVariable("path") String path){
        Map<String,Object> map = new HashMap<>();

        map.put("lowestPrice",lowestPrice);
        map.put("brand",brand);
        map.put("path",path);
        return map;
    }
    // 矩阵变量存在同名情况
    @GetMapping("/boss/{bossId}/{empId}")
    public Map boss(@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge,
                    @MatrixVariable(value = "age",pathVar = "empId") Integer empAge){
        Map<String,Object> map = new HashMap<>();
        map.put("bossAge",bossAge);
        map.put("empAge",empAge);
        return map;
    }
}

视图解析

由于 JSP 不支持压缩包内编译的方式,而 Spring Boot 默认打包方式为 Jar 包,故不支持 JSP。推荐的第三方模板引擎技术点此

  • thymeleaf

thymeleaf 语法简单,贴近 JSP,但并不是高性能模板引擎,适用于单体用户。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

Thymeleaf 配置位于 ThymeleafProperties,新建页面放在 templates 文件夹中。

数据访问

数据库场景配置与整合

spring-boot-starter-data-* 都是底层整合数据访问相关的。

  • 引入 jdbc 场景

spring-data-jdbc 进行 jdbc 相关操作,spring-boot-starter-jdbc 中导入数据库连接池 HikariCP。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

HikariCP => 数据库连接池
spring-jdbc => jdbc 操作
spring-tx => 事务操作

由于不能确定使用何种数据库,故官方不会将数据库驱动自动导入。

  • 配置项
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/user_db?serverTimezone=UTC&characterEncoding=utf-8
    username: root
    password: admin123
    driver-class-name: com.mysql.cj.jdbc.Driver
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.9</version>
</dependency>

整合 MyBatis

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.2</version>
</dependency>

需要注意的是,若在 application.yaml 指明 MyBatis 的配置文件位置,就不应该在 configuration 选项中再次指定具体配置,否则会出现 IllegalStateException。

导入MyBatis官方Starter。
编写Mapper接口,需@Mapper注解才会被扫描。
编写SQL映射文件并绑定Mapper接口。
在application.yaml中指定Mapper配置文件的所处位置,以及指定全局配置文件的信息 (建议:配置在mybatis.configuration)。

整合MyBatis-注解配置混合版

CREATE TABLE `user_db`.`city` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(45) NULL,
  `state` VARCHAR(45) NULL,
  `country` VARCHAR(45) NULL,
  PRIMARY KEY (`id`));

https://github.com/mybatis/spring-boot-starter/wiki/Quick-Start

既可以在 Mapper 中以注解标注某个方法,也可将方法配置在配置文件中。

一个一个扫描 Mapper 较繁琐可以在主程序为 @MapperScan 添加指定的扫描包,即包中全是 mapper。

mybatisplus

MybatisPlusAutoConfiguration配置类,MybatisPlusProperties配置项绑定。

SqlSessionFactory自动配置好,底层是容器中默认的数据源 ConditionalOnMissingBean。

mapperLocations自动配置好的,有默认值classpath*:/mapper/**/*.xml,这表示任意包的类路径下的所有mapper文件夹下任意路径下的所有xml都是sql映射文件。 建议以后sql映射文件放在 mapper下。

容器中也自动配置好了SqlSessionTemplate。

由于 registerBeanDefinitions 方法,@Mapper 标注的接口也会被自动扫描,建议直接 @MapperScan("com.lun.boot.mapper")批量扫描。

MyBatisPlus优点之一:只需要我们的Mapper继承MyBatisPlus的BaseMapper 就可以拥有CRUD能力,减轻开发工作。

整合 redis

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

官方整合的自动配置包都在原生的 spring-boot-autoconfigure 中。

/usr/local/Cellar/maven/3.8.5/libexec/repository/org/springframework/boot/spring-boot-autoconfigure/2.7.0/spring-boot-autoconfigure-2.7.0.jar!/org/springframework/boot/autoconfigure/data/redis

RedisAutoConfiguration 自动配置类。RedisProperties 属性类 --> spring.redis.xxx是对redis的配置。

连接工厂 LettuceConnectionConfiguration、JedisConnectionConfiguration 分别对应新生与经典两种客户端。xxxTemplate 就是用于操作 xxx 的。

redis 经典数据结构就是 kv 类型,RedisTemplate<Object, Object> 对应 kv 都可是 object;StringRedisTemplate,key,value都是String。

@AutoConfiguration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
    public RedisAutoConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean(
        name = {"redisTemplate"}
    )
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        return new StringRedisTemplate(redisConnectionFactory);
    }
}

整合 JUnit5

  • JUnit 5 组成 => JUnit Platform、JUnit Jupiter、JUnit Vintage

JUnit 平台是在 JVM 上启动测试框架的基础,支持其他测试引擎的接入。
JUnit Jupiter 是 JUnit5 新特性的核心测试引擎,用于在 Junit Platform 上运行。
JUnit Vintage 提供兼容 JUnit4.x、JUnit3.x 的测试引擎。

Spring Boot 2.4 以上版本移除了默认 Vintage 的依赖,spring-boot-starter-test 没有了相应 Vintage 包。如果需要兼容 JUnit4 应自行引入相关依赖。

Spring 中 JUnit5 的基本单元测试模板使用 @SpringBootTest 注解,在 JUnit4 中是 @SpringBootTest+@RunWith(SpringRunner.class)。

  • 依赖注入
<!-- JUnit Vintage => 兼容 JUnit4 => 可选 -->
<dependency>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!-- JUnit5 对应的 starter => 必须 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

指标监控 Actuator

大型应用常拆分成微服务进行开发,当把这些微服务部署上线时,需要全天候的监控这些微服务的运行状况信息。Actuator 是 Spring-Boot 为简化指标监控和减少冗余代码所抽取的专门模块。此外也可以使用 spring-boot-admin

  • 依赖注入
<!-- 在微服务中引入 spring-boot-starter-actuator 依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

在 actuator 依赖注入后会被动引入用于自动配置指标监控的 autoconfigure 以及底层框架 micrometer(用于自定义指标监控)。

在引入场景成功后可通过 http://localhost:8080/actuator/[EndPoint] 查看具体监控端点的信息 => Metrics 运行时指标、Health 健康状况指标...

支持的暴露方式有 HTTP(默认只暴露 health 和 info Endpoint)以及 JMX(默认暴露所有Endpoint)。JMX 方式暴露可以在 jconsole 控制台的 MBean 查看。

若有监控大面板的需求,常要将每个监控端点模式暴露成 HTTP,这里需要有前端的配合,通过前端发送请求,指定展示具体的监控内容。后端在配置文件将端点以 Web 方式进行暴露。

management: # 所有监控有关配置 => actuator
  endpoints:
    enabled-by-default: true # 默认开启所有监控端点
    web: # 以 web 方式暴露
      exposure:
        include: '*' # 暴露所有端点 => http://localhost:8080/actuator/[Endpoint]/detailPath
  • 具体端点信息

健康检查端点一般用于云平台定时的检查应用健康状况,需要返回当前应用的一系列状况集合。

management:
  ...
  endpoint:
    health:
      show-details: always

Metrics Endpoint 提供详细且有层级的空间指标信息,这些信息能以主动推送 pull 或者被动获取 push 的方式获得。

management:
  endpoints:
    enabled-by-default: false # 默认关闭所有监控端点 => 没有开启不准访问
    web:
      exposure:
        include: '*'
  endpoint:
    health:
      show-details: always
      enabled: true # 端点开放
    info:
      enabled: true
    beans:
      enabled: true
  • 定制端点信息 => 通常用于对项目中额外引用组件的状态检查

定制 EndPoint 信息主要是以实现 [EndPoint]Indicator 接口或继承其 Indicator 的抽象类来完成相关需求。

@Component // 将此组件放入容器
public class MyComHealthIndicator extends AbstractHealthIndicator {
    /**
     * mongodb 获取连接测试
     * @param builder
     * @throws Exception
     */
    @Override
    protected void doHealthCheck(Health.Builder builder) throws Exception {
        // 作为细节信息的容器
        Map<String,Object> map = new HashMap<>();
        // 模拟检查过程
        if(1 == 2){
        // 模拟处理方式 => builder.up() -> 健康返回|builder.down() -> 不健康返回
            builder.status(Status.UP);
            map.put("count",1);
            map.put("ms",100);
        } else {
            builder.status(Status.OUT_OF_SERVICE);
            map.put("err","连接超时");
            map.put("ms",3000);
        }
        // 附带业务状态码等信息...
        builder.withDetail("code",100).withDetails(map);
    }
}
# 这种方式失效建议使用下一种
info:
  appName: boot-admin
  version: 1.0.1
  mavenProjectName: @project.artifactId@ # 使用@@可以获取 maven 的 pom 文件值
  mavenProjectVersion: @project.version@
@Component
public class MyInfoContributor implements InfoContributor {
    @Override
    public void contribute(Info.Builder builder) {
        builder.withDetail("MyInfo", Collections.singletonMap("user", "zs"));
    }
}

在自定义增强 Metrics 监控时,可以使用构造器注入的方式让有参构造器的参数标识为 MeterRegistry 注册中心,通过 MeterRegistry 类型的形参调用不同的度量指标完成注册,最后只需要在待监控的位置进行监控操作即可。

// 方式一
class MyService{
    Counter counter;
    public MyService(MeterRegistry meterRegistry){
         counter = meterRegistry.counter("myservice.method.running.counter"); // 指标名
    }
    public void hello() {
        counter.increment();
    }
}
// 方式二
@Bean
MeterBinder queueSize(Queue queue) {
    return (registry) -> Gauge.builder("queueSize", queue::size).register(registry);
}

除了在已有端点进行拓展外,当官方给出的监控端点不能满足需求时,应自定义监控端点。

@Component // 放入容器
@Endpoint(id = "myDocker") // 表示自定义的监控端点
public class DockerEndpoint {
    @ReadOperation // 端点读操作
    public Map getDockerInfo(){
        return Collections.singletonMap("info","docker started...");
    }
    @WriteOperation // 端点写操作
    private void restartDocker(){
        System.out.println("docker restarted....");
    }
}

其他功能

Profile

配置文件是框架的核心,Profile 功能增强多环境适配。

  • application-profile

application.yaml => 默认加载的配置文件
application-prod|test|dev.yaml => 根据激活环境加载不同的配置文件

因为默认配置文件在任何时候都会加载,所以适合指定激活的环境。当配置环境成功后,项目会同时加载默认配置文件以及激活的环境配置文件。当这两个文件出现同名配置,以环境标识的配置文件优先。注意激活的方式除了配置文件,也可以通过命令行完成。命令行激活的方式优先级高于配置项。

# application.yaml
spring.profile.active=dev|test|prod # 指定激活的环境
# 通常用于项目打包后激活指定方式的 jar 包
java -jar xxx-SNAPSHOT.jar --spring.profiles.active=prod --person.name=zs
  • @Profile 条件装配

当类或者方法上添加了注解 @Profile("dev|test|prod"),那么只有在指定的环境中才会激活组件或装配方法。

@Component
@Profile("dev")
@ConfigurationProperties("ZSDevDatasource")
@Data
public class ZSDevDatasourceConfig{}
# application-dev.yaml
ZSDevDatasource: 
  name: dev-zs
server:
  port: 7000
  • Profile 分组 => 自定义的组下可以加载多个配置的环境
spring.profiles.active=myprod # 两个环境 proddb 与 prodbean 都会加载进去
spring.profiles.group.myprod[0]=proddb
spring.profiles.group.myprod[1]=prodbean
spring.profiles.group.mytest[0]=testdb
spring.profiles.group.mytest[1]=testbean

外部化配置

外部化配置是将信息抽取成文件放在外部进行集中管理。需要重点关注的是外部配置源、查找位置以及加载顺序。

  • 外部配置源 => Java 属性文件、YAML 文件、环境变量、命令行参数...
  • 查找位置 => classpath 根路径、classpath 根路径下 config 目录、jar 包当前目录、jar包当前目录的 config 目录以及 /config 子目录的直接子目录
  • 加载顺序 => 指定环境优先,外部优先
    • 当前 jar 包内部的 application.properties 和 application.yml
    • 当前 jar 包内部的 application-{profile}.properties 和 application-{profile}.yml
    • 引用的外部 jar 包的 application.properties 和 application.yml
    • 引用的外部 jar 包的 application-{profile}.properties 和 application-{profile}.yml

自定义 starter

Spring-Boot 为简化开发已经抽取了许多常用场景的启动器,但是依然不能满足所有的开发需求。

xxx-starter 是普通的 Maven 工程,用于指向依赖;具体要加载的依赖 xxx-starter-autoconfigure,是需要用到 Spring Initializr 创建的 Maven 工程。

  • 若要项目在启动时加载指定的自动配置类,应在后者包中配置 META-INF/spring.factories 里 EnableAutoConfiguration 的值

  • 详细步骤移步 springboot-customer-starter。

Bugs 解决

  • 导入 spring-boot-maven-plugin 持续飘红
Plugin 'org.springframework.boot:spring-boot-maven-plugin:' not found

解决方式参考 SOF,在 pom.xml 中添加插件版本。

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <version>${project.parent.version}</version>
</plugin>
  • 通过注解 @Autowired 注入 Jdbctemplate 飘红

将 @Autowired 转换为 @Resource。

  • 配置 druid monitor 时数据源页面出现 (*) property for user to setup

因 Spring Boot 在启动时并不会初始化数据源,只有在真正使用数据源的时候才会初始化,可通过日志来观察。

解决方法是在执行过 sql 语句后,再查看数据源页面,即可恢复正常。

  • 启动 SpringBoot 后出现 Whitelabel Error Page
Whitelabel Error Page => This application has no explicit mapping for /error, so you are seeing this as a fallback.

主因是 IntelliJ Idea 目录结构的 Application 启动类的位置不对,应将 Application 类放在最外侧,包含所有子包。

本人将 controller 目录放置在与 Application 目录同级处,所以导致了该问题。

此外可以通过在启动类中,添加 scanBasePackages 属性指定 controller 的位置。

@SpringBootApplication(scanBasePackages="controller") // scanBasePackages 注解属性默认会扫描该类所在的包下所有的配置类
public class BootHelloTestApplication {
    public static void main(String[] args) {
        SpringApplication.run(BootHelloTestApplication.class, args);
    }
}
  • @Autowired is not applicable to local variable

自动注入 @Autowired 不能应用于局部变量的意思就是别在方法体里面用。

  • spring.factories 没有叶子

看看是不是把 META-INF 写成了 META_INF...

  • Could not find or load main class com.xxx...

版本更新所导致项目重构时找不到模块,可以先删除整个 .idea 目录,然后重启再次导入项目。

结束

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议,转载请注明出处!