一、 什么是自动配置
Spring Boot自动配置是Spring Boot框架的一个核心特性,它可以帮助开发者简化应用程序配置的过程。自动配置是通过在classpath(类路径)中查找特定条件的bean,选择性地为开发者提供默认的bean定义和实例化,从而简化应用程序的配置和使用。
例如,当我们使用Spring Boot框架开发一个web应用时,只需要在pom.xml中添加Spring Boot Starter Web依赖,Spring Boot就会自动配置好一个嵌入式的Tomcat服务器,我们只需要编写对应的Controller,便可以直接运行Web应用了。
Spring Boot还提供了一系列的starter依赖,例如Spring Boot Starter Data JPA、Spring Boot Starter Security等,它们都是基于Spring Boot自动配置特性实现的,能够帮助我们快速集成相应的功能模块。
SpringBoot 自动配置主要是由 @EnableAutoConfigeration 这个注解实现的。在了解这个注解之前我们首先需要明白 @Conditional、@Enable、@Import 这三个注解的含义及用法。
二、@Conditional注解
Spring Boot 不可能一上来就把依赖中提供的所有的bean全部创建,这样不仅会造成严重的冗余还会影响性能,因此我们需要给Spring Boot提供一个判断依据,让 Spring Boot 知道我们需要哪个bean,并创建这个bean,这个判断依据就是 Condition 。
Condition 是在Spring 4.0 增加的条件判断功能,通过这个功能可以实现选择性的创建Bean操作。
为方便理解,我们自定义一个@MyConditional注解并实现应用:
传入需要的完全限定类名:
@Configuration
public class UserConfig {
@Bean
@MyConditional(value = {"xxx.xxx.xxx","xxx.xxx.xxx"})//使用@ConditionalOnProperty可以写入配置文件
//传入需要的完全限定类名,返回值为true则创建bean
public User user(){
return new User();
}
}
自定义@MyConditional注解:
@Target({ElementType.TYPE,ElementType.METHOD})//注解可以修饰类和方法
@Retention(RetentionPolicy.RUNTIME)//注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
@Documented//生成文档
@Conditional(value = MyCondition.class)//指定为自定义的实现类
public @interface MyConditional {
String[] value();//字符数组,用于接收传入的完全限定类名
}
自定义Condition实现类MyCondition:
public class MyCondition implements Condition {
//重写matches方法
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> map = metadata.getAnnotationAttributes(MyConditional.class.getName());
//根据注解名获取注解属性,返回值为map(属性名为key,传入数据为value)
String[] value = (String[]) map.get("value");//获得完全限定类名字符串数组
boolean flag = true;
try{
for (String className : value) {
Class<?> cls = Class.forName(className);//遍历并获得其Class对象
}
}catch (ClassNotFoundException e){
flag = false;//失败返回false,说明没有相应坐标
}
return flag;//成功获得返回true
}
}
三、@Enable注解
SpringBoot中提供了很多Enable开头的注解,这些注解都是用于动态启用某些功能的,而其底层原理时使用 @Import 注解导入一些配置类,实现Bean的动态加载。
@Import
@Enable底层依赖于@Import注解导入一些类,使用@Import导入的类会被Spring加载到IOC容器中。而 @Import 提供四种用法:
- 导入Bean
- 导入配置类
- 导入 ImportSelector 实现类,一般用于加载配置文件中的类(自动配置会用到)
public interface ImportSelector {
//将字符串数组中的类全部导入Spring容器
String[] selectImports(AnnotationMetadata importingClassMetadata);
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
}
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//目前字符串数组的内容是写死的,未来可以设置在配置文件中动态加载
return new String[]{"xxx.xxx.xxx", "xxx.xxx.xxx"};
}
}
- 导入 ImportBeanDefinitionRegistrar 实现类
public interface ImportBeanDefinitionRegistrar {
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,BeanDefinitionRegistry registry,BeanNameGenerator importBeanNameGenerator) {
registerBeanDefinitions(importingClassMetadata, registry);
}
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//AnnotationMetadata注解
//BeanDefinitionRegistry向spring容器中注入
//1.获取user的definition对象
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
//2.通过beanDefinition属性信息,向spring容器中注册id为user的对象
registry.registerBeanDefinition("user", beanDefinition);
}
}
四、@EnableAutoConfigeration注解
@EnableAutoConfigeration 注解是启动类注解@SpringBootApplication的重要组成之一,也是SpringBoot实现自动配置的关键注解。:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration//声明SpringBoot配置注解
@EnableAutoConfiguration//自动配置
//扫描Bean
@ComponentScan(excludeFilters = {//扫描Bean
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {...}
@EnableAutoConfigeration 注解通过Spring提供的 @Import 注解导入了 AutoConfigurationImportSelector 类( @Import 注解可以导入配置类或者Bean到当前类中):
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage///添加该注解的类所在的package作为自动配置package进行管理
@Import(AutoConfigurationImportSelector.class)//加载配置文件中的类
public @interface EnableAutoConfiguration {...}
AutoConfigurationImportSelector 类中 getCandidateConfigurations 方法会将所有自动配置类的信息以List的形式返回,这些配置信息会被Spring容器作Bean来管理。从提示信息可以看到这些自动配置类的信息是在 META-INF/spring.factories 文件下找到的:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
META-INF/spring.factories 文件中的 EnableAutoConfiguration 后面就是自动配置类信息:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
.....................
进入这些类会发现基本每个类或方法上都有 @ConditionalOnProperty 或类似注解修饰,进行选择判断:
@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class,
AnnotatedElement.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false)
public static class JdkDynamicAutoProxyConfiguration {
}
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
public static class CglibAutoProxyConfiguration {
}
}
五、 自定义启动器
使用项目中导入自定义启动器中转坐标:
<dependency>
<groupId>com.lxh</groupId>
<artifactId>redis-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
中转坐标导入自定义启动器坐标:
<dependency>
<groupId>com.lxh</groupId>
<artifactId>redis-spring-boot-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
resources/META-INF/spring.factories 内容:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.lxh.RedisAutoconfiguration
AutoConfigurationImportSelector 类根据配置信息加载对应类实现自动配置:
@Configuration
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoconfiguration {
//注入jedis
//@ConditionalOnBean
@Bean
public Jedis jedis(RedisProperties redisProperties){
return new Jedis(redisProperties.getHost(),redisProperties.getPort());
}
}
同时也可通过配置修改默认配置:
@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;
}
}