目录
1 项目工程类信息注册
1.1 @Component形式
1.2 @ComponentScan形式
1.3 @Import形式
1.4?@ImportResource形式
1.5 beanMethods方式
1.6 解析接口中的beanMethods方式
1.7 递归解析当前类的父类
2 Spring自动装配原理
3 小结
对Springboot有所了解的读者,对一些理念想必很熟悉,如Springboot工程会默认加载当前主启动类下的所有类文件到IOC容器,以及Springboot可以通过自动装配的功能,实现对外部功能的无缝引入。
怎么说呢?大的方向是这样,但是如果更近一步,要问一下这些功能具体是如何实现的,只有通过源码才能完整而清晰的了解其每一步的实现意图。
下面我们结合Springboot的启动过程,来直接进入源码,再现Springboot的依托主启动类扫包和自动装配原理。
首先,通过主启动类的main方法进入SpringApplication的run方法:
public static void main(String[] args) {
SpringApplication.run(new Class[]{BookstoreApplication.class}, args);
}
之后再通过如下顺序:
this.refreshContext(context); (SpringApplication) --->>>
this.refresh(context); (SpringApplication) --->>>
refresh();(AbstractApplicationContext) --->>>
this.invokeBeanFactoryPostProcessors(beanFactory);(AbstractApplicationContext)
最终进入类PostProcessorRegistrationDelegate中的BeanFactory后置处理器执行方法invokeBeanFactoryPostProcessors。
在此方法中,第一次执行方法
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
时,会执行一个特殊的BeanDefinitionRegistry后置处理器——ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法。
在此方法中会通过
this.processConfigBeanDefinitions(registry);
下的
parser.parse(candidates);
真正进入主启动类下项目工程类信息注册,以及Spring自动装配功能。
说了这么多,其实是为了让读者可以通过断点,启动任意一个Springboot项目,自己去断点调试一下,验证笔者所说,毕竟眼见为实,耳听为虚,实践才是最好的老师。
有兴趣的读者,可以直接进入ConfigurationClassPostProcessor类的processConfigBeanDefinitions方法,再进入parser.parse(candidates),就可以直接而快速的了解接下来笔者要讲解的源码。
1 项目工程类信息注册
关于bean信息注册的方法,基本都在ConfigurationClassParser这个类中,processConfigBeanDefinitions实现bean信息注册的入口方法在ConfigurationClassParser的parse方法中:
public void parse(Set<BeanDefinitionHolder> configCandidates) {
//主启动类bean信息集合
Iterator var2 = configCandidates.iterator();
while(var2.hasNext()) {
BeanDefinitionHolder holder = (BeanDefinitionHolder)var2.next();
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
//注册项目工程类信息
this.parse(((AnnotatedBeanDefinition)bd).getMetadata(), holder.getBeanName());
.......
}
//注册Spring提供默认类信息
this.deferredImportSelectorHandler.process();
}
this.parse方法主要就是依据其传入的参数,也就是主启动类的bean定义信息BeanDefinition和BeanDefinitionHolder构造一个新的对象ConfigurationClass,再以ConfigurationClass和DEFAULT_EXCLUSION_FILTER,作为参数进入真正的执行方法processConfigurationClass。
private static final Predicate<String> DEFAULT_EXCLUSION_FILTER = (className) -> {
return className.startsWith("java.lang.annotation.") || className.startsWith("org.springframework.stereotype.");
};
进入processConfigurationClass后,首先会判断当前当前主启动类ConfigurationClass是否需要被拦截,主要是根据@Conditional注解和DEFAULT_EXCLUSION_FILTER的信息来判断:
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
if (!this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
在执行完过滤条件后,再以ConfigurationClass和DEFAULT_EXCLUSION_FILTER参数构造一个参数SourceClass,其是ConfigurationClassParser的一个内部类,最终把ConfigurationClass、SourceClass和DEFAULT_EXCLUSION_FILTER三个作为参数,来执行下一步的类信息解析:
do {
sourceClass = this.doProcessConfigurationClass(configClass, sourceClass, filter);
} while(sourceClass != null);
?有人看到这一步就会很奇怪,configClass和sourceClass,这两参数基本都是一样,只不过另一个又包装了一层,这样构造的意义在哪里呢?其原因主要也是为了框架的需要,这样可以利用递归,把主启动类包下的每一个已经加入IOC容器的类都当作是一个配置类,可以任意向下扩展。
它可以在自身已经被@Component或其他注解形式修饰,在已经加入IOC容器的前提下,通过@ComponentScan注解、@Import注解或者@ImportResource注解,来给IOC容器新增我们需要的指定类。
下面通过doProcessConfigurationClass方法分别介绍:
protected final ConfigurationClassParser.SourceClass doProcessConfigurationClass(ConfigurationClass configClass, ConfigurationClassParser.SourceClass sourceClass, Predicate<String> filter) throws IOException {
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
this.processMemberClasses(configClass, sourceClass, filter);
}
在介绍这个方法之前,我们先说一个前提,只要满足条件的基础上,方法正常执行,会返回一个configClass,被缓存到当前类的一个Map属性configurationClasses中,后面会被构造成bean的定义信息被注册到IOC容器。
在此基础上,我们会对当前类,有可能出现的扩展情况,进行处理,通俗说,就是在当前类配置了一些属性,需要把新的类信息注入IOC容器。
1.1 @Component形式
如果当前类带有@Component注解,我们会对其内部类进行解析,看其内部类是否配置了@Component注解,需要被注入IOC容器:
private void processMemberClasses(ConfigurationClass configClass, ConfigurationClassParser.SourceClass sourceClass, Predicate<String> filter) throws IOException {
//获取内部类
Collection<ConfigurationClassParser.SourceClass> memberClasses = sourceClass.getMemberClasses();
//如果存在内部类
if (!memberClasses.isEmpty()) {
List<ConfigurationClassParser.SourceClass> candidates = new ArrayList(memberClasses.size());
Iterator var6 = memberClasses.iterator();
ConfigurationClassParser.SourceClass candidate;
while(var6.hasNext()) {
candidate = (ConfigurationClassParser.SourceClass)var6.next();
//内部类是否需要被注入IOC容器
if (ConfigurationClassUtils.isConfigurationCandidate(candidate.getMetadata()) && !candidate.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) {
//满足条件注入
candidates.add(candidate);
......
//递归方法,最终会再调用doProcessConfigurationClass
this.processConfigurationClass(candidate.asConfigClass(configClass), filter);
.......
实操用法如下:
@SpringBootApplication
public class BookstoreApplication {
public static void main(String[] args) {
SpringApplication.run(new Class[]{BookstoreApplication.class, TestApp.class}, args);
}
@Component
class TestClass{
private final String name = "123";
TestClass(){
System.out.println(name);
}
}
}
如上,会通过方法processMemberClasses把TestClass信息注入IOC。
1.2 @ComponentScan形式
因为第一次进入doProcessConfigurationClass方法,传入的就是已经加入容器内的主启动类,所以会解析主启动类的注解@ComponentScan,大致和前文介绍的一样,如果@ComponentScan的basePackages和basePackageClasses属性都没有定义的话,就加载主启动类当前路径下各级文件夹中所有的class:
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
Iterator var14 = componentScans.iterator();
while(var14.hasNext()) {
AnnotationAttributes componentScan = (AnnotationAttributes)var14.next();
Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
下一步需要进行判断,这些加载的class是否满足立即被加载进容器的条件,简单来说就是类上是否包含四个注解:
[org.springframework.context.annotation.Import, org.springframework.stereotype.Component, org.springframework.context.annotation.ImportResource, org.springframework.context.annotation.ComponentScan]
如果含有其中之一, 则使用递归调用的方式,重新调用ConfigurationClassParser类的parser方法:
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
this.parse(bdCand.getBeanClassName(), holder.getBeanName());
}
以这样的方式,把加入容器中的每一个类都当作了潜在的配置类,可以灵活的引用没有配置进容器的类。
实操示例如下:
@Configuration
@ComponentScan("test")
public class TestConfiguration {
}
当前类在com包下,被扫描进IOC容器,因为使用了@ComponentScan注解,可以加载外部包test下,标注了@Component的类:
package test;
import org.springframework.stereotype.Component;
@Component
public class TestComponentScan{
TestComponentScan(){
System.out.println("test----TestComponentScan if exist in IOC");
}
}
1.3 @Import形式
this.processImports(configClass, sourceClass, this.getImports(sourceClass), filter, true);
把当前类中注解了@Import的类信息注册到容器中,注意这里有一种特殊情形需要注意,如果式主启动类,由于它通过@EnableAutoConfiguration复合注解,组合了@Import({AutoConfigurationImportSelector.class})注解,相当于要加载一些Spring自带的需要加入容器的类,这就属于特殊情形。
Spring框架使AutoConfigurationImportSelector实现了一个特殊接口DeferredImportSelector,把它归属到特殊的一类Import,等当前工程项目中所有的类注册完成后再继续加载:
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector)selector);
}
此时,其他的普通类通过@Import注解依然通过递归调用的方式,可以对@Import其他引入的类进行继续解析:?
this.processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
1.4?@ImportResource形式
根据注解信息,找到Spring的配置文件路径,之后加载配置信息,把配置的bean信息注册到IOC容器:
importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
具体操作示例如下:
@SpringBootApplication
@ImportResource(locations={"classpath:beans.xml"})
public class BookstoreApplication {
public static void main(String[] args) {
SpringApplication.run(BookstoreApplication.class, args);
?beans.xml具体信息如下,注册一个不在当前主启动类的bean:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--将 HelloService 以xml的方式,注入到容器中-->
<bean id="helloService" class="test.HelloService"></bean>
</beans>
?在项目启动后,HelloService就可以直接从容器中获取:
public class HelloService {
HelloService(){
System.out.println("基于@ImportResource注解的bean--HelloService注入");
}
}
1.5 beanMethods方式
在实际的项目中,还有一种情况用的比较多,比如当前类标注了@Configuration,就代表是一个配置类,之后在方法中可以用@Bean注解的方式来把返回值注入到容器中。
Set<MethodMetadata> beanMethods = this.retrieveBeanMethodMetadata(sourceClass);
Iterator var18 = beanMethods.iterator();
while(var18.hasNext()) {
MethodMetadata methodMetadata = (MethodMetadata)var18.next();
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
具体用法如下:
@Configuration
public class TestComponentScan{
@Bean
public TestBeanMethod testBeanMethod(){
return new TestBeanMethod();
}
}
1.6 解析接口中的beanMethods方式
主要是针对当前类已经实现的接口中,其方法上,是否有需要注入IOC容器中的类:
this.processInterfaces(configClass, sourceClass);
示例如下:
public interface InterfaceService {
@Bean
default Object interfaceBean(){
return new Object();
}
}
@Component
public class CustomApplicationListener implements InterfaceService {
}
?CustomApplicationListener ?会在自身注入容器的同时,把InterfaceService中注入了@Bean方法interfaceBean也加入进容器。
其代码实现为:
private void processInterfaces(ConfigurationClass configClass, ConfigurationClassParser.SourceClass sourceClass) throws IOException {
Iterator var3 = sourceClass.getInterfaces().iterator();
while(var3.hasNext()) {
ConfigurationClassParser.SourceClass ifc = (ConfigurationClassParser.SourceClass)var3.next();
Set<MethodMetadata> beanMethods = this.retrieveBeanMethodMetadata(ifc);
Iterator var6 = beanMethods.iterator();
while(var6.hasNext()) {
MethodMetadata methodMetadata = (MethodMetadata)var6.next();
if (!methodMetadata.isAbstract()) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
}
this.processInterfaces(configClass, ifc);
}
}
这些beanMethods的bean定义信息会依附于当前类bean信息而存在,具体表现为存在ConfigurationClass类的Set属性beanMethods中,在需要的时候解析出来。
1.7 递归解析当前类的父类
现在到了最后一步,首先判断这个类是否还有父类,如果没有,直接返回空,如果有,获取父类后,继续判断,如果这个父类不为空、包名开头不是java且不能是已经被解析的父类,否则返回空:
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if (superclass != null && !superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
return sourceClass.getSuperClass();
}
}
return null;
取得返回值后,跳出循环,把当前ConfigurationClass返回,缓存到Map中:
do {
sourceClass = this.doProcessConfigurationClass(configClass, sourceClass, filter);
} while(sourceClass != null);
this.configurationClasses.put(configClass, configClass);
经过ConfigurationClassParser类中的parse方法调用AnnotatedBeanDefinition类型的parse方法,我们完成了对当前项目工程下所有类,注册到IOC容器中的操作。
2 Spring自动装配原理
下面需要继续介绍,Spring自带的框架类,如何加入容器。在ConfigurationClassParser类中的执行方法为:
this.deferredImportSelectorHandler.process();
首先会获取到前面我们通过主启动类上的@Import注解导入的自动装配类信息,之后新建一个Group用来对这一类信息归类,把@Import中的信息导入到Group,之后对Group中的信息进行处理:
public void process() {
List<ConfigurationClassParser.DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
this.deferredImportSelectors = null;
try {
if (deferredImports != null) {
......
deferredImports.forEach(handler::register);
handler.processGroupImports();
}
} finally {
this.deferredImportSelectors = new ArrayList();
}
}
public void processGroupImports() {
Iterator var1 = this.groupings.values().iterator();
while(var1.hasNext()) {
ConfigurationClassParser.DeferredImportSelectorGrouping grouping = (ConfigurationClassParser.DeferredImportSelectorGrouping)var1.next();
Predicate<String> exclusionFilter = grouping.getCandidateFilter();
grouping.getImports().forEach((entry) -> {
ConfigurationClass configurationClass = (ConfigurationClass)this.configurationClasses.get(entry.getMetadata());
最后通过grouping.getImports()方法来调用主启动类导入的类AutoConfigurationImportSelector中的process方法,来进行自动装配:
public Iterable<Entry> getImports() {
Iterator var1 = this.deferredImports.iterator();
while(var1.hasNext()) {
ConfigurationClassParser.DeferredImportSelectorHolder deferredImport = (ConfigurationClassParser.DeferredImportSelectorHolder)var1.next();
this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector());
}
进入AutoConfigurationImportSelector:
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> {
return String.format("Only %s implementations are supported, got %s", AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName());
});
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(this.getAutoConfigurationMetadata(), annotationMetadata);
获取配置实体:?
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
?执行扫描方法:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}
?通过this.getSpringFactoriesLoaderFactoryClass()方法扫描META-INF/spring.factories中key为EnableAutoConfiguration的全类名:
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
Spring框架的示例配置为:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
?以上就是Spring自动装配的全部内容。
到了这一步,我们才讲完了把所有需要注册进容器的类缓存到一个Map集合,下一步就是取到Map集合的值,然后把值信息作为bean的定义信息,真正注册到容器中:
Set<ConfigurationClass> configClasses = new LinkedHashSet(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry());
}
this.reader.loadBeanDefinitions(configClasses);
这样,就完成了ConfigurationClassPostProcessor类中,第二次BeanDefinitionRegistry后置处理器中指定方法的执行:
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
currentRegistryProcessors.clear();
执行完成后,清空currentRegistryProcessors,为下一次执行BeanDefinitionRegistry后置处理器方法做准备。
3 小结
想必很多读者对主启动类下包扫描,和Springboot自动装配原理都不陌生,这里只是给大家提供了一个源码级别的验证,让大家能够更清晰而完整的了解Springboot的一些原理。本文也结合了一些具体示例,希望对大家有所帮助。由于笔者水平有限,有不足之处,也望大家多多指教、包涵!
|