Spring Boot Starter简化开发原理
在我刚学spring的时候,让我印象深刻的是各种xml的配置,而且很多配置是这个项目配一遍,开新项目时又把旧项目的配置复制一遍,而且比较容易出错,调试一下又要费不少的时间。Starter简化了这些繁琐的配置,遵循Spring Boot“约定大约配置”的理念,在我们不主动进行配置时给予默认的属性,而且很多情况下这些属性都不需要更改。这是怎么实现的呢? 在SpringApplication这个类,也就是启动类上有一个@SpringApplication的注解,该注解是由@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan这三个注解组合而成
@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 {
//不重要的内容忽略
}
点开@EnableAutoConfiguration,可以看到该注解导入了一个AutoConfigurationImportSelector的类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
String[] excludeName() default {};
}
接下来我们重点看这个类:
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
// 忽略不重要的内容
@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());
}
该类继承了DeferredImportSelector,而DeferredImportSelector继承了ImportSelector。这里有一个小知识: 在使用@Import注解来注册bean的时候,Import注解的值可以是ImportSelector的实现类,spring容器会实例化这个实现类,并执行其selectImports方法 因此,spring会调用该类的selectImports方法,而大家注意,这个类继承的是DeferredImportSelector,因此,这个类会在其他@Configuration加载后才会进行加载。接下来我们重点看selectImports方法,该方法调用了getAutoConfigurationEntry(autoConfigurationMetadata,annotationMetadata),进入这个方法:
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);
}
其中,这个方法调用了getCandidateConfigurations(annotationMetadata, attributes);
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;
}
继续进入SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader());
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
这个方法又调用了loadSpringFactories(classLoader),重点来了:
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
//重点
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
可以看到,该方法会从FACTORIES_RESOURCE_LOCATION中获取需要自动配置的类,而FACTORIES_RESOURCE_LOCATION的具体内容如下:
/**
* The location to look for factories.
* <p>Can be present in multiple JAR files.
*/
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
因此,我们可以得出结论spring会从包的类"META-INF/spring.factories"中读取需要自动配置的类,从而实现springboot的自动配置。下面我们用一个常用的starter,验证一下我们得出的结论是否正确
mybatis-spring-boot-starter 原理探索
打开mybatis-spring-boot-starter这个jar,可以看到只有这几个文件
点开pom文件:
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot</artifactId>
<version>2.1.0</version>
</parent>
<artifactId>mybatis-spring-boot-starter</artifactId>
<name>mybatis-spring-boot-starter</name>
<properties>
<module.name>org.mybatis.spring.boot.starter</module.name>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</dependency>
</dependencies>
</project>
这里有一个点,就是starter的作用有两个,一个是依赖管理,另外一个是启动某些功能,这个pom文件体现的就是依赖管理的作用,将该功能所需要的依赖自动引入。而下面这个依赖则是mybatis自动配置的关键
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-autoconfigure</artifactId>
</dependency>
我们主要看两个类,一个是MybatisProperties,一个是MybatisAutoConfiguration,这两个类的命名是有规范的,以AutoConfiguration结尾的,是一个javaConfig形式的配置类,对标spring中以xml形式配置的类,而以Properties结尾的类,其作是从配置文件中读取属性。而AutoConfiguration类会从对应的Properties中获取属性进行配置。打开MybatisProperties类:
/**
* Configuration properties for MyBatis.
*
* @author Eddú Meléndez
* @author Kazuki Shimizu
*/
@ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX)
public class MybatisProperties {
public static final String MYBATIS_PREFIX = "mybatis";
private static final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
/**
* Location of MyBatis xml config file.
*/
private String configLocation;
/**
* Locations of MyBatis mapper files.
*/
private String[] mapperLocations;
// 忽略下面的代码
}
我们可以看到,这个类使用了@ConfigurationProperties注解,这个注解的作用是从配置文件中读取前缀为mybatis的属性,注入到匹配的属性上,如key为mybatis.configLocation的值,就会被注入到该类configLocation的属性上面。接下来我们看一下MyBatisAutoConfiguration的代码
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration implements InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);
private final MybatisProperties properties;
private final Interceptor[] interceptors;
private final TypeHandler[] typeHandlers;
private final LanguageDriver[] languageDrivers;
private final ResourceLoader resourceLoader;
private final DatabaseIdProvider databaseIdProvider;
private final List<ConfigurationCustomizer> configurationCustomizers;
public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider,
ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider,
ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider,
ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
this.properties = properties;
this.interceptors = interceptorsProvider.getIfAvailable();
this.typeHandlers = typeHandlersProvider.getIfAvailable();
this.languageDrivers = languageDriversProvider.getIfAvailable();
this.resourceLoader = resourceLoader;
this.databaseIdProvider = databaseIdProvider.getIfAvailable();
this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
}
//省略部分代码
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
applyConfiguration(factory);
if (this.properties.getConfigurationProperties() != null) {
factory.setConfigurationProperties(this.properties.getConfigurationProperties());
}
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
if (this.databaseIdProvider != null) {
factory.setDatabaseIdProvider(this.databaseIdProvider);
}
if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
}
if (this.properties.getTypeAliasesSuperType() != null) {
factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
}
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
if (!ObjectUtils.isEmpty(this.typeHandlers)) {
factory.setTypeHandlers(this.typeHandlers);
}
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
}
Set<String> factoryPropertyNames = Stream
.of(new BeanWrapperImpl(SqlSessionFactoryBean.class).getPropertyDescriptors()).map(PropertyDescriptor::getName)
.collect(Collectors.toSet());
Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
// Need to mybatis-spring 2.0.2+
factory.setScriptingLanguageDrivers(this.languageDrivers);
if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
defaultLanguageDriver = this.languageDrivers[0].getClass();
}
}
if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
// Need to mybatis-spring 2.0.2+
factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
}
return factory.getObject();
}
//省略部分代码
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
if (executorType != null) {
return new SqlSessionTemplate(sqlSessionFactory, executorType);
} else {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
}
我们先从注解看起: @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class }) 这个注解表示只有存在sqlSessionFactory和SqlSessionFactoryBean这两个类时,才会初始化该类; @ConditionalOnSingleCandidate(DataSource.class) 这个注解表示,只有在仅有一个DataSource.class的或者有多个DataSource,但是指定了首选dataSource时,才会初始化本类(这就是在配置多数据源时要指定首选数据源的原因) @EnableConfigurationProperties(MybatisProperties.class) 这个注解的作用是使MybatisProperties上的@ConfigurationProperties生效 @AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class }) 这个注解的作用是指定当前类加载的顺序 接下来是sqlSessionFactory方法和sqlSessionTemplate方法,这两个方法都有@Bean和@ConditionalOnMissingBean注解, 这表明spring会在用户没有自定义SqlSessionFactory或SqlSessionTemplate的时候自动配置这两个bean。 最后我们打开META-INF文件夹,这里我们看到了一个熟悉的文件:spring.factories,打开文件可以看到:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
根据我们上一部分所学的知识,spring会自动的去配置这两个类,至此,我们可以稍微理一下整个自动加载的流程是怎样的:
1.SpringBootApplication启动类在运行时扫描到@SpringBootApplication注解,该注解是由多个注解组合而成,其中一个注解是@Import(AutoConfigurationImportSelector.class),该类实现ImportSelector接口,当@Import所指定的类是一个ImportSelector的实现类时,springbootApplication会运行其selectImports方法,selectImport方法会扫描所有jar包META-INF文件夹下面的spring.factories文件,然后会以org.springframework.boot.autoconfigure.EnableAutoConfiguration为key,获取所有value,组成一个List,接着会扫描这些List中的类
2.由于mybatis-spring-boot-starter中的spring.factories,因此spring会扫描org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration 这个类,而由于该类上的注解,因此该类只会在存在 1.sqlSessionFactory和SqlSessionFactoryBean这两个类 2.只有在仅有一个DataSource.class的或者有多个DataSource,但是指定了首选dataSource 这两个条件都符合的情况下初始化,且由于@AutoConfigureAfter注解,该类的初始化顺序是在 DataSourceAutoConfiguration和MybatisLanguageDriverAutoConfiguration后面 同时,该类上的@EnableConfigurationProperties注解会使MybatisProperties这个类上的@ConfigurationProperties生效,因此会读取配置文件上所有mybatis为前缀的属性,并匹配到该类的属性上。
3.在初始化MybatisAutoConfiguration时,由于sqlSessionFactory(DataSource dataSource)和sqlSessionTemplate(SqlSessionFactory sqlSessionFactory)这两个方法上有@Bean和 @ConditionalOnMissingBean 这两个注解,因此spring会在用户没有自定义sqlSessionFactory或sqlSessionTemplate的情况下自动使用MybatisProperties这个类从配置文件读取的值来进行初始化,若没有在配置文件读取到对应到属性,则会使用默认值
4.最后,我们感受到的效果是,只要引入mybatis-spring-boot-starter这个依赖,就可以使用mybatis对数据进行访问,而dataSource,sqlSessionFactory这些东西有时候我们自己配置也可以,不配置也可以的原因。