一、什么是自动配置?
Spring Boot 自动配置是Spring Boot 框架的核心特性之一,它能够根据项目中添加的依赖自动配置Spring应用程序。
首先我们先来将两个注解:@Condition 和 @Enable
(一)代码简单了解自动配置
<1>不添加 RedisTemplate 的 starter 坐标,观察代码结果可知不会自动注入 RedisTemplate 模板类
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.13</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.apesource</groupId> <artifactId>springboot_homework_condition</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot_homework_condition</name> <description>Demo project for Spring Boot</description> <properties> <java.version>8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
启动类代码:
首先我们新建的项目它的启动类长这样:
package com.apesource.springboot_homework_condition; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SpringbootHomeworkConditionApplication { public static void main(String[] args) { SpringApplication.run(SpringbootHomeworkConditionApplication.class, args); } }
这个启动类中本来就有一句代码—— run 方法,我们按住 ctrl 键并且点击 run ,可以进到 run 方法中去,我们可以看到 run 方法是有返回值的,返回值还是一个ConfigurableApplicationContext 对象。
我们可以打印它的返回值,来取证它是否给我们自动配置。
启动类中代码就加工了一下:
package com.apesource.springboot_homework_condition; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; @SpringBootApplication public class SpringbootHomeworkConditionApplication { public static void main(String[] args) { //启动SpringBoot的应用,返回Spring的IOC容器 ConfigurableApplicationContext context = SpringApplication.run(SpringbootHomeworkConditionApplication.class, args); //获取Bean, redisTemplate<没添加坐标的情况> Object redisTemplate = context.getBean("redisTemplate"); System.out.println(redisTemplate); } }
运行如下:
NoSuchBeanDefinitionException: No bean named 'redisTemplate' 出现找不到的错误,说明没有坐标不会有配置
接下来我们看看加上坐标的情况
<2>添加 RedisTemplate 的 starter 坐标,观察代码结果可知会有自动注入 RedisTemplate 模板类
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.13</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.apesource</groupId> <artifactId>springboot_homework_condition</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot_homework_condition</name> <description>Demo project for Spring Boot</description> <properties> <java.version>8</java.version> </properties> <dependencies> <!-- 添加坐标--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
//启动类代码 package com.apesource.springboot_condition_01; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; @SpringBootApplication public class SpringbootCondition01Application { public static void main(String[] args) { //启动SpringBoot的应用,返回Spring的IOC容器 ConfigurableApplicationContext context = SpringApplication.run(SpringbootCondition01Application.class, args); //获取Bean,redisTemplate //情况2 有添加starter坐标,发现有对象 Object redisTemplate = context.getBean("redisTemplate"); System.out.println(redisTemplate); } }
运行结果如下(显示有对象内存地址,说明 RedisTemplate 模板类自动配置成功了):
结论:有坐标就注入模板类, 没有就不注入,它是有选择地注入
其实这里面所有的条件选择判断都依托于 @Conditional,那我们现在深入了解
(1)Conditionol 注解
Conditionol 是在 Spring 4.0 增加的条件判断功能,通过这个可以功能实现选择性的创建 Bean 操作。我们通过 RedisTemplate 简单演示代码深入了解。
首先在 shift+shift 键弹出搜索框(更多的快捷键可以去看初识 IDEA),搜索 Conditional
对该内容的解释 @Target({ElementType.TYPE, ElementType.METHOD}):说明这个注解可以修饰类、修饰方法 @Retention(RetentionPolicy.RUNTIME):说明这个注解运行时生效 @Documented:说明这个注解可以生成 Document 文档public @interface Conditional { /** * All {@link Condition} classes that must {@linkplain Condition#matches match} * in order for the component to be registered. */ Class<? extends Condition>[] value(); }它只有一个属性,属性名为 value;属性类型为一个,且继承了 Condition。
然后我们按住 Ctrl 键进去 Condition 看看
发现它有一个 matches 方法,返回 boolean 值( matches 方法两个参数:context:上下文对象,可以获取属性值,获取类加载器,获取BeanFactory等;metadata:元数据对象,用于获取注解属性。)那就知道了,子继承父,它这个方法也继承了,就得有实现,有 true\false 两返回值,那肯定是 matches 返回 true 就条件成立,注入;返回 false 就条件不成立,不注入。这就是有选择了。
现在我们用一个实例演示:在 Spring 的 IOC 容器中有一个 User 的 Bean ,要求有选择地加载该 Bean,导入 jedis 坐标后,加载,否则不加载
首先 pom.xml 中导入 jedis 坐标
<!-- jedis坐标--> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> <!-- -->
src-main-java-包中 建立一个 domain 包下 User 类,没有任何属性;如果直接在 Uaer 类注解,那直接就注入了,没意义;所以我们需要建立一个 Config 工具类。
package com.apesource.springboot_homework_condition.domain; public class User { }
package com.apesource.springboot_homework_condition.config; import com.apesource.springboot_homework_condition.domain.User; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration//注解标注该类位配置类 public class UserConfig { @Bean//代表像容器中注入一个User对象 public User user(){ return new User(); } }
因为上面我们也看了 @Conditional 它仅一个继承了 Conditional 注解的的接口 Condition ,那我们就得再创建一个 Condition 的实现类被 @Conditional 引用。
package com.apesource.springboot_homework_condition.condition; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; public class ClassCondition implements Condition { @Override//重写 matches 方法 public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { // 写我们的判断,判断 redis.clients.jedis.Jedis.class文件是否存在,找到就返回 true ,注入,否则不注入 Boolean flag = true; try { Class<?> cls = Class.forName("redis.clients.jedis.Jedis");//存在一个找不到的异常情况,给放try-catch块 } catch (ClassNotFoundException e) { flag = false; } return flag; } }
判断条件重写好了,然后给我们的配置类标注该注解,完成它的功能
package com.apesource.springboot_homework_condition.config; import com.apesource.springboot_homework_condition.condition.ClassCondition; import com.apesource.springboot_homework_condition.domain.User; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; @Configuration//注解标注该类位配置类 public class UserConfig { @Conditional(value = ClassCondition.class) @Bean//代表像容器中注入一个User对象 public User user(){ return new User(); } }
启动类进行测试
package com.apesource.springboot_homework_condition; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; @SpringBootApplication public class SpringbootHomeworkConditionApplication { public static void main(String[] args) { //启动SpringBoot的应用,返回Spring的IOC容器 ConfigurableApplicationContext context = SpringApplication.run(SpringbootHomeworkConditionApplication.class, args); //获取 user 对象 Object user = context.getBean("user"); System.out.println(user); } }
测试结果:
1、注入 jedis 坐标情况,返回对象地址
2、不注入 jedis 坐标情况,没找到
总结:
自定义条件:
① 定义条件类:自定义类实现Condition接口,重写 matches 方法,在 matches 方法中进行逻辑判断,返回 boolean值 。
matches 方法两个参数:
context:上下文对象,可以获取属性值,获取类加载器,获取BeanFactory等。
metadata:元数据对象,用于获取注解属性。
② 判断条件: 在初始化Bean时,使用@Conditional(条件类.class)注解
SpringBoot 提供的常用条件注解: 一下注解在springBoot-autoconfigure的condition包下 ConditionalOnProperty:判断配置文件中是否有对应属性和值才初始化Bean ConditionalOnClass:判断环境中是否有对应字节码文件才初始化Bean ConditionalOnMissingBean:判断环境中没有对应Bean才初始化Bean ConditionalOnBean:判断环境中有对应Bean才初始化Bean
(2)Enable 注解
SpringBoot中提供了很多Enable开头的注解,这些注解都是用于动态启用某些功能的。而其底层原理是使用@Import注解导入一些配置类,实现Bean的动态加载
我们查看Enable相关注解,比如 @EnableScheduling 和 @EnableCaching,可以发现除了常规注解,它还分别 Import 导入了 SchedulingConfiguration和CachingConfigurationSelector 类
由此可知:
@Enable底层依赖于@Import注解导入一些类,使用 @Import 导入的类就会被 Spring 加载到 IOC 容器中。而 @Import 提供四种用法
可导入 Bean、配置类、ImportSelector 实现类(一般用于加载配置文件的类)和ImportBeanDefinitionRegistar 实现类
我们现在就代码理解一下:
我们拿多模块试试
新建一个项目 springboot_enable_01,基于该项目新建一个同级模块springboot_enable_other_02
我们在 springboot_enable_other_02 com.apesource.domain包中新建一个 User 类,在 springboot_enable_other_02 com.apesource.config 包中新建一个 UserConfig 配置类
package com.apesource.domain; public class User { }
package com.apesource.config; import com.apesource.domain.User; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration//注解标注配置类 public class UserConfig { @Bean//创建对象 public User user(){ return new User(); } }
完成!然后想在 springboot_enable_01 中调用 User对象,这就是多模块编程了,怎么联系起来呢?只需要将 springboot_enable_other_02 坐标导入 springboot_enable_01中。
复制 02 配置信息:
将该坐标粘贴至 01
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.13</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.apesource</groupId> <artifactId>springboot_enable_01</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot_enable_01</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!-- 导入 02 坐标--> <dependency> <groupId>com.apesource</groupId> <artifactId>springboot_enable_other_02</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <!-- --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
启动类:
package com.apesource.springboot_enable_01; import com.apesource.domain.User; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.ComponentScan; /*仅仅导入02坐标我们还不足以拿到 Bean 对象,需要注解: *@ComponentScan:@SpringBootApplication中有@ComponentScan注解, *扫描范围:当前引导类所在包及其子包(不在同一包就扫描不到)*/ @SpringBootApplication @ComponentScan("com.apesource.config") public class SpringbootEnable01Application { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnable01Application.class, args); //获取 Bean 对象 User user = context.getBean(User.class); System.out.println(user); } }
结果(有对象地址,获取对象成功):
启动类(@Import 导入 Bean):
package com.apesource.springboot_enable_01; import com.apesource.domain.User; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Import; /*仅仅导入02坐标我们还不足以拿到 Bean 对象,需要注解: *@ComponentScan:@SpringBootApplication中有@ComponentScan注解, *扫描范围:当前引导类所在包及其子包(不在同一包就扫描不到)*/ @SpringBootApplication //@ComponentScan("com.apesource.config") /*可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器*/ @Import(User.class)//加载类 public class SpringbootEnable01Application { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnable01Application.class, args); //获取 Bean 对象 User user = context.getBean(User.class); System.out.println(user); } }
结果(有对象地址,获取对象成功):
启动类(@Import 导入 配置类):
package com.apesource.springboot_enable_01; import com.apesource.config.UserConfig; import com.apesource.domain.User; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Import; /*仅仅导入02坐标我们还不足以拿到 Bean 对象,需要注解: *@ComponentScan:@SpringBootApplication中有@ComponentScan注解, *扫描范围:当前引导类所在包及其子包(不在同一包就扫描不到)*/ @SpringBootApplication //@ComponentScan("com.apesource.config") /*可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器*/ //@Import(User.class)//加载类 @Import(UserConfig.class)//加载配置类 public class SpringbootEnable01Application { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnable01Application.class, args); //获取 Bean 对象 User user = context.getBean(User.class); System.out.println(user); } }
结果(有对象地址,获取对象成功):
除过这三种方式,我们还可以仿照 Enable 的注解自定义一个 Enable 的注解,我们在 02 的config 的包中新建一个注解
复制 Enable 注解(随便点进去一个 Enable 注解都可以复制)中可复制前三行常规注解
package com.apesource.config; import org.springframework.context.annotation.Import; import java.lang.annotation.*; /*复制的前三行常规注解*/ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(UserConfig.class)//导入UserConfig配置类 public @interface EnableUser { }
又回到 01 启动类,用我们自己造的注解——(@Import 导入 配置类):
package com.apesource.springboot_enable_01; import com.apesource.config.EnableUser; import com.apesource.config.UserConfig; import com.apesource.domain.User; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Import; /*仅仅导入02坐标我们还不足以拿到 Bean 对象,需要注解: *@ComponentScan:@SpringBootApplication中有@ComponentScan注解, *扫描范围:当前引导类所在包及其子包(不在同一包就扫描不到)*/ @SpringBootApplication //@ComponentScan("com.apesource.config") /*可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器*/ //@Import(User.class)//加载类 //@Import(UserConfig.class)//加载配置类 @EnableUser public class SpringbootEnable01Application { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnable01Application.class, args); //获取 Bean 对象 User user = context.getBean(User.class); System.out.println(user); } }
结果(有对象地址,获取对象成功):
@Import 导入ImportSelector 实现类(一般用于加载配置文件的类)
我们在 02 config 包新建一个 ImportSelector 的实现类,重写方法的这个 String[] 数组内容可以被 Import 直接导入,为了方便看,我们 domain 新建了个 Student 类,然后将 User 类和 Student 类都导入,那我们的配置类就要多创建一个 Bean 对象
package com.apesource.domain; public class Student { }
package com.apesource.config; import com.apesource.domain.Student; import com.apesource.domain.User; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration//注解标注配置类 public class UserConfig { @Bean//创建对象 public User user(){ return new User(); } @Bean//创建对象 public Student student(){ return new Student(); } }
package com.apesource.config; import org.springframework.context.annotation.ImportSelector; import org.springframework.core.type.AnnotationMetadata; public class MyImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { //目前字符串内容写死的 return new String[]{"com.apesource.domain.User","com.apesource.domain.Student"}; } }
又回到 01 启动类:
package com.apesource.springboot_enable_01; import com.apesource.config.EnableUser; import com.apesource.config.MyImportSelector; import com.apesource.config.UserConfig; import com.apesource.domain.Student; import com.apesource.domain.User; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Import; /*仅仅导入02坐标我们还不足以拿到 Bean 对象,需要注解: *@ComponentScan:@SpringBootApplication中有@ComponentScan注解, *扫描范围:当前引导类所在包及其子包(不在同一包就扫描不到)*/ @SpringBootApplication //@ComponentScan("com.apesource.config") /*可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器*/ //@Import(User.class)//加载类 //@Import(UserConfig.class)//加载配置类 //@EnableUser @Import(MyImportSelector.class) public class SpringbootEnable01Application { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnable01Application.class, args); //获取 Bean 对象 User user = context.getBean(User.class); System.out.println(user); Student student = context.getBean(Student.class); System.out.println(student); } }
结果(有对象地址,获取对象成功):
@Import 导入ImportBeanDefinitionRegistar 实现类
我们在 02 config 包新建一个 ImportBeanDefinitionRegistar 的实现类,重写registerBeanDefinitions 方法,
又回到 01 启动类:package com.apesource.config; import com.apesource.domain.User; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.type.AnnotationMetadata; public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { //registerBeanDefinitions 注册 Bean 的信息对象 @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) { //AnnotationMetadata注解 //BeanDefinitionRegistry向spring容器中注入 //1.获取user类的definition对象 AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition(); //2.通过beanDefinition属性信息,向Spring容器中注册id为user的对象 registry.registerBeanDefinition("user",beanDefinition);//id,类型 } }
结果(有对象地址,获取对象成功):package com.apesource.springboot_enable_01; import com.apesource.config.EnableUser; import com.apesource.config.MyImportBeanDefinitionRegistrar; import com.apesource.config.MyImportSelector; import com.apesource.config.UserConfig; import com.apesource.domain.Student; import com.apesource.domain.User; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Import; /*仅仅导入02坐标我们还不足以拿到 Bean 对象,需要注解: *@ComponentScan:@SpringBootApplication中有@ComponentScan注解, *扫描范围:当前引导类所在包及其子包(不在同一包就扫描不到)*/ @SpringBootApplication //@ComponentScan("com.apesource.config") /*可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器*/ //@Import(User.class)//加载类 //@Import(UserConfig.class)//加载配置类 //@EnableUser //@Import(MyImportSelector.class) @Import({MyImportBeanDefinitionRegistrar.class}) public class SpringbootEnable01Application { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnable01Application.class, args); //获取 Bean 对象 User user = context.getBean(User.class); System.out.println(user); // Student student = context.getBean(Student.class); // System.out.println(student); } }
![]()
(3)@EnableAutoConfiguration 注解
SpringBoot 中提供了很多 Enable 开头的注解,这些注解都是用于动态启用某些功能的。而其底层原理 是使用@Import注 解导入一些配置类,实现 Bean 的动态加载
关于 Springboot 的自动配置,其实最重要的就是我们启动类上的这个核心注解@SpringBootApplication,SpringBootApplication翻译过来就是 Springboot 主配置文件,它也确实是自动配置的支持。
我们按住 Ctrl 键点进去该注解看看
前三行就是常规的注解嘛,在此解释后三个。
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class):扫描启动类及其子类所有的内容
@SpringBootConfiguration:SpringBoot 配置类,Ctrl 键点进去该注解看看
@Configuration——配置类,所以@SpringBootConfiguration只是进行了二次封装,那也就是说,我们的启动类本质上是一个配置类
@EnableAutoConfiguration——自动配置,Ctrl 键点进去该注解看看
解释:
@AutoConfigurationPackage:自动配置包,帮我们找当前启动类所在的包,交给@ComponentScan 扫描
@Import(AutoConfigurationImportSelector.class)——做自动配置功能的注解
Ctrl 键点进去该注解看看 AutoConfigurationImportSelector 这个类
Ctrl 键点进去该注解看看 DeferredImportSelector,就是 ImportSelector 接口
然后我们就又回到这里:
往下滑,我们可以看到
它这个字符串将 autoConfigurationEntry.getConfigurations() 的结果集打包成字符串了,那我们点进去 getAutoConfigurationEntry 方法看看
返回字符串类型的集合,返回 configurations 这个属性,想知道这个集合咋来的,点进去getCandidateConfigurations 方法看看
我们可以 debug 看看它这个集合哪里来的,打断点
回到我们的去启动类,注释掉刚刚我们获取的对象,测试启动类本身
一直按红圈圈圈住的那个按钮——下一步,直到出现我们的 List 集合,144个,展开可以看到好多
所以我们的启动类这么多的都自动配置了。
我们可以在这里找到那么多的配置的源头在 META-INF/spring.factories 这。
spring.factories 就在项目的 jar 包里。项目的 External Libraries 中
autoconfigure 自动配置,展开就能看到 META-INF。META-INF里面就有 spring.factories文件
点开就可以看到那144个代表自动配置的键值对
说明:
启动类:
启动类的注解@SpringBootApplication 标注一个主程序类,说明是一个 Spring Boot 应用。
SpringApplication.run(SpringbootApplication.class, args) 实际上启动了一个服务
@SpringBootApplication注解内部
@ComponentScan
对应 XML 配置中的元素;
作用:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中
@SpringBootConfiguration
作用:SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类
@AutoConfigurationPackage
自动配置包
@EnableAutoConfiguration
开启自动配置功能:以前我们需要自己配置的东西,而现在SpringBoot可以自动帮我们配置 ;@EnableAutoConfiguration 告诉SpringBoot开启自动配置功能,这样自动配置才能生效
@Import({AutoConfigurationImportSelector.class})
给容器导入组件
AutoConfigurationImportSelector
自动配置导入选择器,给容器中导入一些组件
总结:
@EnableAutoConfiguration 注解内部使用 @Import(AutoConfigurationImportSelector.class) 来加载配置类。
配置文件位置:META-INF/spring.factories,该配置文件中定义了大量的配置类。
当 SpringBoot 应用启动时,会自动加载这些配置类,初始化Bean 并不是所有的Bean都会被初始化,在配置类中使用Condition来加载满足条件的Bean
二、自定义启动器
目的:自定义 redis-starter,要求当导入redis坐标时,SpringBoot自动创建 Jedis 的 Bean
starter
首先新建项目 Springboot_starter ,据此项目新建一个同级模块,符合启动器命名规范,叫 redis-spring-boot-starter,将来要导入它,redis-spring-boot-starter,它不能有主配置文件、启动类和测试类,得删掉。然后将 redis-spring-boot-starter 的项目信息:
导入到Springboot_starter 中:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.13</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.apesource</groupId> <artifactId>springboot_starter</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot_starter</name> <description>Demo project for Spring Boot</description> <properties> <java.version>8</java.version> </properties> <dependencies> <!-- redis-spring-boot-starter 的项目信息--> <dependency> <groupId>com.apesource</groupId> <artifactId>redis-spring-boot-starter</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
redis-spring-boot-starter 它不可能造 redis 文件,所以它也得导入一个坐标。又新建一模块redis-spring-boot-autoconfigure ,它同样不能有启动类和测试类,得删掉。因为它不能单独启动或运行。redis-spring-boot-autoconfigure 的项目信息导入 redis-spring-boot-starter 中
<!-- redis-spring-boot-autoconfigure 的项目信息--> <dependency> <groupId>com.apesource</groupId> <artifactId>redis-spring-boot-autoconfigure</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
目的:自定义 redis-starter,要求当导入redis坐标时,SpringBoot自动创建 Jedis 的 Bean
starter,要在 redis-spring-boot-autoconfigure 中操作。所以第一步,导入 jedis 坐标,即
<!-- jedis 坐标--> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency>
第二步、需要将 jedis 注入容器中,所以第二步建一个测试类——RedisAutoconfiguration
package com.apesource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import redis.clients.jedis.Jedis; @Configuration public class RedisAutoconfiguration { //注入 jedis @Bean public Jedis jedis(){ return new Jedis("localhost",6379); } }
配置类写好了,但是怎么加载?我们前面看到springboot那些自启动项都在在META-INF/spring.factories中,那我们在src-main-resource中找到META-INF包和包里spring.factories文件,文件可以看到我们的我们的自启动类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.apesource.RedisAutoconfiguration
既然都找到了,那我们去 Springboot_starter 启动类测试
package com.apesource.springboot_starter; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import redis.clients.jedis.Jedis; @SpringBootApplication public class SpringbootStarterApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(SpringbootStarterApplication.class, args); Jedis bean = context.getBean(Jedis.class); System.out.println(bean); } }
结果:
结果出现 localhost:6379 ,我们的自定义启动器成功创建。
优化:动态端口
一、 Springboot_starter 的 yml 文件(新建)或者 properties 文件中添加相应键值对,我以 yml 文件进行演示。
想要实现:我配了端口和ip,就用我配的,我没配就按照默认的《yml 文件中配信息》
spring: redis: port: 6069 host: 127.0.0.1
这个配置的端口和 id 得在 Jedis 注入时就生效,所以 redis-spring-boot-autoconfigure 在运行时得读取到 yml 文件。
在 redis-spring-boot-autoconfigure 新建一类 RedisProperties ,用来实现读取到 yml 文件并完成注入操作
package com.apesource; import org.springframework.boot.context.properties.ConfigurationProperties; //该类实现读取到 yml 文件并完成注入 @ConfigurationProperties(prefix = "spring.redis")//批量注入 public class RedisProperties { //定义默认端口号和 id private String host="localhost"; private int port=6379; /*************get\set方法*************/ public String getHost() { return host; } public void setHost(String host) { this.host = host; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } }
package com.apesource; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import redis.clients.jedis.Jedis; @Configuration @EnableConfigurationProperties(RedisProperties.class) //@EnableConfigurationProperties作用RedisPropertie中批量注入生效 public class RedisAutoconfiguration { @Bean public Jedis jedis(RedisProperties redisProperties){//装配该类 return new Jedis(redisProperties.getHost(),redisProperties.getPort()); } }
结果为我们自己装配的端口和 id: