目录
一、@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、项目部署问题汇总
Condition 是在 Spring 4.0 增加的条件判断功能,通过这个可以功能可以实现选择性的创建 Bean 对象的操作。
通过 @Conditional 就可以实现这个操作。
在解决上述问题之前,我们先来了解一下如何获取 Bean 对象。
(1)获取 IOC 容器
ConfigurableApplicationContext context = SpringApplication.run(DemoConditionApplication.class, args);
(2)获取 Bean 对象
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);
}
}
(3)补充 Redis 相关依赖
org.springframework.boot
spring-boot-starter-data-redis
实际上,创建 Bean 对象的条件可以有很多。
在这里选择上文中的导入依赖后就能创建 Bean 的条件进行举例说明 @Conditional 的具体使用。
(1)提出问题
在刚才获取 Bean 对象的过程中,一开始由于没有导入 Redis 依赖,导致获取不到 Bean 对象。而 redisTemplate 的 Bean 对象创建,依赖于 Redis 的起步依赖。
下面通过一个案例来说明:
- 在 Spring 的 IOC 容器中有一个 User 的 Bean。
- 要求:导入 RedisTemplate 后,加载这个 Bean;没有导入,则不加载。
(2)创建 UserCondition 类(重点部分)
现在我们的需求就是,导入 RedisTemplate 类的时候,才返回 true:
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 类
@Configuration
public class SpringConfig {
@Bean
@Conditional(value = {UserCondition.class})
public User createUser() {
return new User();
}
}
(4)启动测试
SpringBoot 的 web 环境中默认使用 tomcat 作为内置服务器,其实 SpringBoot 提供了 4 种内置服务器供我们选择,我们可以很方便的进行切换。
(1)切换原理
(2)切换方法
SpringBoot 中提供了很多 Enable 开头的注解(比如 @EnableAutoConfiguration),这些注解都是用于动态启用某些功能的。
而其底层原理是使用 @Import 注解导入一些配置类,实现 Bean 的动态加载。
我们可以创建两个模块:一个用来启动 SpringBoot,一个用来定义 Bean。
注意,由于我们会给启动 SpringBoot 的模块,添加定义 Bean 的模块作为依赖,所以包路径不能相同,否则在同一项目下,是可以获取到 User 类的。
(1)创建实体类、配置类
(2)引入 Demo-Enable-Bean 作为依赖
(3)启动 Demo-Enable 的 SpringBoot
@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);
}
}
原因其实很简单,前面也遇到过。
在启动类的注解 @SpringBootApplication 中,有 @ComponentScan 这个注解。默认情况下只会扫描当前引导类所在包及其子包。
因此只需要加上一行注解即可:
但是写字符串数组毕竟比较麻烦,所以还可以使用 @Import 导入我们需要的类:
虽然 @Import 可以直接写上我们需要的 UserConfig 类,问题就在于:
这个时候就可以在定义 Bean 的模块中,给 User 写一个 @EnableUser 注解,在其中将所有与 User 相关的类全部 Import。
这样我们就可以在别的项目里,只需要写一个 @EnableUser,就能达到目的。
(1)@EnableUser
// 元注解
@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);
}
}
@EnableXXX 底层依赖于 @lmport 注解导入一些类,使用 @lmport 导入的类会被 Spring 加载到 lOC 容器中。
@lmport 提供 4 种导入方法:
SpringBoot 能够快速启动,得益于其自动配置的机制。关键就在于 @EnableAutoConfiguration 这个注解。
需求:自定义 redis-starter,要求当导入 Jedis 依赖时,SpringBoot 自动创建 Jedis 的 Bean。
参考 MyBatis 的起步依赖构造,我们发现:
可以发现,后两步其实就是 SpringBoot 的自动配置原理。
因此,大致步骤为:
(1)创建两个模块
(2)定义 RedisProperties
由于 Jedis 的初始化需要 host 和 port,并且我们不希望在代码中写死。
@ConfigurationProperties(prefix = "redis")
public class RedisProperties {
private String host = "localhost";
private Integer port = 6379;
}
(3)定义 RedisAutoConfiguration
由于 @Bean 修饰的 Jedis 对象,需要 RedisProperties 类来作为参数,因此要添加 @EnableConfigurationProperties 来引入这个 Bean:
@Configuration
@EnableConfigurationProperties(value = {RedisProperties.class})
public class RedisAutoConfiguration {
@Bean
public Jedis jedis(RedisProperties redisProperties) {
return new Jedis(redisProperties.getHost(), redisProperties.getPort());
}
}
(4)编写配置文件 spring.factories
(5)测试
当用户自定义了 Bean 对象时,可以替代我们自定义的 starter 中的 Bean 对象。
SpringBoot 的监听机制,其实是对 Java 提供的事件监听机制的封装。
Java 中的事件监听机制定义了以下几个角色:
SpringBoot 在项目启动时,会对几个监听器进行回调,我们可以实现这些监听器接口,在项目启动时完成一些操作。
接口则有如下 4 个:
(1)实现 4 个接口,并启动 SpringBoot
先来看这两个不需要手动配置的监听器。
这 2 个监听器的 run 方法在 SpringBoot 启动后会自动调用,我们期望可以用 run() 方法来做一些事情。
(1)程序代码
(2)run() 方法的参数 args
实际使用中,我们希望提前将 redis 中的一些数据加载进缓存中。
- 打印 args,CommandLineRunner 中 输出 Arrays.asList(args);
- 打印 args,ApplicationRunner 中 输出 Arrays.asList(args.getSourceArgs());
此时启动 SpringBoot,会发现这两个输出都是空的。
我们可以在启动项中添加一些参数:
ApplicationContextInitializer 监听器需要使用 META-INF/spring.factories 配置文件来进行引入。
(1)spring.factories
(2)程序代码
(3)启动 SpringBoot
按照配置 ApplicationContextInitializer 的方法同样在 spring.factories 中进行配置:
(1)出现报错
(2)SpringApplication
(3)编写构造函数
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 自带监控功能 Actuator,可以帮助实现对程序内部运行情况监控,比如监控状况、 Bean 加载情况、配置属性、日志信息等。
可以通过监控信息来排除故障。
org.springframework.boot
spring-boot-starter-actuator
我们看到当访问的页面只有 health 相关的功能,那是因为新版本中,info 端点默认是不启用的,在 application.properties 中配置的 info 开头的变量默认也是不启用的。
(1)解决方法
修改配置,添加下面两个参数:
(2)为 info 添加参数信息
(1)显示 health 完整信息
(2)添加 redis 组件
(3)开启所有的 endpoint
当 SpringBoot 项目开发完成,应该怎么将它放到服务器、生产环境上运行。
SpringBoot 项目开发完毕后,支持 2 种方式部署到服务器:
(1)创建工程
(2)打包
(3)启动 jar 包
(1)继承父类、实现父类方法
@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 文件的打包方式
(3)将 war 包交给 tomcat
(4)启动 tomcat
(5)访问 UserController
(1)无效源发行版本、无效目标发行版本
主要原因是:编译环境使用的 JDK 和运行环境使用的 JDK 版本不一样。
(2)使用外置 tomcat 时,application.properties 等配置文件不生效