目录
一、@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 的相关依赖;
package com.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class DemoConditionApplication {
public static void main(String[] args) {
// 启动 SpringBoot,获取 IOC 容器
ConfigurableApplicationContext context = SpringApplication.run(DemoConditionApplication.class, args);
// 获取 Bean 对象
Object redisTemplate = context.getBean("redisTemplate");
System.out.println(redisTemplate);
}
}
- 启动后就会发现,SpringBoot 找不到 redisTemplate 这个 Bean;
(3)补充 Redis 相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</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;
public class UserCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
boolean flag = true;
try {
Class.forName("org.springframework.data.redis.core.RedisTemplate");
} catch (ClassNotFoundException e) {
flag = false;
}
return flag;
}
}
(3)创建 SpringConfig 类
- User 类随便新建一个即可;
- SpringConfig 中,实现注入 User 的方法(@Bean 修饰方法);
- @Conditional 传入我们刚写好的 UserCondition,其中的 matches 方法会判断是否创建这个 Bean;
@Configuration
public class SpringConfig {
@Bean
@Conditional(value = {UserCondition.class})
public User createUser() {
return new User();
}
}
(4)启动测试
- 由于我们刚才导入了 Redis 的起步依赖,已经包含了 RedisTemplate,此时获取 User 的 Bean 对象,是可以直接获取到的。
- 然后我们将 Redis 的起步依赖注释掉,重新 Build 项目,启动 SpringBoot,就会发现 Bean 对象找不到了。
3、切换内置 web 服务器
SpringBoot 的 web 环境中默认使用 tomcat 作为内置服务器,其实 SpringBoot 提供了 4 种内置服务器供我们选择,我们可以很方便的进行切换。
(1)切换原理
- SpringBoot 如何切换服务器。本质上也是通过 @Conditional 注解,来判断是否引入了依赖、传递了参数,最终决定启用哪一个服务器。
(2)切换方法
- 首先要用 <exclusion> 将 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 对象,观察是否能获取到;
@SpringBootApplication
public class DemoEnableApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(DemoEnableApplication.class, args);
User user = (User) context.getBean("createUser");
System.out.println(user);
}
}
- 输出结果:
2、原因分析
原因其实很简单,前面也遇到过。
在启动类的注解 @SpringBootApplication 中,有 @ComponentScan 这个注解。默认情况下只会扫描当前引导类所在包及其子包。
因此只需要加上一行注解即可:
但是写字符串数组毕竟比较麻烦,所以还可以使用 @Import 导入我们需要的类:
3、封装 @Import
虽然 @Import 可以直接写上我们需要的 UserConfig 类,问题就在于:
- 当我们需要这个定义 Bean 的模块(以后就是 jar 包)中的所有关于 User 的类呢?一个个导入会很麻烦。
- 并且如果在启动 SpringBoot 的模块中,Import 大量其他 jar 包的类,也会提高耦合度。
这个时候就可以在定义 Bean 的模块中,给 User 写一个 @EnableUser 注解,在其中将所有与 User 相关的类全部 Import。
这样我们就可以在别的项目里,只需要写一个 @EnableUser,就能达到目的。
(1)@EnableUser
- 写上元注解;
- 添加关于 User 的类;
// 元注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 导入关于 User 的所有类
@Import(value = {UserConfig.class})
public @interface EnableUser {
}
(2)在 SpringBoot 的启动类中添加 @EnableUser
@SpringBootApplication
@EnableUser
public class DemoEnableApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(DemoEnableApplication.class, args);
User user = (User) context.getBean("createUser");
System.out.println(user);
}
}
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 的配置文件;
@ConfigurationProperties(prefix = "redis")
public class RedisProperties {
private String host = "localhost";
private Integer port = 6379;
}
(3)定义 RedisAutoConfiguration
由于 @Bean 修饰的 Jedis 对象,需要 RedisProperties 类来作为参数,因此要添加 @EnableConfigurationProperties 来引入这个 Bean:
- 需要注意的是,Spring boot 2.2.1 默认关闭对 @ConfigurationProperties 的扫描;
- 有三种方式可以解决,但在这种自定义 starter 的需求下,只能使用 @EnableConfigurationProperties 来完成引入
@Configuration
@EnableConfigurationProperties(value = {RedisProperties.class})
public class RedisAutoConfiguration {
@Bean
public Jedis jedis(RedisProperties redisProperties) {
return new Jedis(redisProperties.getHost(), redisProperties.getPort());
}
}
(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)出现报错
- <init> 报错一般是指:缺少了构造方法。
- 这里的问题就是:没有一个有参构造,参数为(SpringApplication,String[])。
(2)SpringApplication
- SpringApplication:就是项目启动后的事件源;
- 事件源上可以产生很多生命周期不同的相关事件,所以监听器需要事件源作为构造所需的参数;
(3)编写构造函数
- 参考 SpringBoot 官方的实现类,定义构造函数,其中包含上述 2 个参数;
- 在新版本中,SpringApplicationRunListener 的 running 方法已经被弃用了,使用 ready 取代它;
- 下面给出完整代码:
public class MySpringApplicationRunListener implements SpringApplicationRunListener {
public MySpringApplicationRunListener(SpringApplication springApplication, String[] args) {
// structor
}
@Override
public void starting(ConfigurableBootstrapContext bootstrapContext) {
System.out.println("starting--项目启动中");
}
@Override
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
System.out.println("environmentPrepared--环境对象准备");
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
System.out.println("contextPrepared--上下文对象准备");
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
System.out.println("contextLoaded--上下文对象加载(耗时操作)");
}
@Override
public void started(ConfigurableApplicationContext context, Duration timeTaken) {
System.out.println("started--上下文对象加载完成");
}
@Override
public void ready(ConfigurableApplicationContext context, Duration timeTaken) { // running
System.out.println("ready--项目启动完成,开始运行");
}
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
System.out.println("failed--项目启动失败");
}
}
(4)启动 SpringBoot
五、SpringBoot 监控
1、监控概述
SpringBoot 自带监控功能 Actuator,可以帮助实现对程序内部运行情况监控,比如监控状况、 Bean 加载情况、配置属性、日志信息等。
可以通过监控信息来排除故障。
2、使用方法
- 导入相关依赖:spring-boot-starter-actruator;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</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;
@SpringBootApplication
public class DemoDeployApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(DemoDeployApplication.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(DemoDeployApplication.class);
}
}
(2)修改 pom.xml 文件的打包方式
- 将 <packaging> 改为 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 版本不一样。
- 首先要明确自己的 java 版本:windows 使用 java -version 查看版本;
- 然后参考这篇文章修改:https://blog.csdn.net/weixin_44368270/article/details/131155839
(2)使用外置 tomcat 时,application.properties 等配置文件不生效
- 因为 SpringBoot 中的 application 配置配置文件,对应的都是内置的服务器;
- 当我们使用了外置服务器,就没有效果了;