第八章 自动配置原理及自定义starter
分类: springboot 专栏: springboot3.0新教材 标签: 自定义starter
2024-01-17 00:20:56 1710浏览
前期回顾
- redis有几种数据类型
- redis常用的命令有哪些
- redis的应用场景有哪些
- linux中redis启动命令
- springboot访问redis的操作步骤
- 定时任务是啥,应用场景
- 文章浏览量,点赞量,评论量这种功能怎么实现的?
- redis集群有几种?怎么搭建的,简单说说流程
-
什么时候需要用到分布式锁?
自动配置原理
在第一个 Spring Boot 项目中,用户只须导入 spring-boot-starter-web 依赖,无须配置Web 的功能就可进行 Web 开发,这是因为 Spring Boot 提供了自动配置的功能。
自动配置的分为两种情况: -
SpringBoot 官方已经收录的第三方应用,下面称为内置自动配置类,如 Redis。
-
Spring Boot官方未收录的第三方应用,下面称为外部自动配置类,如 MyBatis。
@SpringBootApplication
作用:启动类上加的注解,这个类的同级别或者子包下面的组件才会被扫描到
是一个复合注解,等价于
@SpringBootConfiguration :表示这是一个 Spring Boot 的配置类,
@ComponentScan:表示扫描当前包下的所有组件,
@EnableAutoConfiguration:表示开启自动配置功能,显然这个注解就是要研究的重点
元注解
@Target:这是一个描述注解
@Retention:说明注解的生命周期以及作用域。有些注解是可以改变你代码运行结果的,有些注解不能(比如重写。@Override)
RetentionPolicy.SOURCE:只是在代码中有,编译后的class文件里没有,所以不会造成任何影响
RetentionPolicy.RUNTIME:对代码有影响
@Documented:注解标准文档
@EnableAutoConfiguration
最核心的就是@EnableAutoConfiguration
其中注解
- @AutoConfigurationPackage 用于自动配置包扫描。它会根据启动类的位置,自动扫描并加载该类所在包及其子包中的所有组件。
- @Import({AutoConfigurationImportSelector.class})作用是给容器导入自动配置类,导入哪些,需要进一步研究 AutoConfigurationImportSelector 类
我们关注下AutoConfigurationImportSelector 类的 selectImports方法,该方法用于获得所有自动配置类的全路径名称作为元素的数组,再将该数组中的所有这些自动配置类来加载到 Spring 容器中。
搞清楚 getAutoConfigurationEntry 方法是如何获得包括了所有自动配置类的全路径名称的 List集合的。selectImports 方法内部找到 getAutoConfigurationEntry 方法
它关键是调用了getCandidateConfigurations(annotationMetadata, attributes)方法来获取包括了所有自动配置类的全路径名称的 List集合的,获取到这个集合后,还进行了必要的过滤、排除与筛选,然后封装到 AutoConfigurationEntry 对象中。
这个方法调用了ImportCandidates 类的load方法加载编译在 classes 目录下的 META-INF/spring/下的org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,当类加载器读取此文件时,ImportCandidates 类将获取此文件中列出来的所有自动配置类的路径,再通过反射实例化对象,这样此文件中列出来的所有自动配置类就能加载到容器中了。
主要是找到org.springframework.boot.autoconfigure.AutoConfiguration.imports文件
可以看看官方自动配置类(以redis为例)里的这个文件,再看看非官方的(以mybatis为例)
可见无论是官方的还是非官方的,这个文件名字都是一样的,打开看看
不过加载这些自动配置类的时候,并不会全部创建这些自动配置类的 Bean,因为这些自动配置类中一般都做了条件注解,只有符合条件的 Bean 才会创建,这些常用的条件包括类路径下有哪个类存在时才加载,所以通常需要先导入第三方库的依赖,一旦第三方库的依赖导入,条件中要求的类就存在了,当 Spring Boot 启动时,相关的 Bean 就会创建并加载到容器中,否则就不会创建 Bean。
典型的就是只有导入 spring-boot-starter-data-redis 依赖时,自动配置类 RedisAutoConfiguration 才会创建 RedisTemplate 的 Bean 放入容器中,程序中以下注入RedisTemplate 的代码才会生效:
什么是条件注解
每一个 XxxxAutoConfiguration 自动配置类都是在某些条件之下才会生效的,这些条件的限制在 Spring Boot 中以注解的形式体现
拿Redis举例,先找到redis的自动配置类
该自动配置类里面包含了创建 RedisTemplate 实例的方法,生成一个 Bean 纳入 Spring容器管理。注解@ConditionalOnClass({RedisOperations.class})的作用是就是当 RedisOperations 类存在时才会创建这个自动配置类中的 Bean。所以简单来说,只要导入了清单中的某个依赖(jar 包)Spring Boot 启动时就会帮你自动配置好。
常见的条件注解如下:
@ConditionalOnBean: 系统的ioc容器里如果存在某个bean的情况下才会实例化这个bean
@ConditionalOnClass: 系统中如果存在某个类的情况下,才会实例化一个Bean.
@ConditionalOnExpression: 当表达式为true的时候, 才会实例化一个Bean.
@ConditionalOnMissingBean: 系统的ioc容器里如果不存在某个bean的情况下才会实例化这个bean
@ConditionalOnMissingClass: 系统中如果不存在某个类的情况下,才会实例化一个Bean.
@ConditionalOnNotWebApplication: 不是web应用,才会实例化一个Bean。
@ConditionalOnBean: 当容器中有指定Bean的条件下进行实例化。
@ConditionalohMissingBean: 当容器里没有指定Bean的条件下进行实例化。
@ConditionalOnClass: 当classpath类路径下有指定类的条件下进行实例化。
@ConditionalOnMissingClass: 当类路径下没有指定类的条件下进行实例化。
@ConditionalOnWebApplication: 当项目是一个Web项目时进行实例化。
@ConditionalOnNotWebApplication: 当项目不是一个Web项目时进行实例化。
@ConditionalOnProperty:当指定的属性有指定的值时进行实例化。
@ConditionalOnExpression:基于SpEL表达式的条件判断。
@Condit ionalOnJava:当JVM版本为指定的版本范围时触发实例化。
@ConditionalOnResource:当类路径下有指定的资源时触发实例化。
@ConditionalOnJndi:在JNDI存在的条件下触发实例化。
启动流程分析
当SpringApplication创建完毕后,就开始执行run方法。这个方法会执行以下关键步骤:
- 准备应用程序上下文的环境:首先,run() 方法会根据当前的环境(如开发环境、生产环境)加载适当的配置文件,并准备应用程序上下文所需的环境变量。
- 执行应用程序上下文的刷新:接下来,run() 方法会调用 refresh() 方法来刷新应用程序上下文。在这个阶段,Spring 会完成所有 Bean 的实例化、依赖注入、属性设置等操作,并触发各个 Bean 的初始化回调方法。
- 处理应用程序启动事件:一旦应用程序上下文刷新完成,run() 方法会发布一个 ApplicationStartedEvent 事件,表示应用程序已经启动完毕。这个事件可以被其他组件监听到,进行相应的处理。
- 运行应用程序:最后,run() 方法会调用应用程序上下文中的 start() 方法,启动应用程序。具体的启动逻辑会根据应用程序的类型和配置而有所不同。例如,对于 Web 应用程序,会启动内嵌的 Web 服务器,并监听指定的端口。
总体而言,ConfigurableApplicationContext 的 run() 方法是 Spring 应用程序启动的入口点,它负责完成应用程序上下文的初始化、刷新和启动等关键操作。通过该方法的调用,可以实现 Spring 应用程序的快速启动和运行。
请注意,以上描述是基于 Spring Boot 2.x 版本的启动流程。Spring Boot 3.x 版本,可能会有一些改变。
我们可以在resources目录下新建一个banner.txt来修改默认的banner
自定义banner.txt
// _ooOoo_ //
// o8888888o //
// 88" . "88 //
// (| ^_^ |) //
// O\ = /O //
// ____/`---'\____ //
// .' \\| |// `. //
// / \\||| : |||// \ //
// / _||||| -:- |||||- \ //
// | | \\\ - /// | | //
// | \_| ''\---/'' | | //
// \ .-\__ `-` ___/-. / //
// ___`. .' /--.--\ `. . ___ //
// ."" '< `.___\_<|>_/___.' >'"". //
// | | : `- \`.;`\ _ /`;.`/ - ` : | | //
// \ \ `-. \_ __\ /__ _/ .-` / / //
// ========`-.____`-.___\_____/___.-`____.-'======== //
// `=---=' //
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //
// 佛祖保佑 永无BUG 永不修改 //
其实咱们可以类比替代web.xml文件的那个类
总结:我认为自动配置就是干掉之前ssm框架里的application.xml的配置
run启动说白了就是拉起spring框架 拉起springmvc框架,相当于干掉之前的web.xml(也会读取相关配置,决定是加载dev环境还是加载prod环境),启动的过程中肯定还有很多事情做,比如打印日志啥的,打印banner,打印整个项目启动时长,端口啥的
启动器starter
在 Spring Boot 中,启动器(Starter)是一种用于简化依赖项管理的方式。每个启动器都包含了一组预定义的依赖项,以及适当的版本号和传递依赖项,可以快速地将所需的依赖项添加到项目中。
启动器通常使用 “spring-boot-starter-” 前缀命名,例如 spring-boot-starter-web、spring-boot-starter-data-jpa 等。每个启动器都包含一个或多个 Maven 依赖项,这些依赖项包含了在特定场景下使用 Spring Boot 所需的所有依赖项。
例如,如果使用 spring-boot-starter-web 启动器,它会自动包含以下依赖项:
- spring-boot-starter
- spring-boot-starter-json
- spring-boot-starter-tomcat
- spring-web
- spring-webmvc
这些依赖项涵盖了使用 Spring Boot 创建 Web 应用程序所需的所有基本依赖项。通过使用启动器,我们可以轻松地添加这些依赖项,而不需要手动添加每个依赖项。
除了标准的启动器之外,还可以创建自定义启动器,以便在项目中添加自定义依赖项。自定义启动器可以基于现有的启动器进行扩展,也可以添加新的依赖项和配置文件,并为它们提供默认值。
通过使用启动器,Spring Boot 可以简化依赖项管理,并提供更加一致和可靠的依赖项版本。这样,我们就可以专注于应用程序的开发,而不必过多关注依赖项管理的细节。
自定义starter
在日常开发工作中,可能会需要开发一个通用模块,以供其它工程复用。SpringBoot提供这样的功能机制:把通用模块封装成一个个 starter,这样其它工程复用的时候只需要在pom.xml 中引用依赖即可,由SpringBoot 自动装配。
咱们可以模仿mybatis的自动配置类和属性类写一个启动器(因为我们也属于非官方的第三方启动器)
这就是mybatis的自动配置类以及关联的properties文件。
下面假设为用户注册功能创建一个自动配置类。
自定义用户注册启动器
1.创建autoconfigure模块
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.0</version>
</parent>
<dependencies>
<!--导入自动配置的相关依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<!--方便idea能检测到该依赖中用到的配置属性,能够自动补全,其实就是在编译的时候在
META-INF下生成一个spring-configuration-metadata.json文件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
@Component
@Data
@ConfigurationProperties(prefix = "jf3q.register")
public class RegisterProperties {
private String username="admin";
private String password="123456";
}
@Data
@AllArgsConstructor
public class RegService {
private String username;
private String password;
public void login(){
System.out.println("用户名:"+username);
System.out.println("密码:"+password);
}
}
@Configuration
@EnableConfigurationProperties({RegisterProperties.class})
public class RegisterAutoConfiguration {
@Autowired
RegisterProperties registerProperties;
@Bean
@ConditionalOnMissingBean(RegService.class)//不存在这个bean的时候创建
RegService regService(){
return new RegService(registerProperties.getUsername(),registerProperties.getPassword());
}
}
2.创建stater模块
把上面的模块打包安装到本地仓库,然后在此模块引入进来就行了
<dependencies>
<dependency>
<groupId>com.jf3q</groupId>
<artifactId>RegisterAutoConfiguration</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
3.测试模块
jf3q:
register:
password: 5678
username: xiaojie
@SpringBootTest
public class StarterTest {
@Autowired
RegService regService;
@Test
void testUser(){
regService.login();
}
}
自定义redis启动器
1.创建autoconfigure模块
- 修改pom文件
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.0.0</version> </parent> <dependencies> <!--导入自动配置的相关依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> <!--方便idea能检测到该依赖中用到的配置属性,能够自动补全,其实就是在编译的时候在 META-INF下生成一个spring-configuration-metadata.json文件--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <!--jedis依赖--> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>建一个属性类
- 新建一个属性类
@Data @ConfigurationProperties(prefix = "jfit.redis") public class MyRedisProperties { private int database = 0; private String host="localhost"; private int timeout=2000;//单位是秒,默认值为0,表示无限制 private String password; private int port=6379; }
- 新建一个自动配置类
@Configuration @EnableConfigurationProperties(MyRedisProperties.class) @ConditionalOnClass(JedisPool.class) public class MyRedisAutoConfiguration { @Autowired MyRedisProperties redisProperties; @Bean public JedisPoolConfig setPoolConfig(){ return new JedisPoolConfig(); } @Bean public JedisPool setPool(){ JedisPool jedisPool = new JedisPool(setPoolConfig(), redisProperties.getHost(), redisProperties.getPort(), redisProperties.getTimeout(), redisProperties.getPassword(), redisProperties.getDatabase()); return jedisPool; } //RedisAPI这是我专门定义的一个工具类 @Bean public RedisAPI setProperties(){ RedisAPI redisAPI = new RedisAPI(); redisAPI.setJedisPool(setPool()); return redisAPI; } }
- 工具类
public class RedisAPI { JedisPool jedisPool; Jedis jedis; public void setJedisPool(JedisPool jedisPool) { this.jedisPool = jedisPool; } public void set(String key, String value, Integer expireTime){ try { jedis = jedisPool.getResource(); jedis.setex(key,expireTime,value); } catch (Exception e) { e.printStackTrace(); } finally { jedisPool.close(); } } public void set(String key, String value){ try { jedis = jedisPool.getResource(); jedis.set(key,value); } catch (Exception e) { e.printStackTrace(); } finally { jedisPool.close(); } } public String get(String tokenString) { try { jedis = jedisPool.getResource(); return jedis.get(tokenString); } catch (Exception e) { e.printStackTrace(); } finally { jedisPool.close(); } return null; } }
- resources下META-INF下新建一个spring.factories(新版本不是这个文件,切记,看上面写的)
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.redis.spring.boot.redisspringbootautoconfigure.MyRedisAutoConfiguration
2.创建starter模块
在pom文件中引用一下上面步骤安装好的依赖即可
<dependencies>
<dependency>
<groupId>com.jf3q</groupId>
<artifactId>redis-spring-boot-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
3.测试模块
在pom文件中把第二步安装好的依赖导入就可以使用了
注意:在创建自动配置模块的时候,一直无法生成那个json文件。导致application.properties里不能自动提示
解决方案:我建自动配置模块的时候把springboot-test引入了导致无法生成json文件
作业
自定义一个启动器,模仿老师的写法,试着写一个redis的启动器(用jedis做增删查)
好博客就要一起分享哦!分享海报
此处可发布评论
评论(0)展开评论
展开评论
您可能感兴趣的博客