• [Spring] SpringBoot2 简介(二)—— 高级配置


    目录

    一、@Conditional 注解

    1、SpringBoot 如何获取 Bean 对象

    2、SpringBoot 创建 Condition 类

    3、切换内置 web 服务器

    二、@EnableXXX 注解

    1、SpringBoot 不能直接获取其他 jar 包/工程中的 Bean

    2、原因分析

    3、封装 @Import

    4、@Import 注解

    5、SpringBoot 自动配置原理

    三、自定义 starter 起步依赖

    1、步骤分析

    2、实现步骤

    3、@ConditionalOnMissingBean

    四、SpringBoot 监听机制

    1、Java 监听机制

    2、SpringBoot 监听机制

    3、CommandLineRunner 与 ApplicationRunner

    4、ApplicationContextInitializer

    5、SpringApplicationRunListener

    五、SpringBoot 监控

    1、监控概述

    2、使用方法

    3、info 或其他信息不显示

    4、显示组件完整信息

    六、SpringBoot 项目部署

    1、部署 jar 包方式

    2、部署 war 包方式

    3、项目部署问题汇总


    一、@Conditional 注解

    Condition 是在 Spring 4.0 增加的条件判断功能,通过这个可以功能可以实现选择性的创建 Bean 对象的操作。

    • 我们可以想这么一个问题:有一个 User 对象,只有符合某种条件的时候,才能被 Spring 当作 Bean 对象,加入到 IOC 容器中。
    • 也就是,SpringBoot 是如何知道应该创建哪些 Bean。

    通过 @Conditional 就可以实现这个操作。

    1、SpringBoot 如何获取 Bean 对象

    在解决上述问题之前,我们先来了解一下如何获取 Bean 对象。

    (1)获取 IOC 容器

    • 想要获取 Bean 对象,首先要获取 IOC 容器对象,也就是 ApplicationContext;
    • 我们启动 SpringBoot 的 run 方法,其实就是返回了一个 IOC 容器;
    ConfigurableApplicationContext context = SpringApplication.run(DemoConditionApplication.class, args);

    (2)获取 Bean 对象

    • 这里我们获取一个 redisTemplate 的对象;
    • 注意此时还没有导入 redis 的相关依赖;
    1. package com.demo;
    2. import org.springframework.boot.SpringApplication;
    3. import org.springframework.boot.autoconfigure.SpringBootApplication;
    4. import org.springframework.context.ConfigurableApplicationContext;
    5. @SpringBootApplication
    6. public class DemoConditionApplication {
    7. public static void main(String[] args) {
    8. // 启动 SpringBoot,获取 IOC 容器
    9. ConfigurableApplicationContext context = SpringApplication.run(DemoConditionApplication.class, args);
    10. // 获取 Bean 对象
    11. Object redisTemplate = context.getBean("redisTemplate");
    12. System.out.println(redisTemplate);
    13. }
    14. }
    • 启动后就会发现,SpringBoot 找不到 redisTemplate 这个 Bean; 

    (3)补充 Redis 相关依赖

    1. <dependency>
    2. <groupId>org.springframework.bootgroupId>
    3. <artifactId>spring-boot-starter-data-redisartifactId>
    4. dependency>
    • 添加 Redis 的起步依赖后,就可以获取到 redisTemplate 的 Bean 对象了:

    2、SpringBoot 创建 Condition 类

    实际上,创建 Bean 对象的条件可以有很多。

    在这里选择上文中的导入依赖后就能创建 Bean 的条件进行举例说明 @Conditional 的具体使用。

    (1)提出问题

    在刚才获取 Bean 对象的过程中,一开始由于没有导入 Redis 依赖,导致获取不到 Bean 对象。而 redisTemplate 的 Bean 对象创建,依赖于 Redis 的起步依赖

    • 换句话说,SpringBoot 如何知道当没有引入 Redis 的起步依赖时,不该创建 redisTemplate 的 Bean 对象

    下面通过一个案例来说明:

    • 在 Spring 的 IOC 容器中有一个 User 的 Bean。
    • 要求:导入 RedisTemplate 后,加载这个 Bean;没有导入,则不加载。

    (2)创建 UserCondition 类(重点部分)

    • @Conditional() 修饰某个 Bean 对象时,需要传入 Condition 类数组;
    • Condition 类要求实现 matches 方法;
    • matches 方法就是创建 Bean 对象的判断条件,返回值为 true/false

    现在我们的需求就是,导入 RedisTemplate 类的时候,才返回 true:

    • 当加载不到这个类的时候,出现异常,就返回 false;
    1. public class UserCondition implements Condition {
    2. @Override
    3. public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    4. boolean flag = true;
    5. try {
    6. Class.forName("org.springframework.data.redis.core.RedisTemplate");
    7. } catch (ClassNotFoundException e) {
    8. flag = false;
    9. }
    10. return flag;
    11. }
    12. }

    (3)创建 SpringConfig 类

    • User 类随便新建一个即可;
    • SpringConfig 中,实现注入 User 的方法(@Bean 修饰方法);
    • @Conditional 传入我们刚写好的 UserCondition,其中的 matches 方法会判断是否创建这个 Bean;
    1. @Configuration
    2. public class SpringConfig {
    3. @Bean
    4. @Conditional(value = {UserCondition.class})
    5. public User createUser() {
    6. return new User();
    7. }
    8. }

    (4)启动测试

    • 由于我们刚才导入了 Redis 的起步依赖,已经包含了 RedisTemplate,此时获取 User 的 Bean 对象,是可以直接获取到的。

    • 然后我们将 Redis 的起步依赖注释掉,重新 Build 项目,启动 SpringBoot,就会发现 Bean 对象找不到了。

    3、切换内置 web 服务器

    SpringBoot 的 web 环境中默认使用 tomcat 作为内置服务器,其实 SpringBoot 提供了 4 种内置服务器供我们选择,我们可以很方便的进行切换。

    (1)切换原理

    • SpringBoot 如何切换服务器。本质上也是通过 @Conditional 注解,来判断是否引入了依赖、传递了参数,最终决定启用哪一个服务器。

    (2)切换方法

    • 首先要用 将 tomcat 从依赖中移除;
    • 然后引入 jetty 的依赖;

    二、@EnableXXX 注解

    SpringBoot 中提供了很多 Enable 开头的注解(比如 @EnableAutoConfiguration),这些注解都是用于动态启用某些功能的。

    而其底层原理是使用 @Import 注解导入一些配置类,实现 Bean 的动态加载。

    • 比如:引入了 redisTemplate 之后,就可以直接使用 @Autowired 注入属性了。

    1、SpringBoot 不能直接获取其他 jar 包/工程中的 Bean

    我们可以创建两个模块:一个用来启动 SpringBoot,一个用来定义 Bean。

    注意,由于我们会给启动 SpringBoot 的模块,添加定义 Bean 的模块作为依赖,所以包路径不能相同,否则在同一项目下,是可以获取到 User 类的。

    (1)创建实体类、配置类

    • 在 Demo-Enable-Bean 模块中,创建一个 User 类、一个 UserConfig 类;
    • 在 UserConfig 类中,注入一个 User 的 Bean;

    (2)引入 Demo-Enable-Bean 作为依赖

    • 在 Demo-Enable 模块中,引入 Demo-Enable-Bean;

    (3)启动 Demo-Enable 的 SpringBoot

    • 在启动类中,获取 User 的 Bean 对象,观察是否能获取到;
    1. @SpringBootApplication
    2. public class DemoEnableApplication {
    3. public static void main(String[] args) {
    4. ConfigurableApplicationContext context = SpringApplication.run(DemoEnableApplication.class, args);
    5. User user = (User) context.getBean("createUser");
    6. System.out.println(user);
    7. }
    8. }
    • 输出结果:

    2、原因分析

    原因其实很简单,前面也遇到过。

    在启动类的注解 @SpringBootApplication 中,有 @ComponentScan 这个注解。默认情况下只会扫描当前引导类所在包及其子包。

    因此只需要加上一行注解即可:

    但是写字符串数组毕竟比较麻烦,所以还可以使用 @Import 导入我们需要的类:

    3、封装 @Import

    虽然 @Import 可以直接写上我们需要的 UserConfig 类,问题就在于:

    • 当我们需要这个定义 Bean 的模块(以后就是 jar 包)中的所有关于 User 的类呢?一个个导入会很麻烦。
    • 并且如果在启动 SpringBoot 的模块中,Import 大量其他 jar 包的类,也会提高耦合度。

    这个时候就可以在定义 Bean 的模块中,给 User 写一个 @EnableUser 注解,在其中将所有与 User 相关的类全部 Import。

    这样我们就可以在别的项目里,只需要写一个 @EnableUser,就能达到目的。

    (1)@EnableUser

    • 写上元注解;
    • 添加关于 User 的类;
    1. // 元注解
    2. @Target(ElementType.TYPE)
    3. @Retention(RetentionPolicy.RUNTIME)
    4. @Documented
    5. // 导入关于 User 的所有类
    6. @Import(value = {UserConfig.class})
    7. public @interface EnableUser {
    8. }

    (2)在 SpringBoot 的启动类中添加 @EnableUser

    1. @SpringBootApplication
    2. @EnableUser
    3. public class DemoEnableApplication {
    4. public static void main(String[] args) {
    5. ConfigurableApplicationContext context = SpringApplication.run(DemoEnableApplication.class, args);
    6. User user = (User) context.getBean("createUser");
    7. System.out.println(user);
    8. }
    9. }

    4、@Import 注解

    @EnableXXX 底层依赖于 @lmport 注解导入一些类,使用 @lmport 导入的类会被 Spring 加载到 lOC 容器中。

    @lmport 提供 4 种导入方法:

    • 导入Bean;
    • 导入配置类;
    • 导入 ImportSelector 的实现类;(一般用于加载配置文件中的类)
    • 导入 ImportBeanDefinitionRegistrar 的实现类。

    5、SpringBoot 自动配置原理

    SpringBoot 能够快速启动,得益于其自动配置的机制。关键就在于 @EnableAutoConfiguration 这个注解。

    • @EnableAutoConfiguration 注解内部使用 @Import(AutoconfigurationImportselector.class) 来加载配置类。
    • 而 Importselector 的实现类,又会从配置文件:META-INF/spring.factorles 中加载大量配置类。
    • 当 SpringBoot 启动时,就会自动加载这些配置类,初始化 Bean。
    • 并不是所有 Bean 都会被初始化,在配置类中使用 @Conditinal 来加载满足条件的配置类。

    三、自定义 starter 起步依赖

    需求:自定义 redis-starter,要求当导入 Jedis 依赖时,SpringBoot 自动创建 Jedis 的 Bean。

    1、步骤分析

    参考 MyBatis 的起步依赖构造,我们发现:

    • starter 模块不写代码,专门用来整合依赖,其中依赖了 autoconfigure;
    • autoconfigure 模块就包含了 MyBatis 的配置类 MyBatisAutoConfiguration,其中就定义了 Bean 对象;
    • 而要想 Spring 能够识别 MyBatis 的配置类,就会有一个 spring.factorles 配置文件,配置文件中就有 MyBatis 的配置类;

    可以发现,后两步其实就是 SpringBoot 的自动配置原理。

    因此,大致步骤为:

    • 创建 redis-spring-boot-autoconfigure 模块;
    • 创建 redis-spring-boot-starter 模块,依赖 redis-spring-boot-autoconfigure 模块;
    • 在 redis-spring-boot-autoconfigure 模块中初始化 Jedis 的Bean,并定义 META-INF/spring.factories 文件;
    • 在测试模块中引入自定义的 redis-starter 依赖,测试获取 Jedis 的 Bean,操作 redis。

    2、实现步骤

    (1)创建两个模块

    • 创建 starter 和 autoconfigure 模块;

    • starter 依赖 autoconfigure,autoconfigure 依赖 jedis;

    (2)定义 RedisProperties

    由于 Jedis 的初始化需要 host 和 port,并且我们不希望在代码中写死。

    • 因此可以使用配置文件来设置 host 和 port;
    • 相应的要增设一个 RedisProperties 类来绑定对应的配置文件(自行补上 set、get);
    • @ConfigurationProperties 绑定前缀为 redis 的配置文件;
    1. @ConfigurationProperties(prefix = "redis")
    2. public class RedisProperties {
    3. private String host = "localhost";
    4. private Integer port = 6379;
    5. }

    (3)定义 RedisAutoConfiguration

    由于 @Bean 修饰的 Jedis 对象,需要 RedisProperties 类来作为参数,因此要添加 @EnableConfigurationProperties 来引入这个 Bean:

    • 需要注意的是,Spring boot 2.2.1 默认关闭对 @ConfigurationProperties 的扫描;
    • 有三种方式可以解决,但在这种自定义 starter 的需求下,只能使用 @EnableConfigurationProperties 来完成引入
    1. @Configuration
    2. @EnableConfigurationProperties(value = {RedisProperties.class})
    3. public class RedisAutoConfiguration {
    4. @Bean
    5. public Jedis jedis(RedisProperties redisProperties) {
    6. return new Jedis(redisProperties.getHost(), redisProperties.getPort());
    7. }
    8. }

    (4)编写配置文件 spring.factories

    • 在 resource 下创建目录:META-INF/spring.factories;
    • 添加 RedisAutoConfiguration 的全类名;
    • 注意,一定要 RedisAutoConfiguration 的全类名是 绿色 才可以;

    (5)测试

    • 在 SpringBoot 启动类的项目中添加我们所编写的 RedisStarter 依赖;
    • 获取其中 Jedis 的 Bean 对象;

    3、@ConditionalOnMissingBean

    当用户自定义了 Bean 对象时,可以替代我们自定义的 starter 中的 Bean 对象。

    • name = "xxx",表示用户定义了 id 为 xxx 的 Bean 时,就不会生成 @ConditionalOnMissingBean 修饰的 Bean。

    四、SpringBoot 监听机制

    1、Java 监听机制

    SpringBoot 的监听机制,其实是对 Java 提供的事件监听机制的封装。

    Java 中的事件监听机制定义了以下几个角色:

    • 事件:Event,继承 java.util.EventObject 类的对象;
    • 事件源:Source,任意对象 Object;
    • 监听器:Listener,实现 java.util.EventListener 接口的对象;

    2、SpringBoot 监听机制

    SpringBoot 在项目启动时,会对几个监听器进行回调,我们可以实现这些监听器接口,在项目启动时完成一些操作。

    • SpringBoot 内部已经将注册监听器等任务都做好了;
    • SpringBoot 将接口提供,我们只需要实现接口,就能完成监听到事件源之后的操作;

    接口则有如下 4 个:

    • ApplicationContextInitializer(需要手动配置);
    • SpringApplicationRunListener(需要手动配置);
    • CommandLineRunner;
    • ApplicationRunner;

    (1)实现 4 个接口,并启动 SpringBoot

    • 将上面 4 个接口都实现后,为每个实现方法添加输出;
    • 启动 SpringBoot,观察输出;

    • 由输出可知,只有后 2 个监听器被调用了,因为我们还没有配置前 2 个监听器;

    3、CommandLineRunner 与 ApplicationRunner

    先来看这两个不需要手动配置的监听器。

    这 2 个监听器的 run 方法在 SpringBoot 启动后会自动调用,我们期望可以用 run() 方法来做一些事情。

    (1)程序代码

    • 分别作对应的输出;

    (2)run() 方法的参数 args

    实际使用中,我们希望提前将 redis 中的一些数据加载进缓存中。

    • 打印 args,CommandLineRunner 中 输出 Arrays.asList(args);
    • 打印 args,ApplicationRunner 中 输出 Arrays.asList(args.getSourceArgs());

    此时启动 SpringBoot,会发现这两个输出都是空的。

    我们可以在启动项中添加一些参数:

    • 若没有“程序实参”这个选项,可以在旁边的“修改选项中添加”;
    • 在“程序实参”中写上一个键值对,然后启动 SpringBoot,就能输出到控制台了;

    4、ApplicationContextInitializer

    ApplicationContextInitializer 监听器需要使用 META-INF/spring.factories 配置文件来进行引入。

    (1)spring.factories

    • 添加键值对:ApplicationContextInitializer 类路径 = 实现类的类路径;

    (2)程序代码

    • 在 initialize 方法中,进行一个输出;
    • 注意:不需要 @Component; 

    (3)启动 SpringBoot

    • 启动 SpringBoot,发现项目启动之前就已经有输出了;
    • 实际上,Initializer 可以用于在项目还未启动之前,去检测一些资源是否存在;

    5、SpringApplicationRunListener

    按照配置 ApplicationContextInitializer 的方法同样在 spring.factories 中进行配置:

    (1)出现报错

    • 报错一般是指:缺少了构造方法。
    • 这里的问题就是:没有一个有参构造,参数为(SpringApplication,String[])。

    (2)SpringApplication

    • SpringApplication:就是项目启动后的事件源
    • 事件源上可以产生很多生命周期不同的相关事件,所以监听器需要事件源作为构造所需的参数;

    (3)编写构造函数

    • 参考 SpringBoot 官方的实现类,定义构造函数,其中包含上述 2 个参数;
    • 在新版本中,SpringApplicationRunListener 的 running 方法已经被弃用了,使用 ready 取代它;

    • 下面给出完整代码: 
    1. public class MySpringApplicationRunListener implements SpringApplicationRunListener {
    2. public MySpringApplicationRunListener(SpringApplication springApplication, String[] args) {
    3. // structor
    4. }
    5. @Override
    6. public void starting(ConfigurableBootstrapContext bootstrapContext) {
    7. System.out.println("starting--项目启动中");
    8. }
    9. @Override
    10. public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
    11. System.out.println("environmentPrepared--环境对象准备");
    12. }
    13. @Override
    14. public void contextPrepared(ConfigurableApplicationContext context) {
    15. System.out.println("contextPrepared--上下文对象准备");
    16. }
    17. @Override
    18. public void contextLoaded(ConfigurableApplicationContext context) {
    19. System.out.println("contextLoaded--上下文对象加载(耗时操作)");
    20. }
    21. @Override
    22. public void started(ConfigurableApplicationContext context, Duration timeTaken) {
    23. System.out.println("started--上下文对象加载完成");
    24. }
    25. @Override
    26. public void ready(ConfigurableApplicationContext context, Duration timeTaken) { // running
    27. System.out.println("ready--项目启动完成,开始运行");
    28. }
    29. @Override
    30. public void failed(ConfigurableApplicationContext context, Throwable exception) {
    31. System.out.println("failed--项目启动失败");
    32. }
    33. }

    (4)启动 SpringBoot

    五、SpringBoot 监控

    1、监控概述

    SpringBoot 自带监控功能 Actuator,可以帮助实现对程序内部运行情况监控,比如监控状况、 Bean 加载情况、配置属性、日志信息等。

     可以通过监控信息来排除故障。

    2、使用方法

    • 导入相关依赖:spring-boot-starter-actruator;
    1. <dependency>
    2. <groupId>org.springframework.bootgroupId>
    3. <artifactId>spring-boot-starter-actuatorartifactId>
    4. dependency>
    • 访问:http://localhost:8080/acruator

    3、info 或其他信息不显示

    我们看到当访问的页面只有 health 相关的功能,那是因为新版本中,info 端点默认是不启用的,在 application.properties 中配置的 info 开头的变量默认也是不启用的。

    (1)解决方法

    修改配置,添加下面两个参数:

    • management.endpoints.web.exposure.include = health, info;
    • management.info.env.enabled = true;

    • 再次访问,这时可以看到,info 端点已经出现了;

    (2)为 info 添加参数信息

    • 同样在 application.properties 中配置 info 的参数值;

    • 然后访问 info 端点链接; 

    4、显示组件完整信息

    (1)显示 health 完整信息

    • 当访问 health 端点的时候,会发现只有一个 status 为 up 的信息。

    • 如果希望显示完整信息,可以修改 show-details 属性。

    • 再次访问 health,就能显示详细信息。 

    • 由于还没有开启其他组件,因此现在只有对磁盘和ping的监控。

    (2)添加 redis 组件

    • 添加 redis 起步依赖;
    • 启动 redis 服务器:redis-server.exe;

    (3)开启所有的 endpoint

    • 其实就是将上文中,只开启 info、health 的参数值,修改为 * 即可;
    • 将所有的监控 endpoint 暴露出来;

    • 访问 actuator 就会发现多出了一大堆信息;

    六、SpringBoot 项目部署

    当 SpringBoot 项目开发完成,应该怎么将它放到服务器、生产环境上运行。

    SpringBoot 项目开发完毕后,支持 2 种方式部署到服务器:

    • jar 包(官方推荐,使用内置的 Tomcat)
    • war 包(使用外部的 Tomcat)

    1、部署 jar 包方式

    (1)创建工程

    • 添加 web 起步依赖;
    • 创建一个 Controller 类,包含一个访问路径;

    (2)打包

    • 使用 maven 的 package 功能,将项目打包 jar 包;

    (3)启动 jar 包

    • 输入命令:java -jar xxx.jar;
    • 启动成功即可访问 UserController;

     

    2、部署 war 包方式

    (1)继承父类、实现父类方法

    • 继承 SpringBootServletInitializer;
    • 实现 configure 方法,返回值为 SpringApplicationBuilder;
    1. @SpringBootApplication
    2. public class DemoDeployApplication extends SpringBootServletInitializer {
    3. public static void main(String[] args) {
    4. SpringApplication.run(DemoDeployApplication.class, args);
    5. }
    6. @Override
    7. protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
    8. return builder.sources(DemoDeployApplication.class);
    9. }
    10. }

    (2)修改 pom.xml 文件的打包方式

    • 改为 war 包方式;
    • 然后使用 maven 的 package 打成 war 包;

    (3)将 war 包交给 tomcat

    • 将 war 放到 tomcat 目录的 webapps 目录下;

    (4)启动 tomcat

    • 找到 bin 目录下的 startup.bat,启动 tomcat;

    (5)访问 UserController

    • 先访问 locathost:8080,显示 tomcat 的主页面;
    • 注意:由于使用的是外置的 tomcat,因此 URL 需要加上工程路径;

     

    3、项目部署问题汇总

    (1)无效源发行版本、无效目标发行版本

    主要原因是:编译环境使用的 JDK 和运行环境使用的 JDK 版本不一样。

    (2)使用外置 tomcat 时,application.properties 等配置文件不生效

    • 因为 SpringBoot 中的 application 配置配置文件,对应的都是内置的服务器;
    • 当我们使用了外置服务器,就没有效果了;

  • 相关阅读:
    拉线位移传感器的输出信号分为几种,小编让大家明白
    LeetCode讲解篇之77. 组合
    【面试:并发篇38:多线程:线程池】ThreadPoolExecutor类的基本概念
    ThingsBoard教程(二七):设备批量导入,包含设备id,设备token
    上周热点回顾(12.4-12.10)
    Java#19(面向对象三大特征之一:多态)
    idea jsp文件 高亮_有了这几款idea插件后,同事再也不叫我小白了
    家电上云后,智能家居如何构建场景化应用
    vscode使用git
    质数的判定和质因数分解
  • 原文地址:https://blog.csdn.net/joyride_run/article/details/133963390