一、前言
自动配置是spring boot 一个重要特性,所谓“自动”就是我们直接引用功能所需jar包,除了极个别的核心配置外,几乎不用额外的配置,从而减少繁琐配置项,也就是常说的约定大于配置思想的体现。下面结合mybatis插件--tk.mybatis说说spring boot如何实现的自动配置的。
tk.mybatis可以看做是mybatis框架的一个插件,项目提供了常规的增删改查操作以及Example 相关的单表操作。通用 Mapper 是为了解决 MyBatis 使用中 90% 的基本操作,使用它可以很方便的进行开发,可以节省开发人员大量的时间。项目地址
二、实现原理
1、spring boot入口
既然是自动配置,肯定是在spring boot启动中实现的注入,我们来从spring的入口找寻答案:
@SpringBootApplication //spring boot启动的核心注解
public class NewMediaMpApiApplication
public static void main(String[] args) {
SpringApplication.run(NewMediaMpApiApplication.class, args);
}
}
这是spring boot 最简单的启动类,点开@SpringBootApplication这个注解看下。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration//开启自动配置类
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
//omit code ...
}
2、自动配置入口
这个注解是有多个注解复合而成,其中@EnableAutoConfiguration由名字可以看它应该就是实现自动配置的入口,顺便说一下,@Enable**开头的注解在spring boot中往往都是开启xx功能的入口,我们在看源码的时候可以从这里入手。跟进去看看:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
//omit code ...
}
这个注解的描述官方给出的描述是:
Enable auto-configuration of the Spring Application Context, attempting to guess and configure beans that you are likely to need
即,为spring容器上下文开启自动配置,尝试猜测你需要加载哪些配置bean。
3、收集需要配的类
顺着@Import可以找到,整个自动配置的实现都是通过AutoConfigurationImportSelector这个选择器实现把jar中需要的组件导入到spring IOC容器中这一功能。(@Import是通过导入的方式将类加载到spring容器中的),AutoConfigurationImportSelector实现了ImportSelector接口,官方文档是这么描述这个接口的:
Interface to be implemented by types that determine which @{@link Configuration}, class(es) should be imported based on a given selection criteria, usually one or more annotation attributes.
文档说的很明白,实现这个接口的类按照给定的选择条件(通常是注解中的一个或多个属性),筛选出需要注入的配置类。该接口定义了一个方法:返回满足条件配置类的数组。我们看下AutoConfigurationImportSelector是如何实现这个方法的:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
//核心方法,获取满足条件的配置信息
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
核心方法就是获取配置类信息,我们往下继续跟进到底是怎么获取的配置:
protected AutoConfigurationEntry getAutoConfigurationEntry(
AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//获取所有候选的配置信息
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
//过滤掉重复的
configurations = removeDuplicates(configurations);
//排除掉明确需要排除的配置
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
这个方法主要的功能是获取了候选的配置类,然后再按规则过滤掉,这里只关注如何获取候选配置类的,往下走:
/**
* Return the auto-configuration class names that should be considered
* 返回认为需要自动配置的类
*/
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;
}
4、配置类是从哪里加载的呢?
我们看到加载配置类的方法了,胜利就在前方,进SpringFactoriesLoader.loadFactoryNames()方法:
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
//loadFactoryNames主要调用的方法
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
//FACTORIES_RESOURCE_LOCATION的值 "META-INF/spring.factories"
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
//根据spring.factories配置中的value值获取候选的配置类
while (urls.hasMoreElements()) {
//循环遍历配置项的值
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
//将配置类的文件路径加入到结果集中
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
至此,我们找到自动配置加载的起始位置: META-INF/spring.factories。spring通过SpringFactoriesLoader.loadFactoryNames()扫描Java jar包下META-INF/spring.factories文件,获取所有需要加载的配置类,从而初始化到spring容器中。
下面我们去看下使用自动配置的jar包中看是否是这样实现的,用前文说到我们以tk.mybaits为例:
<img src="https://upload-images.jianshu.io/upload_images/14480907-b912eb91a942dab1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="1.png" style="zoom: 67%;" />
打开spring.factories文件看看配置文件里的内容:
这个配置文件告诉spring,MapperAutoConfiguration就是tk.mybaits的配置类,根据此配置类将运行所需的内容注入到spring的IOC容器中。
三、tk加载到spring容器中
1、MapperAutoConfiguration配置文件详解
我们来详细学习下这个配置类,简化细节:
//声明这是一个配置类
@org.springframework.context.annotation.Configuration
//这两个配置类必须存在才能初始换当前配置类
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
//spring容器中必须存在DatcSource才会初始化当前配置类
@ConditionalOnBean(DataSource.class)
//导入MybatisProperties.class文件中的关于mybatis的配置
@EnableConfigurationProperties({MybatisProperties.class})
//当前配置类必须在DataSourceAutoConfiguration.class加载后才能初始化
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
//当前配置类必须在MybatisAutoConfiguration加载之前才能初始化
@AutoConfigureBefore(name = "org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration")
public class MapperAutoConfiguration {
//omit code...
//从spring中为MapperAutoConfiguration赋值
public MapperAutoConfiguration(MybatisProperties properties,
ObjectProvider<Interceptor[]> interceptorsProvider,
ResourceLoader resourceLoader,
ObjectProvider<DatabaseIdProvider> databaseIdProvider,
ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
this.properties = properties;
this.interceptors = interceptorsProvider.getIfAvailable();
this.resourceLoader = resourceLoader;
this.databaseIdProvider = databaseIdProvider.getIfAvailable();
this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
}
//如果spring容器中没有sqlSessionFactory,注入这个bean
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
//omit code ...
}
//如果spring容器中没有sqlSessionTemplate,注入这个bean
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
//omit code ...
}
//这个是整个tk.mybatis的核心配置文件,即扫描
public static class AutoConfiguredMapperScannerRegistrar
implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
//扫描所有被@Mapper注解修饰的接口,也是在这一个过程中,tk生成了内置的常用方法的sql,用于调用时在mybatis执行
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
logger.debug("Searching for mappers annotated with @Mapper");
//omit code...
}
//omit code ...
}
}
本配置类的注解描述部分,主要声明了所依赖的bean,需要以及初始化所需要的属性,是初始化到spring容器的先决条件。tk作为mybatis的插件,在加载数据源先关组件后,加载mybatis前插入tk加载过程,使用上述注解组合达到这个目的。前置条件满足后,最终目的还是AutoConfiguredMapperScannerRegistrar配置的加载,即tk生成了内置的常用方法的sql,用于调用时在mybatis执行。
2、@Configuration配合使用的常见注解
另外,还有写配合@Configuration使用的常见注解:
-
属性类注解
EnableConfigurationProperties
-
@ConfigurationProperties(prefix = "xxx")
在需要注入配置的类上加上这个注解,prefix的意思是,以该前缀打头的配置,@EnableConfigurationProperties({MybatisProperties.class}) 开启tk.mybatis的配置项
-
条件系列注解,@Conditon*
-
@ConditionalOnBean 仅仅在当前上下文中存在某个对象时,才会实例化一个Bean
@ConditionalOnBean(DataSource.class) 表示容器中已经注入了数据源的对象
-
@ConditionalOnClass 某个class位于类路径上,才会实例化一个Bean),该注解的参数对应的类必须存在,否则不解析该注解修饰的配置类
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class}) 表示必须要存在这两个和数据路连接相关的类
@ConditionalOnExpression 当表达式为true的时候,才会实例化一个Bean
@ConditionalOnMissingBean 仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean
@ConditionalOnMissingClass 某个class类路径上不存在的时候,才会实例化一个Bean
@ConditionalOnNotWebApplication 不是web应用时,才会执行
-
-
顺序系列注解
-
@AutoConfigureAfter 在加载配置的类之后再加载当前类
@AutoConfigureAfter(DataSourceAutoConfiguration.class) 表示在加载数据源的配置之后加载tk.mybatis的配置类
-
@AutoConfigureBefore 在加载配置的类之后再加载当前类
@AutoConfigureBefore(name = "org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration") 表示要在mybatis配置类加载前完成tk.mybatis的注入
-
至此,spring将tk运行所需的全部组件在启动时注入到spring的IOC容器中。
3、spring boot内置的自动配置项
从而实现了spring boot的自动配置,了解整个过程后,我们就可以知道spring boot中那些导入包简单配置下就能用的原理了,比如Spring data jpa 、Spring data redis等配置上连接地址就可以用了,其实秘密就在于此,我们来看下spring boot 中的内置的自动配置:
是不是看着很眼熟,这就是spring boot常说的“简化配置”的关键所在。
四、自动配置的思维导图
我将自动配置的过程整理成思维导图帮助理解和理解,如下:
欢迎拍砖,欢迎交流~
注:转载请注明出处