最近工作变动,以前公司使用的一直都是spring,到了新公司后项目都使用了springboot,而我对springboot可以做到没有配置文件还可以智能配置非常感兴趣,所以就大致研究了下springboot的自动配置的源码,本文也只会解释springboot自动配置的原理,老规矩,写的不好的地方可以喷,喷完最好可以告诉我最佳答案。
springboot自动配置的源头便在于启动类上的注解 @SpringBootApplication,进入该注解我们会看到它是由下列注解一起构成:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
我们分别看下这些注解起到了上面作用
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
这四个注解可以称之为元注解,可以理解成它们就是专门用在注解类上的。
@Target() “目标” 表示@SpringBootApplication的使用位置,ElementType.TYPE表示用在类上。
1.CONSTRUCTOR:用于描述构造器
2.FIELD:用于描述域
3.LOCAL_VARIABLE:用于描述局部变量
4.METHOD:用于描述方法
5.PACKAGE:用于描述包
6.PARAMETER:用于描述参数
7.TYPE:用于描述类、接口(包括注解类型) 或enum声明
@Retention() “保留” 表示@SpringBootApplication的存在阶段。
1.RetentionPolicy.SOURCE —— 这种类型的Annotations只在源代码级别保留,编译时就会被忽略
2.RetentionPolicy.CLASS —— 这种类型的Annotations编译时被保留,在class文件中存在,但JVM将 会忽略
3.RetentionPolicy.RUNTIME —— 这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用.
@Documented “文档” 注解表明这个注解应该被 javadoc工具记录
@Inherited “继承” 修饰@SpringBootApplication后,那么@SpringBootApplication用在哪个类上,且该类有子类的话,子类是可以继承父类中的@SpringBootApplication
既然这4个注解是元注解,可以用在所有的注解上,肯定跟springboot的自动配置没关系了,@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
这三个注解才是@SpringBootApplication可以自动配置的关键。所以本文的重点之一就是好好唠唠这三个注解。
@SpringBootConfiguration点进去看@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { }
发现本质就是@Configuration,在类上加上该注解的表明这个类是一个javaconfig,我们继续点进去看@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component
会发现@Component注解。在spring的xml配置<context:component-scan /> 或者 @ComponentScan,容器启动的时候会扫描所有含有@Component的类,通过反射机制将类的信息加载到内存中给IOC使用(这里就不啰嗦,感兴趣可以自行百度).
而javaConfig的配置形式如下:
@Configuration
public class Configuration {
// 任何标志了@Bean的方法,其返回值将作为一个bean注册到Spring的IOC容器中
// 方法名默认成为该bean定义的id
@Bean
public BookBean book() {
return new Book();
}
}
它的作用等同于在spring的xml文件中< bean id="book" class="com.jd.bean.Book" />
所以总结一下,就是
**@SpringBootConfiguration注解会标注一个类是javaConfig,在spring或者springboot启动的时候会被扫描。可以根据@Bean注解实例化对象,作用等于在spring的xml文中定义一个< bean > < /bean> **
@ComponentScan上文中已经简提到,注解对应XML配置形式中的 元素,表示启用组件扫描,Spring会自动扫描所有通过注解配置的bean,然后将其注册到IOC容器中。我们可以通过 basePackages等属性来指定 @ComponentScan自动扫描的范围,如果不指定,默认从声明 @ComponentScan所在类的 package进行扫描。正因为如此,SpringBoot的启动类都默认在 src/main/java下,自定义的类的路径一定要和启动类平级或者下级。
现在看下最关键的注解
@EnableAutoConfiguration,点进去会发现
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import({AutoConfigurationImportSelector.class})
,
一眼就可以看到核心就是@Import({AutoConfigurationImportSelector.class})
。写过代码的都知道import是用于导入类的,其实这个注解的作用也是一样,在初始化的时候需要依赖AutoConfigurationImportSelector.class
,所以通过@import导入。重点看下AutoConfigurationImportSelector中的selectImports方法。
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 从配置文件"META-INF/spring-autoconfigure-metadata.properties"中加载 AutoConfigurationMetadata
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
//从注解中加载exclude和excludeName
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//从所有jar包下的/META-INF/spring.factories的文件中获取到候选的自动配置类,
//使用内部工具SpringFactoriesLoader加载spring.factories文件中
// key为org.springframework.boot.autoconfigure.EnableAutoConfiguration 的value
//建议大家可以自己去文件中看下,此处随意选几个配置类展示
//org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration
//org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
//org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration
//org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
//等等
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
//去重复
configurations = removeDuplicates(configurations);
//去掉注解中特意exclude的配置类
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
// 应用过滤器AutoConfigurationImportFilter,
// 对于 spring boot autoconfigure,定义了一个需要被应用的过滤器 :
// org.springframework.boot.autoconfigure.condition.OnClassCondition,
// 此过滤器检查候选配置类上的注解@ConditionalOnClass,如果要求的类在classpath
// 中不存在,则这个候选配置类会被排除掉
configurations = filter(configurations, autoConfigurationMetadata);
//经过上面的filter,已经过滤掉不需要实例化的配置类,也就是没有在pom文件引入相关jar包
fireAutoConfigurationImportEvents(configurations, exclusions);
return StringUtils.toStringArray(configurations);
}
我们在这看一下springboot的配置类,只选择redis的配置类作为例子,其他的配置类也是一样的:
@Configuration//告诉springboot这是个配置类
@ConditionalOnClass({RedisOperations.class})//当类路径下有指定类的条件下才会实例化
@EnableConfigurationProperties({RedisProperties.class})//可以设置属性
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})//导入另外redis相关的配置类
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}
@Bean
@ConditionalOnMissingBean(//当类路径下不存在指定类的条件下
name = {"redisTemplate"}
)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean//当类路径下不存在指定类的条件下
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
selectImport()方法,顾名思义也就是从配置文件中筛选出需要可以实例化的配置类进行实例化。
springboot的自动配置可以简单的理解成就是同过一系列的注解,让springboot在启动的时候从配置文件中加载需要自动配置的类,如果该配置类有引入相应的jar的时候,springboot便会实例化,大家就可以在后面的代码中不做任何配置直接使用了。如果是没有在pom文件中引入相关的jar包,springboot也便不会给你自动实例化配置类。
springboot在启动的时候实例化了配置文件中的哪些配置类,没有实例化哪些配置类都在debug级别的启动日志中有打印出