springboot的自动化配置做了什么
在开始这篇文章前,我想问大家一个问题,那就是springboot的自动化配置到底为我们做了什么?
不知道各位朋友有没有使用SSM框架搞过开发,也就是不用springboot,需要自己手动整合spring,springMVC和mybatis这3个框架,之前我们在使用spring的时候需要写大量的xml配置或Java配置类,就像下面这个样子:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置整合mybatis过程 -->
<!-- 1.配置数据库相关参数properties的属性:${url} -->
<context:property-placeholder location="classpath:jdbc.properties" />
<!-- 2.数据库连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 配置连接池属性 -->
<property name="driverClass" value="${jdbc.driver}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!-- c3p0连接池的私有属性 -->
<property name="maxPoolSize" value="30" />
<property name="minPoolSize" value="10" />
<!-- 关闭连接后不自动commit -->
<property name="autoCommitOnClose" value="false" />
<!-- 获取连接超时时间 -->
<property name="checkoutTimeout" value="10000" />
<!-- 当获取连接失败重试次数 -->
<property name="acquireRetryAttempts" value="2" />
</bean>
<!-- 3.配置SqlSessionFactory对象 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 注入数据库连接池 -->
<property name="dataSource" ref="dataSource" />
<!-- 配置MyBaties全局配置文件:mybatis-config.xml -->
<property name="configLocation" value="classpath:mybatis-config.xml" />
<!-- 扫描entity包 使用别名 -->
<property name="typeAliasesPackage" value="com.soecode.lyf.entity" />
<!-- 扫描sql配置文件:mapper需要的xml文件 -->
<property name="mapperLocations" value="classpath:mapper/*.xml" />
</bean>
<!-- 4.配置扫描Dao接口包,动态实现Dao接口,注入到spring容器中 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 注入sqlSessionFactory -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
<!-- 给出需要扫描Dao接口包 -->
<property name="basePackage" value="com.soecode.lyf.dao" />
</bean>
</beans>
但是自从我们使用springboot后就再也没有写过这些配置了,比如我们在引入spring-boot-starter-jdbc
的依赖后,然后在application.yml中配置好数据库的信息后,就可以直接使用JdbcTemplate了,而不需要再去将JdbcTemplate注册为一个bean放到spring容器中,就像下面这样:
@SpringBootTest(classes = SpringbootAutoConfigApplication.class)
public class DataSourceTest {
// 直接使用JdbcTemplate,而不需要手动通过@configuration配置类 + @bean的方式配置JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void test_query_teacher() {
List<Teacher> teachers = jdbcTemplate.query("select * from teacher", new BeanPropertyRowMapper<>(Teacher.class));
teachers.forEach(System.out::println);
}
}
springboot是如何做到的呢?答案就是自动化配置,springboot通过自动化配置的方式帮我们将一些对象自动注入到spring容器中,下面我们将揭晓springboot是如何做到的。
springboot自动化配置的原理
我们在开发一个springboot项目时肯定会有一个主启动类,如下所示:
@SpringBootApplication
public class SpringbootAutoConfigApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootAutoConfigApplication.class, args);
}
}
然后我们注意看这个@SpringBootApplication
注解,它内部有一个@EnableAutoConfiguration
注解,这个注解就是用来实现自动化配置的。
@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
注解,可以看到这里面有两个关键的注解,分别是:@Import(AutoConfigurationImportSelector.class)
和@AutoConfigurationPackage
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}
这两个注解都非常关键
@Import(AutoConfigurationImportSelector.class)的作用
我们知道spring中的@Import
注解是用来加载配置类的,这里的@Import(AutoConfigurationImportSelector.class)
表示加载的配置类是AutoConfigurationImportSelector
, 然后我们看下这个类的逻辑:
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
}
可以看到这个类实现了很多个接口,其中和自动化配置有关的接口是DeferredImportSelector
,然后我们看下这个接口:
public interface DeferredImportSelector extends ImportSelector {
/**
* 导入分组
*/
@Nullable
default Class<? extends Group> getImportGroup() {
return null;
}
/**
* 处理不同的分组结果
*/
interface Group {
/**
* 这个方法中用来加载自动化配置
*/
void process(AnnotationMetadata metadata, DeferredImportSelector selector);
}
这里面比较重要的方法是getImportGroup和Group接口中的process方法,它目前只有一个实现
@Override
public Class<? extends Group> getImportGroup() {
return AutoConfigurationGroup.class;
}
然后我们看下AutoConfigurationGroup类中process方法的实现:
@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
// 这里是参数校验的代码,可以忽略
Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
() -> String.format("Only %s implementations are supported, got %s",
AutoConfigurationImportSelector.class.getSimpleName(),
deferredImportSelector.getClass().getName()));
// 获取自动化配置的信息
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(annotationMetadata); // 获取自动化配置的信息
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
上面这段代码中,关键代码是getAutoConfigurationEntry(annotationMetadata);
它是用来获取springboot的自动化配置信息,我们具体看下这个方法的逻辑:
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 这里是在获取注解的属性配置信息
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 这里开始读取所有的springboot自动化配置
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
上面的代码中,最重要的代码是:getCandidateConfigurations(annotationMetadata, attributes);
这里将会读取springboot的自动化配置信息,咱们具体看下:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 这里是在从spring.factories文件中读取自动化配置信息,我们在spring.factories中自定义的配置类就是在这里被解析的
List<String> configurations = new ArrayList<>(
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
// 这里是在从META-INF/spring/%s.imports中读取springboot的自动化配置信息,是需要阅读的重点方法
ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
我们重点看下ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
这行代码,它的作用是从META-INF/spring/%s.imports中读取springboot的自动化配置信息。
public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
Assert.notNull(annotation, "'annotation' must not be null");
ClassLoader classLoaderToUse = decideClassloader(classLoader);
// 这里就是在读取springboot中的自动化配置文件的具体信息,LOCATION是一个常量,表示:"META-INF/spring/%s.imports"
String location = String.format(LOCATION, annotation.getName());
Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
List<String> importCandidates = new ArrayList<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
importCandidates.addAll(readCandidateConfigurations(url));
}
return new ImportCandidates(importCandidates);
}
我们可以从上面的方法中得知,springboot加载的就是org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件的内容,如下图所示:
然后我们看下这个文件中有什么,可以看到里面放的全是自动化配置的路径信息
相信看到这里,大家应该就明白springboot是如何加载自动化配置信息的了,但是到这里还没有完,我们先进入到其中一个自动化配置类中看看,比如我们最常用的JDBC,在jdbc这个包中,我们需要重点关注的是类名以AutoConfiguration
结尾的类,这些就是自动化配置类,如下图所示:
然后咱们可以看到这个配置中有一个@Import({ DatabaseInitializationDependencyConfigurer.class, JdbcTemplateConfiguration.class, NamedParameterJdbcTemplateConfiguration.class })
的配置表示加载DatabaseInitializationDependencyConfigurer,JdbcTemplateConfiguration,NamedParameterJdbcTemplateConfiguration这3个配置类,由于我们在开发中经常会用到JdbcTemplate
,所以我们重点看下JdbcTemplateConfiguration这个配置类:
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(JdbcOperations.class)
class JdbcTemplateConfiguration {
@Bean
@Primary
JdbcTemplate jdbcTemplate(DataSource dataSource, JdbcProperties properties) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
JdbcProperties.Template template = properties.getTemplate();
jdbcTemplate.setFetchSize(template.getFetchSize());
jdbcTemplate.setMaxRows(template.getMaxRows());
if (template.getQueryTimeout() != null) {
jdbcTemplate.setQueryTimeout((int) template.getQueryTimeout().getSeconds());
}
return jdbcTemplate;
}
}
我们可以到这个配置类中,springboot帮我们自动注册了JdbcTemplate这个bean,这也是为什么我们在引入spring-boot-starter-jdbc
的依赖后就可以直接使用JdbcTemplate,而不需要写一个配置类专门注册JdbcTemplate,那是因为springboot框架通过自动化配置的方式已经帮我们注册好了。然后我们再注意看JdbcTemplateAutoConfiguration配置类的上面有一个@ConditionalOnClass({ DataSource.class, JdbcTemplate.class })
, 它表示只有当项目中存在DataSource和JdbcTemplate这两个类时,这个自动化配置类才会生效。我们将@ConditionalXXX
这样的注解称为条件注解。
总而言之,springboot的自动化配置的过程是:
- 读取自动化配置的文件(具体的文件是org.springframework.boot.autoconfigure.AutoConfiguration.imports),从里面拿到所有的自动化配置类的路径信息。
- 然后根据路径找到这些自动化配置类进行处理,具体的处理方式是:如果当前项目符合自动配置类上的条件注解中的条件,就加载这个配置类,否则就过滤掉这个自动化配置类。
- 加载配置类后,根据配置类的bean配置将bean注册到spring容器中。
总而言之,springboot在实现自动化配置的过程中,@conditionalXXX
条件注解功不可没。
讲完了@Import(AutoConfigurationImportSelector.class)
的作用,下面我们来看下剩下的@AutoConfigurationPackage
注解的作用。
@AutoConfigurationPackage`注解的作用
咱们看下这个注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
可以看到里面有一个@Import(AutoConfigurationPackages.Registrar.class)
表示加载AutoConfigurationPackages.Registrar这个配置类,然后咱们看下这个配置类:
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
这里面的关键方法是:registerBeanDefinitions,咱们具体看下这个方法的逻辑:
可以看到这里其实就是在将主启动类所在的包下的所有class作为包扫描的起点。
然后再看下register方法,这里其实就是在将符合条件的bean的class信息放到beanDefinition中,用于实现后续的批量注册bean。
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
if (registry.containsBeanDefinition(BEAN)) {
BasePackagesBeanDefinition beanDefinition = (BasePackagesBeanDefinition) registry.getBeanDefinition(BEAN);
beanDefinition.addBasePackages(packageNames);
}
else {
registry.registerBeanDefinition(BEAN, new BasePackagesBeanDefinition(packageNames));
}
}
到目前为止,springboot是如何实现自动化配置的原理就带大家搞清楚了
排除自动化配置
为了检测上面的结论是否正确,咱们可以排除JdbcTemplate的自动化配置,看看排除这个配置后,是否还能正常使用JdbcTemplate
@SpringBootApplication(exclude = JdbcTemplateAutoConfiguration.class)
public class SpringbootAutoConfigApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootAutoConfigApplication.class, args);
}
}
可以看到报错了,日志提示说spring容器中没有JdbcTemplate这个bean
所以这里也验证了一点:那就是自动化配置类确实帮我们将JdbcTemplate注册到了spring容器中,这样就不需要我们手动再去写一个配置类注册JdbcTemplate了。
总而言之,springboot的自动化配置做的只有一件事:bean的自动注册,也就是springboot在启动的时候就帮我们将开发中会用到的一些bean放到spring的对象缓存池中。
文章中完整示例代码的地址
最后的总结
这篇文章中我们带大家从头到尾分析了springboot的自动化配置是如何实现的,并且还带大家通过排除JdbcTemplate的自动化配置类验证了自动配置类自动帮我们注册bean的结论。
觉得有收获的朋友可以点个赞,您的鼓励就是我最大的动力!