Spring 配置类解析过程详解
- 配置类解析概要
- ConfigurationClassPostProcessor解析配置类过程
- 回到外层的processConfigBeanDefinitions
- 配置类解析流程图
配置类解析概要
Spring中配置类解析过程算是spring IOC开始的地方吧,Spring将所有符合条件的配置类解析成一个一个的BeanDefinition,然后放入容器中,后面根据BeanDefinition的一些属性决定是否创建实例放入单例池,Spring IOC你可以从宏观的角度去看,就是将所有符合条件的class扫描成Bean的定义,放入缓存中,然后根据这些Bean定义是否满足放入单例池的条件而去创建单例池,如果不是单例的,那么每次实例化都会从Bean定义缓存中取出相应的BeanDefinition,然后创建对象;Spring对配置类的解析过程是通过spring提供的扩展功能去实现的,也就是BeanFactory的后置处理器去处理的,上一篇笔记已经介绍了BeanFactory的后置处理器是如何使用,并且它的源码分析,BeanFactory的后置处理器分为BeanFactoryPostProcessor和子接口BeanDefinitionRegistryPostProcessor,其中BeanDefinitionRegistryPostProcessor提供了BeanDefinition的注册等功能,而父类的接口提供的方法只能对Bean工厂的Bean进行操作,更详细就不介绍了,我们这里主要来分析下spring是如何提高BeanFactory的后置处理器去处理我们的配置类的,spring通过配置类解析过程将符合条件的bean放入容器过后就完成了扫码功能,在这个过程中主要处理了@Component、@Import、@ImportResource、@ComponScan、@Bean等注解的信息,将符合条件的class注册成为一个BeanDefinition;其中@Import是可以将一个普通的类注册成为一个BeanDefinition,@Import提供了三种注册类型:
1.第一种为将普通的类注册为一个BeanDefinition;
2.第二种是将实现了ImportSelector接口,调用接口回调方法可以返回一个数组,数组就是要注册成为BeanDefinition的类的全限定名;
3.第三种是实现了ImportBeanDefinitionRegistrar,这种可以在解析配置类的时候进行调用它接口中的回调方法,实现BeanDefinition的手动注册和改变,比如你想将接口注册成为BeanDefinition,那么可以实现这个接口,然后通过@Import进行导入即可,Mybatis就是通过实现了这个接口将自己的Mapper扫描出来生成代理接口,然后注册到spring从而和spring进行无缝整合。
看下面的@Import的例子:
先定义几个普通的类,待会要将这个几个普通类注册为BeanDefinition,不通过@Component方式
public class A1 {
}
public class A2 {
}
public class MyImportRegistry implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
System.out.println("我实现了ImportBeanDefinitionRegistrar");
}
}
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{A1.class.getName()};
}
}
配置类
@ComponentScan("com.xxx")
@Import({A2.class, MyImportRegistry.class, MyImportSelector.class})
public class AppConfig {
@Bean
public UserService userService(){
System.out.println(111);
return new UserService();
}
}
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
ac.register(AppConfig.class);
ac.refresh();
System.out.println(ac.getBean(A1.class));
}
通过Debug我们可以看到生成的BeanDefinition如下:
看上面的结果可以知道如果是@Import导入的普通类,bean的名字就是普通类的全限定名,这个待会儿从源码中去分析就可以知道;
再来看下@ImportResouce,@ImportResouce是可以将一个spring的xml文件导入生成BeanDefinition;
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context-2.5.xsd">
<bean name="a1" class="com.xxx.imports.A1" />
<bean name="a2" class="com.xxx.imports.A2" />
</beans>
@ComponentScan("com.xxx")
@ImportResource("classpath:spring.xml")
public class AppConfig {
@Bean
public UserService userService(){
System.out.println(111);
return new UserService();
}
}
实现起来都是非常方便的,都是在解析配置类的流程中完成的;我不知道在实际的工作之中会不会又小伙伴这样使用,反正我是很少这样用,也有这样用的,比如一个项目在迭代过程中,先是使用的xml的方式,后面启用了注解的方式,但是之前的代码一点儿也不想动,哪怕是简单加一个注解也不想动,就可以通过这种方式进行整合。
还有@Bean的方式可能大家用的非常多,也就是将一个方法的返回值作为一个Bean注册到容器中,但是 这里有个简单的知识可能大家都不知道,其实在前面的笔记中分析bean的生命周期的过程中已经说过了,就是@Bean中有很多默认的参数,你如果只定义了一个@Bean,那么其实有很多的默认参数,比如默认的销毁方法,如果你没有定义,那么只要在你的bean中有close或者shutdown方法,那么spring容器在关闭的时候会去自动调用的,我们先看下@Bean的基本信息:
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
/**
* Alias for {@link #name}.
* <p>Intended to be used when no other attributes are needed, for example:
* {@code @Bean("customBeanName")}.
* @since 4.3.3
* @see #name
*/
@AliasFor("name")
String[] value() default {};
/**
* The name of this bean, or if several names, a primary bean name plus aliases.
* <p>If left unspecified, the name of the bean is the name of the annotated method.
* If specified, the method name is ignored.
* <p>The bean name and aliases may also be configured via the {@link #value}
* attribute if no other attributes are declared.
* @see #value
*/
@AliasFor("value")
String[] name() default {};
/**
* Are dependencies to be injected via convention-based autowiring by name or type?
* <p>Note that this autowire mode is just about externally driven autowiring based
* on bean property setter methods by convention, analogous to XML bean definitions.
* <p>The default mode does allow for annotation-driven autowiring. "no" refers to
* externally driven autowiring only, not affecting any autowiring demands that the
* bean class itself expresses through annotations.
* @see Autowire#BY_NAME
* @see Autowire#BY_TYPE
* @deprecated as of 5.1, since {@code @Bean} factory method argument resolution and
* {@code @Autowired} processing supersede name/type-based bean property injection
*/
@Deprecated
Autowire autowire() default Autowire.NO;
/**
* Is this bean a candidate for getting autowired into some other bean?
* <p>Default is {@code true}; set this to {@code false} for internal delegates
* that are not meant to get in the way of beans of the same type in other places.
* @since 5.1
*/
boolean autowireCandidate() default true;
/**
* The optional name of a method to call on the bean instance during initialization.
* Not commonly used, given that the method may be called programmatically directly
* within the body of a Bean-annotated method.
* <p>The default value is {@code ""}, indicating no init method to be called.
* @see org.springframework.beans.factory.InitializingBean
* @see org.springframework.context.ConfigurableApplicationContext#refresh()
*/
String initMethod() default "";
/**
* The optional name of a method to call on the bean instance upon closing the
* application context, for example a {@code close()} method on a JDBC
* {@code DataSource} implementation, or a Hibernate {@code SessionFactory} object.
* The method must have no arguments but may throw any exception.
* <p>As a convenience to the user, the container will attempt to infer a destroy
* method against an object returned from the {@code @Bean} method. For example, given
* an {@code @Bean} method returning an Apache Commons DBCP {@code BasicDataSource},
* the container will notice the {@code close()} method available on that object and
* automatically register it as the {@code destroyMethod}. This 'destroy method
* inference' is currently limited to detecting only public, no-arg methods named
* 'close' or 'shutdown'. The method may be declared at any level of the inheritance
* hierarchy and will be detected regardless of the return type of the {@code @Bean}
* method (i.e., detection occurs reflectively against the bean instance itself at
* creation time).
* <p>To disable destroy method inference for a particular {@code @Bean}, specify an
* empty string as the value, e.g. {@code @Bean(destroyMethod="")}. Note that the
* {@link org.springframework.beans.factory.DisposableBean} callback interface will
* nevertheless get detected and the corresponding destroy method invoked: In other
* words, {@code destroyMethod=""} only affects custom close/shutdown methods and
* {@link java.io.Closeable}/{@link java.lang.AutoCloseable} declared close methods.
* <p>Note: Only invoked on beans whose lifecycle is under the full control of the
* factory, which is always the case for singletons but not guaranteed for any
* other scope.
* @see org.springframework.beans.factory.DisposableBean
* @see org.springframework.context.ConfigurableApplicationContext#close()
*/
String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
autowire():默认注入方式为NO,也就是根据ByType,。。。。再byName;
autowireCandidate():是否是一个自动注入的候选者,默认true;
destroyMethod():销毁方法,默认是(inferred),当你的bean中如果存在close或者shutdown方法,那么会调用,而且只能调用其中的一个,比如看下面的例子:
@Bean
public A1 a1(){
return new A1();
}
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
ac.register(AppConfig.class);
ac.refresh();
System.out.println(ac.getBean(A1.class));
ac.close();
}
public class A1 {
public void close(){
System.out.println("close.....");
}
public void shutdown(){
System.out.println("shutdown....");
}
}
所以这就是@Bean一些通过源码可以得到的细小知识点,如果同时定义了close和shutdown方法,默认是调用close,如果没有close,就会去找shutdown方法,这个知识点知道即可;还有一个知识点就是@Bean默认会将方法的名字作为Bean,但是如果我们定义了Bean的名字呢,比如这样:@Bean(name=“123”)或者@Bean(value = {“123”,“456”,“678”})呢,所以这里又有一个隐藏的小知识点,我们知道spring中beanName只有一个,但是别名可以有多个,在spring的生命周期我们已经分析过了,所以上面的两种定义方式@Bean(name=“123”),那么bean的名字就是123,如果是@Bean(value = {“123”,“456”,“678”}),那么是将第一个作为bean的名字,而后面的都作为bean的别名,我们先来看下效果:
@Bean(value = {"123","456","678"})
public A1 a1(){
return new A1();
}
第二张图就是别名的缓存,而这个在源码里面是如何处理的,我们先看下:
这个源码后面会详细分析,这里简单看下,就是在这里处理的,所以通过源码得到一些知识点就是最专业的。
ConfigurationClassPostProcessor解析配置类过程
上面已经把配置类解析过程中遇到了一些关键点通过栗子来分析了,下面我们就开始分析源码了,这个配置类解析的源码实在有点绕,反正就是记录一下,万一后面忘记了过来翻翻。
接上一篇笔记中beanFactoryPostPorcessor中的解析配置类的过程,上一篇只是对是如何调用beanfactory的后置处理器和顺序来详细说明了,这篇笔记就主要对配置类的解析进行详细的分析,入口在这里(refresh()->invokeBeanFactoryPostProcessors->invokeBeanFactoryPostProcessors):
invokeBeanDefinitionRegistryPostProcessors
/**
* Invoke the given BeanDefinitionRegistryPostProcessor beans.
* 这里是调用BeanFactory工厂的后置处理器,包括系统已经添加进去的和程序员自己定义的bean工厂后置处理器
* 这个后置处理器是BeanDefinition注册的后置处理器
* 在spring中,这个方法里面有个比较重要的后置处理器ConfigurationClassPostProcessor就是在这里调用的
* 这个后置处理器主要处理我们的配置类,比如启动注册的配置类Appconfig,那么这里会获取这个配置类中的
* 配置的扫描类路信息,包括加了@Component、@ComponScan、@Import @ImportResource等注解都会被认为是配置类
* 然后进行扫描注册成BeanDefinition
*/
private static void invokeBeanDefinitionRegistryPostProcessors(
Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) {
//循环调用Bean定义注册后置处理器方法
for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessBeanDefinitionRegistry(registry);
}
}
在上面的代码中循环调用 postProcessor.postProcessBeanDefinitionRegistry解析配置类,调用的就是ConfigurationClassPostProcessor这个后置处理器,所以接下来我们就详细分析下这个后置处理器中的方法。
postProcessBeanDefinitionRegistry
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
//这里生成一个注册IDregistryId,为了防止同一个注册器多次调用,SET集合本身具有去重功能,所以用SET存放
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);
/**
* 这个方法是配置类处理的核心所在,它做的时期:
* 1.处理@Component注解;
* 2.处理@ComponentScan注解;
* 3.处理@PropertySource
* 4.处理@Import注解;
* 5.处理@ImportResouce注解
*
* 简单来说就是根据我们的配置类找到所有符合条件的class对象,然后生成BeanDefinition,放入beanDefinitionMap中
*/
processConfigBeanDefinitions(registry);
}
processConfigBeanDefinitions
/**
*
* 这个方法特别重要,首先:
* 1.spring先将我们加了注册的配置类从bdmap中拿出来获取bd,放入configCandidates中
* 2.然后循环解析配置类,从配置类中获取@ComponentScan中配置的扫描路径
* 3.根据路径spring通过asm技术扫描我们的类,扫描成功的放入bdmap中
* 4.处理@import注解的相关实现
* 4.*****(这个概念非常重要,一定要理解)这个方法还有个非常重要的概念就是,我们的配置类上是否有加@Configuration(这个注解的作用不言而喻,可以说非常重要,
*简单明了就是如果加了@Configuration,那么它就是一个全注解FULL,没有加就是一个普通的组件LITE
* 而区别就是加了@Configuration注解的,spring会帮我们生成一个代理类,是一个CGLIB的代理类,没有加@Configuration,
* 就不会生成一个CGLIB的代理类,那么有什么区别??
* 区别:如果是由@Configuration,那么通常我们的Appconfig配置类中会有一些方法,将普通的类返回为一个Bean,也就是加了一个@Bean
* 如果我们在配置类中调用本配置类的@Bean方法,就是一个本类的普通方法调用,如果加了@Configuration,那么spring会冲容器中给我们
* 拿出这个对象出来,也就是说这个对象的产生只有一次,说白了,加了@Configuration,那么spring就会为我们这个配置类创建一个factoryBean
* 我们的拿的对象都是从工厂bean中取的,这个工厂bean是由cglib代理的。加不加@configuraation的最重要区别就是加了注解我们拿对象从工厂中拿
* 而不加,拿几次就创建几次
*
*
*
*
* *****这个方法特别复杂,太多循环,夹杂着递归,有时候看的脑壳涨
* 1.首先在容器中获取所有的BeanDefinition名字集合;
* 2.循环这个集合,找出符合条件的也就是属于配置类的BeanDefinition名字;
* 3.然后调用pase方法进行解析,解析的时候通过循环进行处理,一个配置类一个配置类的进行处理
* 4.每一个配置类的工作流程是:
* a.判断是否加了@Componet注解,如果加了,处理@Component所在类的内部类,因为内部类可能有其他注解,然后这里是一个递归处理;
* b.判断是否有@ProperResource注解,如果有,处理@ProperResource,将配置的资源文件解析出来放入到Environment中;
* c.处理@ComponScan注解,执行真正的扫描工作,将符合条件的创建ConfigurationClass;
* d.处理@Import注解,@Import注解有三种类型,普通类的导入,ImportSelector,ImportBeanDefinitionRegistrar;
* e.处理@ImportResource注解,@ImportResouce注解一般导入的是spring的配置文件,配置的是<Bean />;
* f.处理每个配置类的@Bean方法
* 在上面的c、d中都可能存在递归的处理,首先c中扫描出来的BeanDefinition然后递归处理@Coponent所在类,看是否有a b c d e f的情况产生
* 简单来说就是递归给你把所有的配置类都给解析的干干净净;
* d中主要是@Import的注解的递归,因为有三种类型,只有ImportBeanDefinitionRegistrar类型的是加入集合中循环处理,其他的两种类型
* 都是递归处理,首先普通类,是递归处理,生成一个ConfigurationClass,然后设置一个属性ImportBy,ImportBy就是谁导入它的
* 谁就是ImportBy的集合一员,最后根据是否有ImportBy来产生一个最基本的BeanDefinition,而ImportSelector的回调方法是会返回
* 一个待处理的类集合数组,数组里面的就是要导入的类列表,也会产生@Import的三种类型,反正就是有时递归
* 看这个方法的时候一定要注意结合上下文来分析,反正注释倒是写的挺全,但是看注释还不如直接上代码,注释只是根据个人自己的理解去写
* 但是代码实现情况,每个人都会不同的理解和注释,只要理解即可.
* 5.配置类解析完成过后,然后do while循环中又重新重容器中拿到BeanDefinition中的数量,然后和之前获取的数量进行比较,最后找出新产生的
* BeanDefinition,然后判断是否是一个符合条件的配置类,如果是一个配置类,然后又添加到配置类候选者集合中 ,一直循环,直到将所有的配置类处理完成
*
* Build and validate a configuration model based on the registry of
*
* {@link Configuration} classes.
*/
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
//声明一个list,用来存放我们的配置类(什么配置类??),比如我们的配置类是AppConfig,
//这个类我们是通过spring提供的方法register注册到DefaultListableBeanFactory的bdmap中的
//所以这里的configCandidates主要为了先存放我们的配置类,因为配置类可以有多个,所以这里是一个集合
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
//这里从registry中拿到所有已经注册到DefaultListableBeanFactory中的bdmap集合
// (DefaultListableBeanFactory继承了BeanDefinitionRegistry)
//这里是拿到容器中所有的BeanDefinition的名字,如果你只设置了一个配置类,你没有手动添加一些bean工厂的后置处理器的话,那么这里
//拿到的就只有6个,有5个bd是spring的内置的bd,有一个bd是你的配置类,而这里我们主要就是拿到这个配置bd,然后进行处理
String[] candidateNames = registry.getBeanDefinitionNames();
//循环处理这个bdmmap集合
for (String beanName : candidateNames) {
//通过BeanName拿到一个bd
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
//这里判断我们的BeanDefinition中是否有一个configurationClass属性,configurationClass是配置类信息封装对象
if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
}
/**
* 这里的判断就是来判断我当前循环的这个BeanDefinition是否是一个配置类的候选者,并且下面的这个逻辑判断了如果你加了
* @Configuration注解,如果有这个注解,那么在bd中设置一个属性full,表示全注解的意思,而如果没有加@Configuration注解
* 那么认为是一个部分注解的配置类,它们二者有什么区别呢?区别就是如果是全注解,那么AppConfig这个类会生成一个代理对象
* 而如果不是全注解则不生成代理对象,也就是说如果加蓝了@Configuration,那么如果你getBean(appcaonfig)的话,返回的是一个代理对象
* 代理的是Appconfig
*/
//这里就是配置类判断的核心代码了,包括是否全注解的添加也在这里,checkConfigurationClassCandidate方法最重要的两点:
//判断当前的bd中的类是否实现了@Configuration,如果是则设置一个属性FUll,否则设置属性LITE
//这个方法返回一个true or false,如果是true,则我们当前循环的这个bd是一个配置类的候选者,加入configCandidates
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
// Return immediately if no @Configuration classes were found
//如果没有找到符合条件的配置类候选者列表,那么直接返回,证明系统中没有符合条件的配置类候选者
if (configCandidates.isEmpty()) {
return;
}
// Sort by previously determined @Order value, if applicable
/**
* 对我们扫描到的配置bd进行排序,看谁先执行,就是说我们如果希望你设置的比如3个配置类,那个先执行,那个后执行, 也就是说
* 你可以控制它的执行顺序,如果这样的话,你可以实现了一个Order接口,实现了Order接口,根据Order的大小进行排序,最后根据Order最小
* 的最先执行,其他的依次执行,如果你都没有加Order,那么这个Order的值默认是Integer.Max_VAlue
*/
configCandidates.sort((bd1, bd2) -> {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
});
// Detect any custom bean name generation strategy supplied through the enclosing application context
//我们的注册器BeanDefinitionRegistry肯定是SingletonBeanRegistry,看工厂类DefulatListableBeanFactory就知道
//这里就是判断你应用是否实现了beanName的生成器,如果没有就使用默认的生成器
SingletonBeanRegistry sbr = null;
if (registry instanceof SingletonBeanRegistry) {
sbr = (SingletonBeanRegistry) registry;
if (!this.localBeanNameGeneratorSet) {
BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
if (generator != null) {
this.componentScanBeanNameGenerator = generator;
this.importBeanNameGenerator = generator;
}
}
}
//因为后置的扫描要处理@ProperResouce注解,所以如果这里之前没有设置environment,那么这里重新创建
//其实在spring启动准备的时候就已经加了这个环境的bean了,所以这里肯定是不能为空,只是spring设计的严谨一点
if (this.environment == null) {
this.environment = new StandardEnvironment();
}
// Parse each @Configuration class
//申明一个配置类解析器,用来解析我们的配置类
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
//这里重新申明了一个SET结合来存放我们上面得到的配置类,为什么要用一次set来存放?(set去重,set底层实现就是map的key,所以是可以去重的)
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
//存放容器中已经被处理过的配置类集合
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
/**
* 下面是开启了一个do while 循环,每次拿到的配置类集合进行处理,因为每次处理完成过后还可能出现新的配置类,所以这里每次
* 获取配置类列表,然后处理,处理完成过后又重新获取新的配置类列表,直到将所有的配置类处理完成
*
*/
do {
//下面这个方法非常重要,我起初刚研究spring源码的时候,为了找到这个核心代码已经放弃过很多次,因为我在前面的代码就断片了,debug也跟不到这里
//来,因为实在太多类了,跟着跟着就丢了;所以下面的解析类就是来解析我们配置类配置了扫码包的路径
//和一系列的其他操作,比如@import的处理等,逻辑非常复杂,核心之核心之处就在这里
parser.parse(candidates);
parser.validate();
/**
* 将扫描到的配置类都放入了parser.getConfigurationClasses(),所以这里获取扫描得到的配置类列表;
* 这里说明一下,在spring的扫描架构中,只要是扫描到的@Component它都认为一个配置类
* 1.@Compoent为一个配置类,表示被扫描到的;
* 2.@Import为一个配置类,其中包含了属性ImportBy,表示被谁导入的;(扩展点)
* 3.beanMethods表示配置类中的@Bean方法列表;
* 4.importedResources属性表示配置类要导入的资源列表;
* 5.importBeanDefinitionRegistrars属性表示被@Import导入的Bean定义注册器(扩展点)
*/
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
// Read the model and create bean definitions based on its content
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
//这里非常重要,就是我们在扩展spring的时候定义了很多的BeanFactoryProcessor和@import的注解类
//在上面的parse中已经把@compenet的类扫描成了bd,把自定义的后置器和@import的类扫描出来了放入了configClasses中
//下面的这个方法就是为了处理添加到了configClasses中的类,将其注册为bd
this.reader.loadBeanDefinitions(configClasses);
//将已经处理过的配置类放入已处理的列表中
alreadyParsed.addAll(configClasses);
//将当前已经处理过的配置类集合清除(它是do while循环退出的依据)
candidates.clear();
/**
* 如果说从容器中拿到的的BeanDefinition的数量是大于一开始获取的BeanDefinition数量candidateNames.length
* 那么就证明在上面的解析配置类的过程中出现了新的配置类(肯定会出现的,配置类只要满足@Component就会成为一个配置类)
* 所以它又还会去处理,但是在parse方法里面已经做了去重,@Component中如果有@Bean这些也会进行处理
* 所以这里再次去处理也是没有问题的;下面代码的逻辑:
* 重新获取系统中所有的BeanDefinition,然后筛选掉上面已经处理过的BeanDefinition,然后去检查是否
* 是配置类的候选者,也就是判断是否是一个配置类,如果是一个配置类,那么添加到配置类处理集合candidates,
* 再次去循环,直到系统中的所有配置类都已经被处理过
*/
if (registry.getBeanDefinitionCount() > candidateNames.length) {
//从容器中重新拿出来的所有的Beand的名字集合
String[] newCandidateNames = registry.getBeanDefinitionNames();
//上一次获取的BeanDefinition名字集合
Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
//从之前到现在已经解析过的配置类名字集合
Set<String> alreadyParsedClasses = new HashSet<>();
for (ConfigurationClass configurationClass : alreadyParsed) {
alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
}
//循环新获取的配置类集合名字
for (String candidateName : newCandidateNames) {
//去掉已经处理过的
if (!oldCandidateNames.contains(candidateName)) {
//如果符合配置类的候选者判断,然后加入到处理集合列表中,开始新一轮的循环
BeanDefinition bd = registry.getBeanDefinition(candidateName);
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
!alreadyParsedClasses.contains(bd.getBeanClassName())) {
candidates.add(new BeanDefinitionHolder(bd, candidateName));
}
}
}
candidateNames = newCandidateNames;
}
}
while (!candidates.isEmpty());
// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
}
if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
// Clear cache in externally provided MetadataReaderFactory; this is a no-op
// for a shared cache since it'll be cleared by the ApplicationContext.
((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
}
}
上面代码的逻辑在代码中注释已经写的很清楚了,我这里简单总结一下:
1.从BeanFactory中(属性beanDefinitionNames)中拿到所有的beanName的名字;
2.循环这个beanNames,根据beanName去beanDefinitionMap获取BeanDefinition,然后判断这个BeanDefinition是否是一个配置类,配置类的检查条件是是实现了@Configutation、@Component、@ComponScan、@Import、@ImportResource、@Bean的条件之一都满足,唯一有一个区别就是如果是加了@Configuration注解的配置类,那么会设置一个属性full表示是全注解的配置类,否则就是一个lite属性,表示部分注解的配置类,如果是全注解的配置类,那么这个配置类最后也是在单例池中生成一个对象,这个对也是一个代理对象,否则就是一个简单Bean对象;满足条件的BeanDefinition就会添加一个配置类的处理集合中candidates;
3.如果筛选出来的candidates集合不为空,那么就开始do while循环,然后循环中调用配置类解析方法parse去解析,解析主要处理了@Component、@ComponScan、@Import、@ImportResource、@Bean这个几个注解:
a.判断是否加了@Componet注解,如果加了,处理@Component所在类的内部类,因为内部类可能有其他注解,然后这里是一个递归处理;
b.判断是否有@ProperResource注解,如果有,处理@ProperResource,将配置的资源文件解析出来放入到Environment中;
c.处理@ComponScan注解,执行真正的扫描工作,将符合条件的创建ConfigurationClass;
d.处理@Import注解,@Import注解有三种类型,普通类的导入,ImportSelector,ImportBeanDefinitionRegistrar;
e.处理@ImportResource注解,@ImportResouce注解一般导入的是spring的配置文件,配置的是;
f.处理每个配置类的@Bean方法。
配置类解析完成过后,然后do while循环中又重新重容器中拿到BeanDefinition中的数量,然后和之前获取的数量进行比较,最后找出新产生的,最新产生的如果也是满足配置类的检查条件,又加入到candidates中进行循环调用parse方法,直到将容器中的所有配置类都解析完成,就是将系统中所有符合条件的每个配置类都解析成BeanDefinition,直到全部处理完成过后,那么就退出循环, 配置类的解析工作就完成了。
上面的代码中有个细节需要说下,可能大家也都知道,如果我们想要控制你的某个配置类是在指定的顺序执行,可以实现Order接口,上面的代码找到的所有配置类也是进行了根据Order排序的,比如你扫描的配置类有2个,你想要第一个配置类最先执行,那么就可以实现Order接口即可。
checkConfigurationClassCandidate
这个方法是一个工具类的方法,就是检查我们从beanDefinitionMap中拿到的BD是否满足是一个配置类的候选者,我们看下它的实现
public static boolean checkConfigurationClassCandidate(
BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
//判断我们的beanclass是否为空或者是否是工程方法名字,显然这里不是
String className = beanDef.getBeanClassName();
if (className == null || beanDef.getFactoryMethodName() != null) {
return false;
}
/**
*
*/
AnnotationMetadata metadata;
//我们的配置类Appconfig肯定是AnnotatedBeanDefinition,所以这里的条件肯定能进去
/**
* 我们的配置类注册的时候,是通过ApplicationContxt.register注册进去的,注册的时候Appconfig是通过asm技术将这个类上的所有
* 注解信息处理成一个元数据,所以Appconfig肯定是一个注解的bd,你可以回头去看下register方法里面的处理逻辑就知道了
* Appconfig就是一个注解的bd,也就是AnnotatedBeanDefinition
* 下面的两个判断的意思就是说如果你是一个注解的bd(AnnotatedBeanDefinition), 那么之前在创建这个bd的时候就已经拿到了它的所有注解
* 信息,并且生成了元数据信息,如果你是一个AbstractBeanDefinition,那么这里重新调用asm的方法去生成类的元数据信息
* 还有一个判断就是如果说你的这个bd是BeanFactoryPostProcessor、BeanPostProcessor等类型,那么直接返回,就表示不是一个配置类的候选者
* 这个肯定是的,如果是这几种类型,表示是spring内置的一些类,肯定不是我们要扫描的配置类候选者
*/
if (beanDef instanceof AnnotatedBeanDefinition &&
className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
// Can reuse the pre-parsed metadata from the given BeanDefinition...
metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
}
else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
// Check already loaded Class if present...
// since we possibly can't even load the class file for this Class.
Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();
//判断如果BeanDefinition是下面这几种类型,那么就表示不是配置类的候选者,是spring的内置的一些bean而已,就给它过滤掉
if (BeanFactoryPostProcessor.class.isAssignableFrom(beanClass) ||
BeanPostProcessor.class.isAssignableFrom(beanClass) ||
AopInfrastructureBean.class.isAssignableFrom(beanClass) ||
EventListenerFactory.class.isAssignableFrom(beanClass)) {
return false;
}
//如果不是spring内置的一些bd,那么又是一个是抽象的bd,那么这里去获取下这个bd中Beanclass中的所有元数据信息
metadata = AnnotationMetadata.introspect(beanClass);
}
else {
try {
//如果不是注解的bd,也不是抽象的bd,那么这里直接去重新拿到到这个beanclass中的所有元数据信息
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
metadata = metadataReader.getAnnotationMetadata();
}
catch (IOException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not find class file for introspecting configuration annotations: " +
className, ex);
}
return false;
}
}
//代码运行到这里的时候,如果是spring远古的bd(spring内部bean^_^)都在metadata中了
//所以下面判断非常重要,如果是spring的内部bean(AbstractBeanDefinition或者RootBeanDefinition)
//那么下面的if和elseif都不会进,直接进入else返回false了
//***如果我们的配置类AppConfig加了注解@Configuration,这里非常重要,设置一个属性full(后续将这个属性的重要性)
//如果我们的配置类AppConfig没有加@Configuration,那么config肯定为空,
// 但是isConfigurationCandidate(metadata)这个方法必须要返回true才是我们的想要得到的结果,也就是这个方法是来
//判断你这个配置类是否加了
// candidateIndicators.add(Component.class.getName());
// candidateIndicators.add(ComponentScan.class.getName());
// candidateIndicators.add(Import.class.getName());
// candidateIndicators.add(ImportResource.class.getName());
//所以这个方法就是看你有没有加这些注解,如果加了,则设置一个属性lite到配置类的bd中
/**
*这里面的逻辑就简单描述下:
* 1.你的配置类是否加了@Configuration注解,如果加了,并且@Configuration中的proxyBeanMethods是true的话,那么加一个full,表示
* 全注解,需要生成配置类的代理对象(cglib),如果你加了@Configuration,但是你的代理方法proxyBeanMethods是false的话,那么也不是一个全注解
* 也不加full属性,所以配置类的代理对象是根据是是否加了@Configuraiton,如果加了,是否重新定义了proxyBeanMethods这个属性.
*
* 2.如果没有加@Configuration注解,判断你是否有@Component、@ComponScan、@Import、@ImportResouce注解
* 如果加了这些注解的一个或者多个,都认为是一个配置类,但是不是全注解,bd中设置一个参数lite
*/
Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
else if (config != null || isConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}
else {
return false;
}
// It's a full or lite configuration candidate... Let's determine the order value, if any.
//当有多个配置类的时候,可以加@Order来设置执行的先后顺序,说白了就是排序,看谁先执行
Integer order = getOrder(metadata);
if (order != null) {
//如果加了@order,则设置一个属性到bd中,后续执行bd扫描的时候就可以根据ordre的属性来设置谁先启动
beanDef.setAttribute(ORDER_ATTRIBUTE, order);
}
return true;
}
static {
candidateIndicators.add(Component.class.getName());
candidateIndicators.add(ComponentScan.class.getName());
candidateIndicators.add(Import.class.getName());
candidateIndicators.add(ImportResource.class.getName());
}
public static boolean isConfigurationCandidate(AnnotationMetadata metadata) {
// Do not consider an interface or an annotation...
if (metadata.isInterface()) {
return false;
}
// Any of the typical annotations found?
for (String indicator : candidateIndicators) {
if (metadata.isAnnotated(indicator)) {
return true;
}
}
// Finally, let's look for @Bean methods...
try {
return metadata.hasAnnotatedMethods(Bean.class.getName());
}
catch (Throwable ex) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to introspect @Bean methods on class [" + metadata.getClassName() + "]: " + ex);
}
return false;
}
}
具体的逻辑在上面,代码里面我也写了注释了,这里主要就是根据@Configutation、@Component、@ComponScan、@Import、@ImportResource、@Bean这几个注解是否在配置类中存在,如果存在就是一个配置类,但是需要排除spring内部的一些Bean。
ConfigurationClass
ConfigurationClass这个是配置类的信息类,简单来说就是spring在扫描的过程中,如果发现是一个合格的BeanDefinition,都会封装成一个ConfigurationClass,但是这个配置信息类中有几个属性:
final class ConfigurationClass {
private final AnnotationMetadata metadata;
private final Resource resource;
@Nullable
private String beanName;
private final Set<ConfigurationClass> importedBy = new LinkedHashSet<>(1);
private final Set<BeanMethod> beanMethods = new LinkedHashSet<>();
private final Map<String, Class<? extends BeanDefinitionReader>> importedResources =
new LinkedHashMap<>();
private final Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars =
new LinkedHashMap<>();
.....
metadata:表示找到的符合条件,比如@Component注解类的注解集合。
resource:资源描述器。
beanName:bean的名字。
importedBy :这个属性集合表示当前的符合条件的配置类是被那个类导入进来的,比如
@Improt(A1.class)
public class Appconfig{...}
那么找到的符合条件的类就是A1,而ImportBy就是AppConfig,所以importBy这个属性主要适用于@Import注解;
beanMethods :这个表示在当前配置类中找到的@Bean注解标示的方法。
importedResources :表示配置类导入的spring资源配置文件的路径集合,这个属性主要保存了@ImportResource注解中配置的路径信息集合。
importBeanDefinitionRegistrars:这个也是@Import注解中实现了ImportBeanDefinitionRegistrar接口的配置类会添加到这个集合中。
ConfigurationClassParser
配置类解析器,这个是配置类的解析器,是spring内置的解析器类,我们看下上面的代码调用配置类的解析器中的parse方法进行解析的,所以我们看下这个类的parse方法如下:
parse解析
/**
*真正的扫描逻辑
* 处理了@Component @ComponScan @Import @ProperResouce @ImprotResource
* @param configCandidates
*/
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
/**
* 和前面的逻辑一样,就是看你的BeanDefinition是否是一个注解的bd,如果不是,根据beanClass获取类上的所有注解信息
* 生成元数据信息,然后进行处理;如通是一个注解的bd,那么直接将这个bd中的元数据信息在构建配置类信息对象的时候传入
* 如果不是一个注解bd,在构建配置类信息对象的时候去获取这个类的元数据信息,最后嗲用parse进行处理
* 所以我们直接看parse这个方法即可
*/
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
}
}
//这里是处理@import的延迟加载的import类,和之前@import一样,不一样的地方在于它是延迟加载的,需要在所有的@import处理
//完毕过后才会来调用
this.deferredImportSelectorHandler.process();
}
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
//processConfigurationClass这个方法是核心,就是真正处理我们的配置类的核心方法,这个方法可能会被调用多次
//递归调用,因为扫描出来的可能又是一个配置类,所以需要重复调用
processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}
processConfigurationClass核心处理方法
/**
*这个方法的作用是处理配置类,内容非常多
* ConfigurationClass:这个是我们的配置类构建的一个配置类信息类,它的里面包含的重要属性有:
* importBy:表示被谁导入的
* beanMethods:配置类中包含的@Bean方法列表
* importedResources:配置类中@ImportResouce的资源列表;
* importBeanDefinitionRegistrars:配置类包含的@Import是ImportBeanDefinitionRegistrar类型的注册器
* ,很多第三方框架,比如mybatis和spring整合的时候就采用了ImportBeanDefinitionRegistrar这个扩展点与spring整合
* 在里面将Mybatis中的mapper接口生成代理对象,然后设置为bd,放入spring,让spring给它创建实例
* @param configClass 我们的配置类
* @param filter java8的lanbad表达式的判断Predicate,这个条件判断的是否是我们的注解是否是spring的内置的注解
* @throws IOException
*/
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
/**
*
*/
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.
//asSourceClass这个方法就是内部处理先判断我们的注解类是否满足filter条件
//如果满足,则通过Class.forname创建,这里的Preditcate条件是表示注解都是否是以javax.annotaion或者spring的包开头的
SourceClass sourceClass = asSourceClass(configClass, filter);
/**
* 这里进行循环处理,条件是每次返回的sourceClass不为空,,在doProcessConfigurationClass方法中
* 每次处理完成过后,会获取这个类的父类,因为可能父类中存在注册信息,所以这里的循环其实就是以当前类为准备,一直进行扫描
* 处理,直到把所有的父类都处理完成,才结束,如果说处理到没有父类的时候,那么doProcessConfigurationClass方法
* 会返回一个空,就会跳出循环
*/
do {
//真正的后置处理器处理
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
}
while (sourceClass != null);
/**
* 每个配置类扫描处理完成过后都加入到configurationClasses
*/
this.configurationClasses.put(configClass, configClass);
}
doProcessConfigurationClass方法处理配置类,处理的逻辑也就是那几个注解是怎么处理的,但是从上面的代码可以知道是用了一个do while循环去处理的,为什么这里要用do while循环,在doProcessConfigurationClass这个方法处理完成了返回的时候会返回一个父类或者null,也就是说你当前处理的这个配置类处理完成了,看是否还有父类,如果还有父类,那么每次处理完成了都获取自己的父类,然后通过循环开始处理父类,直到处理完成,所以我们的配置类也是支持父类的处理的,比如父类有@Bean方法也是可以被扫描到处理的。处理完成过后将当前配置类所找到的所有符合条件的ConfiurationClass(扫码封装的对象)都放入configurationClasses这个集合,这个集合在外层会获取到并且被处理掉。
doProcessConfigurationClass
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {
/**
* 判断我们的注解是否实现了@Component,这个判断是什么意思呢?就是说如果你的配置类比如Appconfig中加了
* @Component注解,那么要先看处理这个配置类的内部类,万一内部类定义了@Bean或者@CompoenScan注解
* 所以这里先处理内部类,如果有内部类,先把内部类处理完成,如果内部类也有配置类,那么也会当成是一个配置类
* 的候选者,内部类的配置类的候选者判断条件和配置类的候选条件判断一致
*/
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// Recursively process any member (nested) classes first
//如果加了@Compenent,则优先处理内部类,这里是一个黑盒子,这里不做研究,就是处理内部类的操作
//说简单点,我们把Appconfig作为一个配置类提供给spring处理,但是如果我们的这个配置类里面还有内部类呢???
//最重要的是我们的这个内部类里面也包含了配置类或者组件类,比如加了@Compent之类的注解,那么spring需要优先处理
//spring怎么处理??spring扫码到如果有符合条件的内部类,又把内部类放入一个set集合中,循环调用我们的processConfigurationClass,
processMemberClasses(configClass, sourceClass, filter);
}
/**
* 内部类处理完成过后,开始处理 @PropertySource,这里的处理逻辑是怎么样的呢?
* 首先我们要明白,程序执行到这里的时候在spring容器中有环境变量Environment,这个时候的环境变量是不包括@PropertySource
* 导入的资源信息,仅仅是系统中和spring容器中以及jvm启动中的一些参数,所以如果你的配置类加了@PropertySource注解,
* 那么这里要把@PropertySource读取出来,然后把key value加入到Environment,而我们的@PropertySource可以这样配置:
* @PropertySource("classpath:xxx.properties")
* @PropertySource("/home/xx/xxx/xxx.properties")
* @PropertySource("${xxxx}")
*所以spring要先获取到@PropertySource中的字符串,那么进行占位符处理,反正就是最后要得到一个路径,关于占位符的处理
* 之前说过了,这里就不多说了
*/
// Process any @PropertySource annotations
//处理@PropertySources和@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.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}
// Process any @ComponentScan annotations
//下面是处理我们的@ComponentScan@ComponentScans,这才是最重要的,是spring容器中最终装入我们的bean的关键
//这里扫描到bdmap中过后,后续spring就通过这个map去实例化单例bean
/**
* 下面的逻辑就是处理@ComponScann/s的核心逻辑
*/
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
//spring @compoentScan上的注解进行循环处理
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
/**
* 下面构建了一个扫描对象ComponentScanAnnotationParser,对每个@ComponScan中配置的路径,得到路径过后进行扫描
* 主要处理了@Component注解,得到所有@Component注解所对应class成BeanDefinition,
* 扫描是通过构建一个扫描对象ClassPathBeanDefinitionScanner去扫描的,而spring在启动的时候也构建了一个ClassPathBeanDefinitionScanner
* 对象,为什么这边没有使用呢?因为spring启动的时候构建的扫描对象是用于程序员可以手动添加扫描的类去扫描的,而这里是配置类扫描
* spring会为每一个配置类构建一个扫描对象来处理
*/
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) {
//下面获取原始bd,如果原始bd为空,则将当前bd赋值给原始bd
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
//本次扫描出来的符合条件的BeanDefinition也就是@Component的BeanDefinition,它肯定也是一个配置类,下面又在判断是否是一个配置类
//如果是,又开始递归,因为如果你有@Component,那么你里面可能还有@Bean方法注解或者本身还具有@Import等注解信息,所以这里还需要进行递归
//调用 ,将所有的信息都找出来然后注册
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
// Process any @Import annotations
//处理@Import注解的相关操作,先通过getImmports得到所有的@Import类
//这里需要注意一点就是,如果我们定义了一个接口实现了ImportSelector,那么这个类如果只是加了@Component,那么
//spring只是把它当成了一个普通bd,不能自动调用起selectImports方法,所以没有办法将需要的对象转换成bd,
// 如果一个类实现了ImportSelector,那么必须通过注解
//@Import进入导入才行;以下的getImports是得到我们传入的类的所有加了@Import注解的集合?什么意思呢?
//就是我们的一个配置类,上面可以加很多注解,每个注解都 可以加@Import,所以getImports就是为了得到你加了几个@Import的类
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
// Process any @ImportResource annotations
/**
* 下面的是处理@ImportResouce("classpath:spring.xml"),类似于这种,获取locations列表,然后循环去处理
* 其中locations可以是通过占位符来实现,然后将占位符处理过后添加到ConfigClass中的importedResources的资源集合中
*/
AnnotationAttributes importResource =
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
if (importResource != null) {
String[] resources = importResource.getStringArray("locations");
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
for (String resource : resources) {
//占位符处理,占位符可以是@ImportResouce("${xxxx}")
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
//将占位符处理过后,添加到集合中
configClass.addImportedResource(resolvedResource, readerClass);
}
}
// Process individual @Bean methods
//下面是判断是否在配置类如AppConfig中添加了@Bean方法的,如果是,将其添加到beanMethods集合中
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// Process default methods on interfaces
/**
* 其实下面的processInterfaces接口处理,主要是处理java8中接口有默认实现的逻辑处理
* 默认接口也可以使用@Bean来定义一个Bean
*/
processInterfaces(configClass, sourceClass);
// Process superclass, if any
/**
* 程序执行到这里过后,当前的配置类已经处理完成了,这里获取它的父类,看是否有父类,如果有父类,返回父类,在do while循环中继续处理
* 如果没有父类,当前配置类就扫描结束了
*/
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if (superclass != null && !superclass.startsWith("java") &&
!this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// Superclass found, return its annotation metadata and recurse
return sourceClass.getSuperClass();
}
}
// No superclass -> processing is complete
return null;
}
上面的这个方法就是整个配置类解析的最核心最底层的部分,首先逻辑是:
1.看下当前配置类是否实现了@Component,一般配置类都实现了@Confinuration或者@ComponScan,但是如果你实现了@Component,那么就会处理这个配置类的内部类,如果有内部类的话,处理内部类,也是一样的,找到内部类,然后开始看下内部类是否满足是一个配置类的候选者,然后递归调用processConfigurationClass方法;
2.第二步是处理@PropertySources注解,如果你在配置类上加了这个注解,那么要处理这个注解,这个注解的处理就比较简单了,将这个注解的配置的路径拿出来读取properties文件,读取key和value放入Environment。
3.处理@ComponScan和@ComponScans注解,拿到所有的子啊@ComponScan中配置的类路径信息,然后开始扫描,其实我们加入spring容器很大概率是用了@Componscan这个注解的,所以这里是核心所在,底层spring是使用了asm技术去扫描的。
4.处理@Import注解,这个注解中有三种类型,可以是一个普通类,一个实现了ImportSelector,实现了ImportBeanDefinitionRegistrar,这三种的处理方式不一样;普通类最好理解,递归调用processConfigurationClass将这个普通类封装成了一个ConfiurationClass,里面有个属性ImportBy集合,就是表示这个配置类是那个导入进来的,如果实现了ImportSelector,那么开始递归调用,最后再进入普通类型的流程逻辑。如果是ImportBeanDefinitionRegistrar,那么添加到ConfigurationClass中的属性importBeanDefinitionRegistrars中,后续进行调用。
5.处理@ImportResouce,这个注解在文章的最开始已经解释了,就是将一个spring的配置文件导入到系统中,然后spring将里面的Bean标签解析成BeanDefinition。
6.处理@Bean,这个注解就是找到这个配置类中的所有@Bean方法,然后添加到ConfigurationClass的beanMethods属性集合中。
@Component内部类处理
/**
* Register member (nested) classes that happen to be configuration classes themselves.
* 这里是处理内部类的,获取配置类中的所有内部类,然后判断是否是一个配置类的候选者,如果是配置类的候选者
* 那么循环调用处理配置类的处理过程,将每个内部类进行处理,处理流程和主配置类的处理流程一样,也会调用到
* processConfigurationClass这个方法里面,就是一个递归调用,如果内部类的条件是满足的,那么就会进行递归调用,直到内部类处理完成
*/
private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass,
Predicate<String> filter) throws IOException {
Collection<SourceClass> memberClasses = sourceClass.getMemberClasses();
if (!memberClasses.isEmpty()) {
List<SourceClass> candidates = new ArrayList<>(memberClasses.size());
for (SourceClass memberClass : memberClasses) {
//循环获取内部类,然后判断是否是一个配置类的候选者,如果是,加入candidates
if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) &&
!memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) {
candidates.add(memberClass);
}
}
OrderComparator.sort(candidates);
for (SourceClass candidate : candidates) {
if (this.importStack.contains(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
//进行循环递归调用
processConfigurationClass(candidate.asConfigClass(configClass), filter);
}
finally {
this.importStack.pop();
}
}
}
}
}
这个是配置类实现了@Component注解过后,获取整个配置类的内部类,然后处理方式和父类的配置类处理方式一模一样,就是一个递归去处理。
@PropertySources配置文件加载
/**
* Process the given <code>@PropertySource</code> annotation metadata.
* @param propertySource metadata for the <code>@PropertySource</code> annotation found
* @throws IOException if loading a property source failed
* 这个方法是处理@PropertySource的逻辑,就是读取出来@PropertySource中的属性,然后得到资源路径列表,然后选好处理
* 将资源文件中的key value读取出来加入到Environment中
*/
private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
String name = propertySource.getString("name");
if (!StringUtils.hasLength(name)) {
name = null;
}
String encoding = propertySource.getString("encoding");
if (!StringUtils.hasLength(encoding)) {
encoding = null;
}
String[] locations = propertySource.getStringArray("value");
Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");
boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));
for (String location : locations) {
try {
//这里是处理$占位符的,得到一个真实的路径
String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
Resource resource = this.resourceLoader.getResource(resolvedLocation);
//然后加入到环境变量中
addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
}
catch (IllegalArgumentException | FileNotFoundException | UnknownHostException ex) {
// Placeholders not resolvable or resource not found when trying to open it
if (ignoreResourceNotFound) {
if (logger.isInfoEnabled()) {
logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage());
}
}
else {
throw ex;
}
}
}
}
@ComponScan和@ComponScans注解
/**
* 这个方法最后调用了一个doScan方法真正完成了注解的扫描,
* 将符合条件的类扫描成AnnotatedBeanDefinition,然后注册到bdmap中
* 在进行扫描之前还需进行一些注解上的判断和参数准备
* @param componentScan @ComponentScan
* @param declaringClass
* @return
*/
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
//看到这里是不是觉得这个类很眼熟???这个类在我们初始化注解应用上下文的时候,也就是创建容器的时候也创了一个ClassPathBeanDefinitionScanner对象
//但是这边这个对象和创建容器时候的扫描对象不是同一个,这边是新创建的,那为什么要创建两个呢?
//原因:因为spring的注解配置应用程序上下文 也就是我们程序入口AnnotationConfigApplicationContext这个类
//实现的接口AnnotationConfigRegistry,这个里面提供了两个接口方法,一个是register,也就是注册我们的配置类的
//而另一个是scan,因为AnnotationConfigApplicationContext是我们实例化创建容器的,所以可以通过
//AnnotationConfigApplicationContext产生的对象直接调用scan来扫描,也就是说spring容器提供了一个供
//用户自己去扫描自定义的特殊的类路径,而不需要spring容器去推断或者经过一系列的负责操作来得到你的扫描包路径
//而我们这边的这个ClassPathBeanDefinitionScanner扫描对象就是spring通过一系列的推断或者操作得到我们的扫描
//包路径,然后自己又创了一个新的扫描对象来处理我们的扫描操作
//这里在构建ClassPathBeanDefinitionScanner对象的时候是添加了一个默认的过滤器,包含过滤器,就是包含了我们的
//@Component注解,后面验证要用
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
//这里就是看你的@CompoentScan是否加了自定义的Bean生成器,如果你加了Bean的生成器,那么就会使用你的Bean生成器,否则就用默认的生成器
Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
BeanUtils.instantiateClass(generatorClass));
//这个在mvc中用
ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
scanner.setScopedProxyMode(scopedProxyMode);
}
else {
Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
}
/**
* 在scanner中,扫描器对象用下面的几个操作是添加了全局的公共的一些参数,比如过滤器,延迟加载的一些属性
* 比如说你在@ComponScan中添加了一个属性lazyInit为true,表示全局启用延迟加载,那么只要你在某个bean中没有指定是否
* 延迟加载,那么默认就是延迟加载的,这里只是设置了默认的一些参数,下面在创建BeanDefinition的时候会应用到每个BeanDefinition
*/
//设置资源路径扫描的Pattern,默认是"**/*.class
scanner.setResourcePattern(componentScan.getString("resourcePattern"));
//添加包含的过滤器,我们在配置@Component可以添加过滤器
//也就是说如果你添加了一些过滤器,如果添加了过滤器,那么所有的配置类都要经过它的包含过滤器,如果不匹配包含的过滤器
//那么spring就不会认为它是一个合格的BeanDefinition,也就不会扫描到
for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addIncludeFilter(typeFilter);
}
}
//这个是看是否配置了不扫描的过滤器,类似上面的,这个过滤器就是表示是否排除在外的BeanDefinition,如果排除在外
//那么如果你的BeanDefinition包含在里面,就不会认为是一个合格的BeanDefinition
for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addExcludeFilter(typeFilter);
}
}
//是否配置了延迟加载,这个是全局配置的延迟加载,如果配置了延迟加载的的属性,那么这个设置一个全局的延迟加载属性
//到scanner中,后面会应用到当前扫描到的所有BeanDefinition
boolean lazyInit = componentScan.getBoolean("lazyInit");
if (lazyInit) {
scanner.getBeanDefinitionDefaults().setLazyInit(true);
}
/**
* 下面的代码主要是为了拿到我们的配置的扫描包路径,非常简单,就是拿到我们配置的扫描包路径比如:com.xxxx.xxxx
*/
Set<String> basePackages = new LinkedHashSet<>();
String[] basePackagesArray = componentScan.getStringArray("basePackages");
for (String pkg : basePackagesArray) {
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
Collections.addAll(basePackages, tokenized);
}
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(declaringClass));
}
scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
@Override
protected boolean matchClassName(String className) {
return declaringClass.equals(className);
}
});
//执行真正的扫描,包路径是一个数组
return scanner.doScan(StringUtils.toStringArray(basePackages));
}
ClassPathBeanDefinitionScanner.doScan
/**
* 这个方法是扫描的核心,spring的所有扫描都用的它来扫描的,spring是通过asm技术来进行扫描的
* Perform a scan within the specified base packages,
* returning the registered bean definitions.
* <p>This method does <i>not</i> register an annotation config processor
* but rather leaves this up to the caller.
* @param basePackages the packages to check for annotated classes
* @return set of beans registered if any for tooling registration purposes (never {@code null})
*/
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
//这里的这个beanDefinitions是spring用来存放spring扫描到的bd,然后封装成一个BeanDefinitionHolder对象放入集合中
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
//findCandidateComponents方法是进行扫描,根据basePackage扫描到所有符合条件的类封装成一个bd
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
//根据BeanDefinition生成bean的名字也就是beanName
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
/**
* 在扫描的方法里面,findCandidateComponents,扫描到符合条件也就是加了@Component注解的类,然后也是符合过滤器的条件
* 的类,是创建的ScannedGenericBeanDefinition,而这个bd是继承了GenericBeanDefinition和AnnotatedBeanDefinition,而GenericBeanDefinition
* 又继承了AbstractBeanDefinition,所以下面的两个条件都是满足的,下面的两个条件都是满足的,第一个条件是设置了
*/
if (candidate instanceof AbstractBeanDefinition) {
//只有当我们扩展了Spring的时候,我们可以创建自定义的Bd,当我们扩展spring创建bd的时候,
//我们这个bd肯定不是注解的bd,可能是AbstractBeanDefinition bd或者GenericBeanDefinition
//所以下面这个方法主要为了给我们创建的bd或者spring内部自己的bd设置一些默认参数(但是这里肯定不是spring内部的bd,为什么??
//因为我们定义的扫描包路径是不可能扫描到spring的内部的对象的,比如我们的包路径com.xxx.xxx,而且是必须加了@Component之类的注解
//才会被扫描到,所以这里的bd肯定是我们自己定义的bd,而且是实现了spring的扩展定义的并且加了
// @Component才能被扫描到并且注册到bdmap中)
//所以postProcessBeanDefinition这个方法的含义就是你(应用程序)告诉spring,我实现了你的一些方法,通过一些手段将我实现的
//类中产生了一些非注解的bd,你帮我把一些参数给我初始化一下,让我成为一个完整的bd,待后续使用(容器实例化)
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
//下面这个判断就通常我们在应用程序中所设置了@Component,也就是注解bd,然后spring对它进行处理
if (candidate instanceof AnnotatedBeanDefinition) {
//也是设置一些参数,和上面postProcessBeanDefinition有点类似,但是这个不同在于我们是注解bd,所以
//下面这个方法就是根据我们 设置的注解是否有一些扩展参数,比如是否延迟加载,是否有一联@DepenOn等设置
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
//判断是否可以注册,就是简单判断这个bean是否已经注册
if (checkCandidate(beanName, candidate)) {
//首先封装bd到BeanDefinitionHolder,就是对bd的数据结构的封装
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
//注册扫描到的bd注册到bdmap中
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
上面的处理@ComponScan的方法简单来说就是扫码到所有符合条件的配置类,然后创建 BeanDefinition对象,然后注册到beanFactory中的beanDefinitionMap中。
@Import注解
/**
* 处理所有的@Import注解
* 这里是@Import的三种类型,分别是Import的普通类,ImportSelector,ImportBeanDefinitionRegistrar
* 1.如果导入的是普通类,那么会递归去调用processConfigurationClass,将普通类注册成一个ConfigClass,它有一个属性为ImportBy,这个Importby就表示是谁将它导入的
* 2.如果是ImportSelector,那么会调用它的selectImports返回一个列表,这个列表也是要被导入的类列表,类可能是普通类,可能是ImportSelector,也有可能是ImportBeanDefinitionRegistrar,
* 如果是普通类,进入普通类的逻辑,如果还是ImportSelector,又递归,如果是ImportBeanDefinitionRegistrar,添加到ConfigClass中属性importBeanDefinitionRegistrars集合中
* 3.如果是ImportBeanDefinitionRegistrar,就直接添加到ConfigClass中属性importBeanDefinitionRegistrars集合中;
* @param configClass 配置类class
* @param currentSourceClass
* @param importCandidates 所有带了@Import注解的类集合
* @param exclusionFilter
* @param checkForCircularImports
*/
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
boolean checkForCircularImports) {
//如果@Import注解集合为空,则直接返回不处理
if (importCandidates.isEmpty()) {
return;
}
//importStack是Improt的一个栈数据结构对象
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
/**
* 下面是对加了@IMport的类(获取@Import配置的类)列表,进行处理,@Import分三种
* 1.普通的bd(普通bd,根据spring的下面判断直接调用之前的方法processConfigurationClass进行初始化成一个bd放入bdmap)
* 2.(实现了Impor实现了ImportSelector的类,会调用selectImpots,得到一个类名称列表,而根据类名称列表再次调用
* processImports,循环判断返回的每个类是什么类型,也符合下面三种情况,如果是普通类,和1的步骤一样,如果还是ImportSelector,又
* 递归调用,如果是实现了ImportBeandefinitionRegistrar,那么在配置类对象中添加一个ImportBeandefinitionRegistrar对象,以备后续指向性)
* 3.实现了ImportBeanDefinitionRegistrar(直接把该对象添加到列表中后续执行其中的方法)
*/
for (SourceClass candidate : importCandidates) {
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
//candidateClass就是@Import中配置的类
Class<?> candidateClass = candidate.loadClass();
//因为实现了ImportSelector接口,所以实现类也是ImportSelector类型
ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
this.environment, this.resourceLoader, this.registry);
Predicate<String> selectorFilter = selector.getExclusionFilter();
if (selectorFilter != null) {
exclusionFilter = exclusionFilter.or(selectorFilter);
}
//判断该Import类是否是延迟加载的ImportSelector,如果是延迟加载的ImportSelector,就添加到deferredImportSelectorHandler列表中
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
//否则调用实现了ImportSelector中的selecctImpots中返回的类列表又开始递归调用
//直到类是普通的类或者ImportBeanDefinitionRegistrar类才算结束
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
//递归调用,递归调用的情况也有可能是这三种,反复递归
processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
}
}
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
//是一个ImportBeanDefinitionRegistrar类,将其添加到importBeanDefinitionRegistrars列表中
// Candidate class is an ImportBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);
//如果是Registrar类型的,那么添加到待处理列表中,后续进行处理
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
/**
* @Import 有三种类型,这里是普通类的类型,就是一个普通类,就是将一个普通类注册为一个BeanDefinition
* 如果是上面的ImportSelector的话,那么调用selectImports得到的注册列表如果是普通类型的类,那么还是会到这里
* 将一个普通的类注册成为一个普通BeanDefinition,最后成为一个创建一个ConfigClass,而这里构造的时候
* importBy就是AppConfig
*/
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
}
}
}
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();
}
}
}
@Import从开头到现在说的已经很多了,这个方法主要处理它的三种类型。
@ImportResource
/**
* 下面的是处理@ImportResouce("classpath:spring.xml"),类似于这种,获取locations列表,然后循环去处理
* 其中locations可以是通过占位符来实现,然后将占位符处理过后添加到ConfigClass中的importedResources的资源集合中
*/
AnnotationAttributes importResource =
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
if (importResource != null) {
String[] resources = importResource.getStringArray("locations");
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
for (String resource : resources) {
//占位符处理,占位符可以是@ImportResouce("${xxxx}")
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
//将占位符处理过后,添加到集合中
configClass.addImportedResource(resolvedResource, readerClass);
}
}
@Bean处理
// Process individual @Bean methods
//下面是判断是否在配置类如AppConfig中添加了@Bean方法的,如果是,将其添加到beanMethods集合中
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
配置类处理收尾
// Process default methods on interfaces
/**
* 其实下面的processInterfaces接口处理,主要是处理java8中接口有默认实现的逻辑处理
* 默认接口也可以使用@Bean来定义一个Bean
*/
processInterfaces(configClass, sourceClass);
// Process superclass, if any
/**
* 程序执行到这里过后,当前的配置类已经处理完成了,这里获取它的父类,看是否有父类,如果有父类,返回父类,在do while循环中继续处理
* 如果没有父类,当前配置类就扫描结束了
*/
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if (superclass != null && !superclass.startsWith("java") &&
!this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// Superclass found, return its annotation metadata and recurse
return sourceClass.getSuperClass();
}
}
// No superclass -> processing is complete
return null;
}
上面的几个注解处理完成过后,然后这里还有两个地方需要处理,就是JAVA8中在接口中也可定义默认的方法,也就是可以添加@Bean,所以processInterfaces主要就是处理下是不是有接口中有@Bean方法。最后的if逻辑就是为了看当前处理的配置类是否还有父类,上上面我已经说了,外层是一个do while循环,如果这里返回null,表示没有父类了,循环可以介绍。
回到外层的processConfigBeanDefinitions
配置类解析的方法入口是processConfigBeanDefinitions这里,parse的方法处理完成了,回到这个方法的parse过后的逻辑,截图如下:
上面的parse方法就处理了太多太多了,处理完成过后回到这里,将思维拉回最初的地方,然后构建了一个confiigClasses,放入的就是parse返回的配置类信息。
loadBeanDefinitions
loadBeanDefinitions这个方法主要将扫描得到的@Import中导入的普通类、注册器、@Bean方法集合、@ImportResouce进行处理,都将它们变成一个一个的BeanDefinition,源码如下:
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
for (ConfigurationClass configClass : configurationModel) {
loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
}
}
private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
//这里就是看我们的configlcass是否有跳过注册,一般不会
if (trackedConditionEvaluator.shouldSkip(configClass)) {
String beanName = configClass.getBeanName();
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
}
this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
return;
}
//这里完全就是为了处理@Import注解中的类,开始讲我们@import中配置的类返回的类注册为bd
//这里只包含普通的类,没有其他
if (configClass.isImported()) {
//下面这个方法非常简单,就是简简单单的把我们configClass中放入的@imprt读取的普通类
// (@import中配置的类种返回的类列表)注册为普通的BeanDefinition
//在这里生成的bean的名字就是类的全限定名
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
//如果在你的bean中,你配置了@Bean的方法,这里将每个@Bean方法注册为一个BeanDefinition
//其中会设置@Bean的所有自定义设置属性参数和默认参数,默认参数比如默认销毁方法等
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
//处理@ImportResource中的xml,一般@ImportResouce中是一个xml文件,xml文件中配置了一些bean
//就是将xml中配置的<bean />解析成BeanDefinition
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
//这里就是执行了后置处理器的回调方法,这里执行完毕过后,我们定义了后置处理器BeanFactoryPostProcessor就
//已经调用完毕了
//getImportBeanDefinitionRegistrars这里的数据是在@Import类型为Registrar的时候添加的
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
上面的一些调用方法处理的逻辑代码就不贴出来的,下面有兴趣去看下,都不是很复杂了,复杂的在上面解析配置类的过程,但是如果不去看也没关系,只要懂了配置类解析的过程即可,这里面的没多重要,就是一些很小的细节,就文章刚开始说的,比如@Bean的名字怎么出现@Bean(value={“2”,“3”,“4”})这种取第一个作为beanName,其他为别名,就是在loadBeanDefinitionsForBeanMethod中处理的,,还有spirngxml配置文件的解析过程,反正上面的过程就是将ConfiurationClass中的importedBy、beanMethods、importedResources、importBeanDefinitionRegistrars都进行处理然后生成一个一个的BeanDefinition,然后注册到beanDefinitionMap中。到这里配置类的解析过程就结束了,虽然比较绕,但是和之前分析的bean的生命周期来说,还是觉得这里要简单些,毕竟绕的地方就那么几处,bean的生命周期那里可是三路十八弯,每个弯又进行十八弯,o( ̄︶ ̄)o。