什么是Springboot的自动配置?
Spring Boot 自动配置是 Spring Boot 框架的一个重要特性。它能够根据项目中引入的依赖和类路径中的资源,自动对应用进行配置,减少了开发者手动配置大量繁琐的配置文件的工作。这一特性极大地提高了开发效率,使开发者能够更加专注于业务逻辑的实现,而不是花费大量时间在基础配置上。
Condition
在 Spring Boot 中,Condition 是在Spring 4.0 增加的条件判断功能,通过这个可以功能可以实现选择性的创建 Bean 操 作。
Condition 接口定义了 matches 方法,用于进行条件匹配。通过实现 Condition 接口或者使用其派生的注解,如 @ConditionalOnProperty 、 @ConditionalOnClass 、@ConditionalOnMissingBean 等,可以根据各种条件来控制自动配置的启用或禁用。例如,@ConditionalOnProperty 可以根据配置文件中的属性值来决定是否启用自动配置。如果配置文件中指定的属性存在且满足特定的值条件,自动配置才会生效。
案例一:
在 Spring 的 IOC 容器中有一个 User 的 Bean,现要求: 导入Jedis坐标后,加载该Bean,没导入,则不加载。
- context 上下文对象。用于获取环境,IOC容器,ClassLoader对象
- metadata 注解元对象。 可以用于获取注解定义的属性值
- 需求: 导入Jedis坐标后创建Bean
- 思路:判断redis.clients.jedis.Jedis.class文件是否存在
-
导入jedis坐标并加载bean
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
-
编写实体类
package com.yym.springboot_condition_01.domain;
public class User {
}
-
编写配置类
package com.yym.springboot_condition_01.config;
import com.yym.springboot_condition_01.condition.ClassCondition;
import com.yym.springboot_condition_01.domain.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@Configuration
public class UserConfig {
@Bean
@Conditional(value = ClassCondition.class)
public User user(){
return new User();
}
}
-
编写ClassCondition
package com.yym.springboot_condition_01.condition;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class ClassCondition implements Condition {
/**
*
* @param context 上下文对象。用于获取环境,IOC容器,ClassLoader对象
* @param metadata 注解元对象。 可以用于获取注解定义的属性值
* @return
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//1.需求: 导入Jedis坐标后创建Bean
//思路:判断redis.clients.jedis.Jedis.class文件是否存在
boolean flag = true;
try {
Class<?> cls = Class.forName("redis.clients.jedis.Jedis");
}catch (ClassNotFoundException e){
flag = false;
}
return flag;
}
}
-
编写启动类
package com.yym.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
//情况1 没有添加坐标前,发现为空
//情况2 有添加坐标前,发现有对象
// Object redisTemplate = context.getBean("redisTemplate");
// System.out.println(redisTemplate);
/********************案例1********************/
Object user = context.getBean("user");
System.out.println(user);
}
}
-
运行结果
成功---打印输出内存地址
失败---(没有添加jedis) 打印输出NoSuchBeanDefinitionException
案例二:
在 Spring 的 IOC 容器中有一个 User 的 Bean,现要求: 将类的判断定义为动态的。判断哪个字节码文件存在可以动态指定 。
实现步骤:
- 不使用@Conditional(ClassCondition.class)注解
- 自定义注解@ConditionOnClass,因为他和之前@Conditional注解功能一直,所以直接复制
- 编写ClassCondition中的matches方法
-
导入jedis和fastjson坐标并加载bean
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<dependency>
-
编写实体类
package com.yym.springboot_condition_02.domain;
public class User {
}
-
编写配置类
package com.yym.springboot_condition_02.config;
import com.yym.springboot_condition_02.condition.*;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import com.yym.springboot_condition_02.domain.*;
@Configuration
public class UserConfig {
@Bean
@Conditional(value = ClassCondition.class)
@ConditionalOnClass(value = "redis.clients.jedis.Jedis")
// @ConditionalOnClass(value = {"com.alibaba.fastjson.JSON","redis.clients.jedis.Jedis"})
public User user(){
return new User();
}
}
-
编写ClassCondition
package com.yym.springboot_condition_02.condition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import java.util.Map;
public class ClassCondition implements Condition {
/**
*
* @param context 上下文对象。用于获取环境,IOC容器,ClassLoader对象
* @param metadata 注解元对象。 可以用于获取注解定义的属性值
* @return
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//1.需求: 导入Jedis坐标后创建Bean
//思路:判断redis.clients.jedis.Jedis.class文件是否存在
boolean flag = true;
try {
Class<?> cls = Class.forName("redis.clients.jedis.Jedis");
}catch (ClassNotFoundException e){
flag = false;
}
return flag;
}
}
-
编写 ConditionalOnClass
package com.yym.springboot_condition_02.condition;
import org.springframework.context.annotation.Conditional;
import java.lang.annotation.*;
//@Target(({ElementType.TYPE,ElementType.METHOD}))//可以修饰在类与方法上
@Retention(RetentionPolicy.RUNTIME)//注解生效节点runtime
@Documented
@Conditional(value = ClassCondition.class)
public @interface ConditionalOnClass {
String[] value();//设置此注解的属性redis.clients.jedis.Jedis
}
-
编写启动类
package com.yym.springboot_condition_02;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class SpringbootCondition02Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootCondition02Application.class, args);
/********************获取容器中user********************/
Object user1 = context.getBean("user");
System.out.println(user1);
// Object user2 = context.getBean("user2");
// System.out.println(user2);
}
}
-
运行结果
成功---打印输出内存地址
失败---(没有添加jedis) 打印输出NoSuchBeanDefinitionException
案例三:
导入通过注解属性值value指定坐标后创建Bean
-
编写配置类
package com.yym.springboot_condition_02.config;
import com.yym.springboot_condition_02.condition.*;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import com.yym.springboot_condition_02.domain.*;
@Configuration
public class UserConfig {
//情况2
@Bean
//当容器中有一个key=k1且value=v1的时候user2才会注入
//在application.properties文件中添加k1=v1
@ConditionalOnProperty(name = "k1",havingValue = "v1")
public User user2(){
return new User();
}
}
-
编写ClassCondition
package com.yym.springboot_condition_02.condition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import java.util.Map;
public class ClassCondition implements Condition {
/**
*
* @param context 上下文对象。用于获取环境,IOC容器,ClassLoader对象
* @param metadata 注解元对象。 可以用于获取注解定义的属性值
* @return
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//2.需求: 导入通过注解属性值value指定坐标后创建Bean
//获取注解属性值 value
Map<String,Object> map = metadata.getAnnotationAttributes(ConditionalOnClass.class.getName());
System.out.println(map);
String[] value = (String[]) map.get("value");
boolean flag = true;
try {
for (String className : value){
Class<?> cls = Class.forName(className);
}
}catch (ClassNotFoundException e){
flag = false;
}
return flag;
}
}
-
编写 ConditionalOnClass
package com.yym.springboot_condition_02.condition;
import org.springframework.context.annotation.Conditional;
import java.lang.annotation.*;
//@Target(({ElementType.TYPE,ElementType.METHOD}))//可以修饰在类与方法上
@Retention(RetentionPolicy.RUNTIME)//注解生效节点runtime
@Documented
@Conditional(value = ClassCondition.class)
public @interface ConditionalOnClass {
String[] value();//设置此注解的属性redis.clients.jedis.Jedis
}
-
编写启动类
package com.yym.springboot_condition_02;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class SpringbootCondition02Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootCondition02Application.class, args);
/********************获取容器中user********************/
// Object user1 = context.getBean("user");
// System.out.println(user1);
Object user2 = context.getBean("user2");
System.out.println(user2);
}
}
-
运行结果
成功---打印输出内存地址
失败---(没有添加jedis) 打印输出NoSuchBeanDefinitionException
Condition – 小结
自定义条件
① 定义条件类:自定义类实现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
@Enable注解
SpringBoot中提供了很多Enable开头的注解,这些注解都是用于动态启用某些功能的。而其底层原理 是使用@Import注 解导入一些配置类,实现Bean的动态加载。
@Enable底层依赖于@Import注解导入一些类,使用@Import导入的类会被Spring加载到IOC容器中。 而@Import提供4中用法:
① 导入Bean
② 导入配置类
③ 导入 ImportSelector 实现类。一般用于加载配置文件中的类
④ 导入 ImportBeanDefinitionRegistrar 实现类
-
项目目录结构
创建两个项目springboot-enable-03和springboot-enable_other-04,04写的是相关功能类,03是只写启动类,运行时从03运行(删除04的启动类和测试类)
-
向springboot-enable-03的pom.xml中导入springboot-enable_other-04
<dependency>
<groupId>com.yym</groupId>
<artifactId>springboot-enable_other-04</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
-
编写实体类
package com.yym.domain;
public class User {
}
package com.yym.domain;
public class Student {
}
-
编写配置类
package com.yym.config;
import com.yym.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();
}
}
-
编写MyImportSelector
package com.yym.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.yym.domain.User","com.yym.domain.Student",
};
}
}
-
编写MyImportBeanDefinitionRegistrar
package com.yym.config;
import com.yym.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 {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//AnnotationMetadata注解
//BeanDefinitionRegistry向spring容器中注入
//1.获取user的definition对象
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(User.class).getBeanDefinition();
//2.通过beanDefinition属性信息,向spring容器中注册id为user的对象
registry.registerBeanDefinition("user",beanDefinition);
}
}
-
编写 EnableUser
package com.yym.config;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(UserConfig.class)
public @interface EnableUser {
}
-
编写启动类
@SpringBootApplication中有@ComponentScan注解, 扫描范围:当前引导类所在包及其子包。当前引导类所在包com.yym.springbootenable03;注入user类所在包com.yym.springbootenable_other04.config;因此扫描不到,所以容器中没有user。解决方案:
1.使用@ComponentScan扫描com.apesource.springbootenable_other04.config包
2.可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器
3.可以对Import注解进行封装。
Import的4中用法:
1. 导入Bean
2. 导入配置类
3. 导入ImportSelector的实现类
查看ImportSelector接口源码
String[] selectImports(AnnotationMetadata importingClassMetadata);
代表将“字符串数组”中的的类,全部导入spring容器
4. 导入ImportBeanDefinitionRegistrar实现类
package com.yym.springbootenable03;
import com.yym.config.EnableUser;
import com.yym.config.MyImportBeanDefinitionRegistrar;
import com.yym.config.MyImportSelector;
import com.yym.config.UserConfig;
import com.yym.domain.Student;
import com.yym.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;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
//@ComponentScan("com.yym.config")
//@Import(User.class)//导入javaBean
//@Import(UserConfig.class)
@Import(MyImportSelector.class)
//@Import(MyImportBeanDefinitionRegistrar.class)
//@EnableUser
//@EnableScheduling
//@EnableCaching
public class SpringbootEnable03Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnable03Application.class, args);
// 获取Bean
// User user = context.getBean(User.class);
// System.out.println(user);
User user = context.getBean(User.class);
System.out.println(user);
Student student = context.getBean(Student.class);
System.out.println(student);
// User user = (User) context.getBean("user");
// System.out.println(user);
}
}
-
运行结果
成功---打印输出内存地址
@EnableAutoConfiguration 注解(开启自动配置)
@SpringBootApplication 来标注一个主程序类,说明这是一个Spring Boot应用
@SpringBootApplication 源码中有一个@EnableAutoConfiguration 注解
点击@EnableAutoConfiguration注解,使用@Import注释加载了AutoConfigurationImportSelector.class类(自动配置选择器)
进入AutoConfigurationImportSelector,它是一个 ImportSelector。在 Spring 中会默认调用 ImportSelector 的 selectImports方法。
AutoConfigurationImportSelector的核心逻辑在getAutoConfigurationEntry方法中;在getAutoConfigurationEntry 方法中,会执行具体哪些 Bean 应该被加载到 Spring 容器的逻辑,从而完成了自动配置。在源码中,获取候选自动配置类列表是通过 getCandidateConfigurations方法。
在 getCandidateConfigurations 方法中,我们可以知道:在所有包名叫做autoConfiguration的包下面都有META-INF/spring.factories文件。为什么当我们想要自定义 Spring 的自动配置功能时,需要在 META-INF/spring.factories 文件中声明。因为 Spring 默认就会去读取这个文件中的类并将这些类实例化注册成容器的 Bean。
总结原理:
- @EnableAutoConfiguration 注解内部使用 @Import(AutoConfigurationImportSelector.class) 来加载配置类。
- 配置文件位置:META-INF/spring.factories,该配置文件中定义了大量的配置类,当 SpringBoot 应用启动时,会自动加载这些配置类,初始化Bean
- 并不是所有的Bean都会被初始化,在配置类中使用Condition来加载满足条件的Bean
自定义启动器
需求: 自定义redis-starter,要求当导入redis坐标时,SpringBoot自动创建Jedis的Bean
实现步骤
- 创建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
创建springboot-starter-04,redis-spring-boot-starter和redis-spring-boot-autoconfigure。在redis-spring-boot-starter的pom.xml中导入redis-spring-boot-autoconfigure,springboot-starter-04的pom.xml中导入redis-spring-boot-starter。autoconfigure写的是相关功能类,04是只写启动类,运行时从04运行(删除autoconfigure和starter的启动类和测试类)
-
pringboot-starter-04的pom.xml中导入redis-spring-boot-starter
<dependency>
<groupId>com.yym</groupId>
<artifactId>redis-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
-
redis-spring-boot-starter的pom.xml中导入redis-spring-boot-autoconfigure
<dependency>
<groupId>com.yym</groupId>
<artifactId>redis-spring-boot-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
-
编写实体类
package com.yym;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
private String host = "localhost";
private int port = 6379;
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.yym;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
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)
public class RedisAutoconfiguration {
// 注入bean
@Bean
public Jedis jedis(RedisProperties redisProperties){
return new Jedis(redisProperties.getHost(),redisProperties.getPort());
}
}
-
编写spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.yym.RedisAutoconfiguration
-
编写application.yml
spring:
redis:
port: 6666
host: 127.0.0.1
-
编写启动类
package com.yym.springbootstarter04;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import redis.clients.jedis.Jedis;
@SpringBootApplication
public class SpringbootStarter04Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootStarter04Application.class, args);
Jedis bean1 = context.getBean(Jedis.class);
System.out.println(bean1);
}
}
-
运行结果