前言:
对于经常使用SpringBoot的程序员来说,SpringBoot大大简化了开发的工作量,节省了大量的配置工作,在SpringBoot启动的时候就自动将相关的配置给封装好了,也就是自动装配功能
通常搭建一个基于spring 的 web 应用,我们需要做以下工作:
1、pom 文件中引入相关jar包,包括 spring、springmvc、redis、mybaits、log4j、mysql-connector-java 等等相关jar …
2、配置web.xml,Listener配置、Filter配置、Servlet配置、log4j配置、error配置 …
3、配置数据库连接、配置spring事务
4、配置视图解析器
5、开启注解、自动扫描功能
6、配置完成后部署tomcat、启动调试
…
搭个初始项目不一会就一个小时甚至半天过去了。而用 springboot 后,一切都变得很简便快速。
1、如下所示引入依赖
mybatis-spring-boot-starter jar包不仅帮我们整合了 mybatis 使用的基础 jar包,而且多了一些 mybatis-antoconfigure jar包,这些jar包主要是一些配置类,比如:dataSource的配置,transactionManager的配置…,而不需要像spring那样我们自己去xml文件配置 dataSource 的 bean对象了,但对于dataSourece 一些配置的修改,则需要在application.yml配置,如下:
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mytest?characterEncoding=utf-8&useSSL=false
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
......
那配置类什么时候被加载,yml文件什么时候被加载,yml配置对配置类进行修改,再到根据配置类实例化所需的对象是怎么个过程,接下来我们进行源码分析
首先以debug方式运行我们的启动类:
public class MyApplicationContext {
public static void main(String[] args) {
SpringApplication.run(MyApplicationContext.class, args);
}
}
我们直接进入到SpringApplication.run()方法:
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[]{ConfigurableApplicationContext.class}, context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// 重点是这个方法,前面都是做一些初始化操作,这个方法是和spring的整合
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
} catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
} catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
一直debug到 AbstractApplicationContext.refresh()方法,该方法如下:
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
// 用于自动装配、处理 @ComponentScan 注解,生成BeanDefinition也加入到BeanFactory
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
} catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
} finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
从AbstractApplicationContext.invokeBeanFactoryPostProcessors(beanFactory)方法debug进去,如下:
BeanFactoryPostProcessor:工厂钩子,允许自定义修改应用上下文的bean definitions 属性;说通俗一些就是可以管理我们的bean工厂内所有的beandefinition(未实例化)数据,可以随心所欲的修改属性。
我们从PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());方法debug进去:
public static void invokeBeanFactoryPostProcessors(
ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
......
/**
*/
// 重点是这个类,执行BeanDefinitionRegistryPostProcessors
// 这里是 ConfigurationClassPostProcessor 实现了 BeanDefinitionRegistryPostProcessors
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
......
}
BeanDefinitionRegistryPostProcessor:它是个接口继承了 BeanFactoryPostProcessor ,允许在常规 BeanFactoryPostProcessor 检测开始之前,注册更多的bean定义进来,
ConfigurationClassPostProcessor:它实现了 BeanDefinitionRegistryPostProcessor 接口,用于处理自动装配类,它会解析会加了@Configuration的配置类,还会解析@ComponentScan、@ComponentScans注解扫描的包,以及解析@Import等注解。
进入ConfigurationClassPostProcessor.postProcessBeanDefinitionRegis方法:
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
int registryId = System.identityHashCode(registry);
if (this.registriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
}
if (this.factoriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against " + registry);
}
this.registriesPostProcessed.add(registryId);
// 看名字也知道是这个方法 跟进去
processConfigBeanDefinitions(registry);
}
进入ConfigurationClassPostProcessor.processConfigBeanDefinitions方法如下:
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
......
// 这个方法来对主类上的注解进行解析
parser.parse(candidates);
......
}
进入ConfigurationClassParser.pase方法如下:
debug进入ConfigurationClassParser.processConfigurationClass方法:
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
// 判断是否有@Conditional注解
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
// 这里existingClass == null 直接跳过
ConfigurationClass existingClass = this.configurationClasses.get(configClass);
if (existingClass != null) {
if (configClass.isImported()) {
if (existingClass.isImported()) {
existingClass.mergeImportedBy(configClass);
}
// Otherwise ignore new imported config class; existing non-imported class overrides it.
return;
}
else {
// Explicit bean definition found, probably replacing an import.
// Let's remove the old one and go with the new one.
this.configurationClasses.remove(configClass);
this.knownSuperclasses.values().removeIf(configClass::equals);
}
}
// Recursively process the configuration class and its superclass hierarchy.
SourceClass sourceClass = asSourceClass(configClass);
do {
/**
通过读取源类的 注解、成员、方法来处理构建一个完成的配置
类。这个方法可以被多次调用,用来处理源类,
这里我们只需要关系我的们 启动类(它也是个源类),
还有什么类可以当作源类,被@Componet修饰的类也会当作源类进行解析。
*/
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
}
while (sourceClass != null);
this.configurationClasses.put(configClass, configClass);
}
debug进入ConfigurationClassParser.doProcessConfigurationClass这个方法:
@Nullable
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
throws IOException {
// Recursively process any member (nested) classes first
// 这个方法直接跳过
processMemberClasses(configClass, sourceClass);
// Process any @PropertySource annotations
// 没有@PropertySource注解,也跳过
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}
// Process any @ComponentScan annotations
// 处理 @ComponentScan 注解,也不是本文重点,我们跳过
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if needed
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
// 这里会把每个扫描出来的类当成 Source类进行解析
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
// Process any @Import annotations
// 重点来了,处理启动类上 @Import的注解
processImports(configClass, sourceClass, getImports(sourceClass), true);
......
}
ConfigurationClassParser.getImport(sourceClass)方法解析如下,有兴趣的朋友可以自己去看里面怎么解析的(递归)
我们验证下 启动类是否有3个@Import注解对应这三个类:
2、由@SpringBootApplication注解→@EnableAutoConfiguration
3、由@SpringBootApplication注解→@EnableAutoConfiguration→@AutoConfigurationPackage
这里加载了3个@Import上的类完全对应上了,我们接下来看,这些类是干什么用的,跟自动装配有啥关系?
debug进入ConfigurationClassParser.processImports(configClass, sourceClass, getImports(sourceClass), true)方法一探究竟
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
//这里不为空 跳过
if (importCandidates.isEmpty()) {
return;
}
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {
// 直接到这里,遍历3个@import对应的实体类
if (candidate.isAssignable(ImportSelector.class)) {
// AutoConfigurationImportSelector 这个类进入这里
// Candidate class is an ImportSelector -> delegate to it to determine imports
......业务逻辑
}
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// AutoConfigurationPackages.Registrar这个类进入这里
// Candidate class is an ImportBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
......业务逻辑
}
else {
// 其他类进入这里
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
......业务逻辑
}
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]", ex);
}
finally {
this.importStack.pop();
}
}
}
1、我们按顺序 先看 AutoConfigurationPackages.Registrar的处理逻辑:
// 获取AutoConfigurationPackages.Registrar的class对象
Class<?> candidateClass = candidate.loadClass();
// 实例化AutoConfigurationPackages.Registrar对象 里面就是通过反射生成
ImportBeanDefinitionRegistrar registrar =
BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
// 这里会判断 AutoConfigurationPackages.Registrar instanceof Aware,这里不是,跳过
ParserStrategyUtils.invokeAwareMethods(
registrar, this.environment, this.resourceLoader, this.registry);
// 把AutoConfigurationPackages.Registrar添加到 ConfigurationClass的importBeanDefinitionRegistrars(Map)
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
2、接下来我们看 AutoConfigurationImportSelector这个类的处理逻辑:这里也并未调用,只是做了封装处理
// 获取AutoConfigurationImportSelector的class对象
Class<?> candidateClass = candidate.loadClass();
// 一样的逻辑,实例化AutoConfigurationImportSelector对象
ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
// 这里判断AutoConfigurationImportSelector instanceof Aware 为true,然后为这里判断AutoConfigurationImportSelector设置一些属性:类加载器、BeanFactory、environment、resourceLoader
ParserStrategyUtils.invokeAwareMethods(
selector, this.environment, this.resourceLoader, this.registry);
if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
// 进入这里,new一个 ConfigurationClassParser.DeferredImportSelectorHolder对象添加到deferredImportSelectors(List)
this.deferredImportSelectors.add(
new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
} else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
解析完,我们debug返回到ConfigurationClassParser.parse(Set
我们debug跟进去:
ConfigurationClassParser.getImports方法如下:
进入AutoConfigurationImportSelector.process方法:
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
// 跟进这个方法 我们看做了什么
String[] imports = deferredImportSelector.selectImports(annotationMetadata);
String[] var4 = imports;
int var5 = imports.length;
for(int var6 = 0; var6 < var5; ++var6) {
String importClassName = var4[var6];
this.entries.put(importClassName, annotationMetadata);
}
}
debug进入AutoConfigurationImportSelector.selectImports方法,重点逻辑都在这里面,这是自动装配的又一关键所在:
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
// 1.加载自动装配元数据,哪些数据? 我们跟进去看下
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// 2.以EnableAutoConfiguration为key在spring.factories文件中 查询出所有的实现类是自动装配类的全集,封装成List集合
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
// 去重 跳过
configurations = this.removeDuplicates(configurations);
// 生成exclusions 也就是pom.xml中 需要排查的
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
// 对configurations配置 做排除检测
this.checkExcludedClasses(configurations, exclusions);
// configurations配置 移除 排除项
configurations.removeAll(exclusions);
// 3、这里对配置类做过滤,不需要用的配置类我们 不进行后面实例化
configurations = this.filter(configurations, autoConfigurationMetadata);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return StringUtils.toStringArray(configurations);
}
}
这里会加载META-INF/spring-autoconfigure-metadata.properties下的数据。META-INF/spring-autoconfigure-metadata.properties的用处是啥? 起码看里面数据结构我们不难发现,是key-value映射形式,
每个配置类可能有4种组合方式,如下:
整个文件下有2个这样的文件
mybatis的总共8个
springboot 561个 加起来 569个
验证:刚好569个 配置类映射关系
configurations总共返回srpingboot的110 + mybatis的2个 = 112个
这里有个问题,spring.factories下的全集自动装配类,为啥要过滤?
如果springboot单单加载spring.factories文件下全部配置类,那么会在启动的时候报很多类找不到的错误,因为我们并没有引入全部整合的第三方jar包。
那么配置类是根据什么来过滤的呢?
上文我们介绍到 META-INF/spring-autoconfigure-metadata.properties 文件下有种 key-配置类全限定类名.ConditionalOnClass 的数据格式,而value正是配置类需要的第三方jar包类,如果没导入这个jar包,而去加载这个类,那么肯定会报类找不到异常,而springboot正是基于这种方式判断的。
拿到了配置类 需要依赖的第三方jar包的全限定类名,你怎么去判断是否有这些类,也就是是否导入了这些依赖呢?其实很简单:
灵魂方法,通过class.forName(全限定类名),如果抛出异常,则说明没有导入这个类的jar包,也说明这个配置类不需要加载。
至此,自动装配需要的配置类BeanDefinition也被加入到BeanFactory,等待被实例化。