SpringBoot源码学习之基于注解开发的主要原理

前言

SpringBoot是一个基于注解开发的框架,与Spring相比,我们不需要编写繁琐的XML文件去定义Bean之间的关系才能交给Spring容器处理。往往我们可以用最简单的方式,只需要在Bean对象所在类上标注一个@Component注解,SpringBoot运行期间便能扫描到这个类并最终加载到容器中。

例如,我们有一个Bean,在SpringBoot和Spring框架中分别需要以下配置。

以SpringBoot的方式配置

@Component
public class TransactionBean {
    private String name;
}

以Spring的方式配置

<beans ...>
    <bean id="transactionBean" class="com.zst.study.component.TransactionBean">
        <property name="age" value="sss"/>
    </bean>
</beans>

通过以上两种方式,都可以在程序运行期间为Spring容器注入一个beanName是transactionBean的Bean对象。但是很明显,第一种通过给指定类上标注@Component的方式看起来更“平易近人”、更简单直接。本文将讲解SpringBoot基于注解开发的主要原理。

准备工作

使用SpringBoot,我们需先引入spring-boot-starter依赖。

<parent>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-parent</artifactId>
   <version>2.4.4</version>
   <relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
   <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter</artifactId>
   </dependency>
 </dependencies>

概述

运行SpringBoot项目,我们需要定义一个启动类,并且为其标注@SpringBootApplication注解,接着在Main函数中运行SpringApplication.run(xxx.class, args),当run方法结束后,即代表着SpringBoot项目启动完成,我们便可以访问Spring容器中的各种资源,比如一个通过SpringBoot构建的WEB项目,当run方法完成时刻,tomcat服务器随即启动成功,我们便可以通过http请求方式访问接口资源。

//SpringBoot的核心注解,一切将以这里为起点
@SpringBootApplication
public class MainSpringBootController {
   public static void main(String[] args){
      //run方法执行完成后,SpringBoot项目启动成功
      ConfigurableApplicationContext run = SpringApplication.run(MainSpringBootController.class, args);
   }
}

是的,一个简易的SpringBoot项目就是这么运行的,所以你应该猜到SpringBoot基于注解开发的起点将是这个@SpringBootApplication注解SpringApplacation#run(...)方法,因此本文将会以这两个点讲解基于注解开发的主要原理。

@SpringBootApplication

@SpringBootApplication是一个组合注解,它由三个核心注解组成:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan。@SpringBootConfiguration本质上是@Configuration注解,告诉Spring,启动类也是一个Bean对象。@ComponentScan是扫描除了启动类这个Bean以外的Bean。@EnableAutoConfiguration主要用于将第三方组件引入项目中。

@SpringBootConfiguration

可以看到,其实@SpringBootConfiguration是由@Configuration组成,也就是说它其实就是一个Configuration配置类。通常按照Spring的一些默认规范,在配置类上会标注此注解,当然也可以标注@Component,因为@Configuration也是由@Component组成。

@ComponentScan

从它的中文译名“组件扫描”,我们大概就能猜到它是干什么的了,没错它正是负责扫描项目中的标注了@Component的类,这些类统称为候选类,因为BeanDefinition对象的创建来源就是经过筛选、符合条件的候选类。

那么启动类上的@ComponentScan又是在何时、何处开始工作的呢?其实@ComponentScan的处理是在SpringApplication#run(...)#refreshContext(...)方法进行的。

在SpringApplication#run(...)方法中,两个方法尤为关键,它们是prepareContext(...)和refreshContext(context)方法,prepareContext(...)#load(...)方法里面会为启动类创建一个BeanDefinition对象并注入到容器中,而refreshContext(context)方法则将以启动类的BeanDefinition对象作为起点,把所有通过注解标注的Bean、或者依然通过Spring的老方法XML形式配置的Bean都逐个找出来为它们执行生命周期相关方法。

@ComponentScan注解处理细节本小节不再过多讲述,将在后面的“SpringApplication#run(...)方法”章节深入解析。

@EnableAutoConfiguration

@EnableAutoConfiguration注解是用来实现自动装配的,什么是自动装配呢?简单的理解就是,我们知道在SpringBoot程序启动过程中会扫描发现自己写的各种Bean并最终加载到容器中去,因为这些Bean上标注了@Component注解。但是对于第三方组件,又需要做些什么才能引入呢?

没错,SpringBoot通过@EnableAutoConfiguration注解引入了一个非常重要的类AutoConfigurationImportSelector.class,这个类将会扫描项目中的所有JAR包下的META-INF/spring.factories文件,只要第三方组件遵循规范,将需要导入的配置类的全限定类名添加到KEY为org.springframework.boot.autoconfigure.EnableAutoConfiguration下面,那么SpringBoot将会读取到这些配置类的全限定类名,这就是自动装配。有了类的最重要的信息——类路径名,那么SpringBoot后面将会根据它们进一步派生为BeanDefinition对象并最终创建为Bean对象。

此注解的讲解篇幅较长,这里不深入介绍。可以到我的另一篇SpringBoot源码学习之SpringBoot自动装配原理文章查阅。

SpringApplication#run(...)方法

SpringApplication#run(...)方法是SpringBoot项目启动的入口,当它执行完成后,容器所需的资源也随之加载完成,例如Bean对象的创建、配置文件内容加载到环境变量等。

在前文讲解@ComponentScan的小节中提到了,对于注解开发来说,此方法中有两个方法尤为关键,一个是prepareContext(...)方法,一个是refreshContext(...)方法。讲述着两个方法之前,我们先看看ConfigurableApplicationContext对象此对象将承载SpringBoot项目启动期间创建的环境变量、Bean对象等资源,也就是说它是一个集合了资源环境的对象。

ConfigurableApplicationContext

ConfigurableApplicationContext其实是一个接口,它实现了接口BeanFactory,也就是说本质上这个ConfigurableApplicationContext也是一个BeanFactory,可以通过getBean(...)等方法从容器中获取指定的Bean对象。 

如果不是WEB项目,在run方法中一般会创建一个AnnotationConfigApplicationContext实例,它实现了ConfigurableApplicationContext接口、继承了GenericApplicationContextGenericApplicationContext有一个DefaultListableBeanFactory成员变量,而这个DefaultListableBeanFactory非常重要,DefaultListableBeanFactory即实现了BeanFactory接口也实现了BeanDefinitionRegistry接口,当把DefaultListableBeanFactory当作BeanFactory使用的时候,可以通过访问其某些getBean(...)方法从容器获取Bean对象。当把它当作BeanDefinitionRegistry使用的时候,可以调用其registerBeanDefinition(...)方法将BeanDefinition对象注册到容器。

public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {
	private final AnnotatedBeanDefinitionReader reader;
	private final ClassPathBeanDefinitionScanner scanner;
}
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
	private final DefaultListableBeanFactory beanFactory;
    //当AnnotationConfigApplicationContext创建之际会调用父类的构造函数,这里会直接new一个新的//DefaultListableBeanFactory实例
    public GenericApplicationContext() {
		this.beanFactory = new DefaultListableBeanFactory();
	}
}
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
		implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
    /** 装载BeanDefinition对象的集合,key是beanName. */
	private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);   
    @Override
	public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException {
        //将BeanDefinition对象注册进Spring容器...
        this.beanDefinitionMap.put(beanName, beanDefinition);
    }
    @Override
	public <T> T getBean(Class<T> requiredType) throws BeansException {
		return getBean(requiredType, (Object[]) null);
	}
    //...其他getBean方法
}

prepareContext(...)方法

在SpringApplication#prepareContext(...)方法中,会做许多事情,例如为ConfigurableApplicationContext对象设置环境变量、对context对象做一些后置处理、注册某些单例的Bean等等、将启动类加载到context对象中。

由于本文主要讲解基于注解开发的主要原理,所以此章节将重点讲解将启动类加载到context对象中的操作,即load(context, sources.toArray(new Object[0]))方法。

public class SpringApplication {
   	private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners...) {
      context.setEnvironment(environment);//设置环境变量
	  postProcessApplicationContext(context);//后置处理
      /**SpringApplication.run()方法是先new一个SpringApplication实例对象出
        来,此时会添加一些实现了ApplicationContextInitializer接口的应用上下文初始化器
        所以当ConfigurableApplicationContext实例对象创建完后,会在这里回调这些初始化器
      */
	  applyInitializers(context);
      // ...注册一些bean
      //这个sources就包含了启动类的class对象,所以会在load方法将启动类加载到这个
      //context对象中
      load(context, sources.toArray(new Object[0]));
}

load(ApplicationContext context, Object[] sources)

“@SpringBootConfiguration”章节分析了,启动类本质上也是一个Bean对象,那么启动类又是在何时、在何处被SpringBoot识别加载呢?在debug图中,可以知道正是在SpringApplication#run(...)#prepareContext(...)#load(...)方法中完成的。在load(...)中,创建了一个BeanDefinitionLoader的加载对象,将此时ConfigurableApplicationContext对象(实际是AnnotationConfigApplicationContext实例对象)和包含启动类Class对象的数组传入。

接着访问BeanDefinitionLoader#load()方法,从下图看到,最终是通过AnnotatedBeanDefinitionReader#doRegisterBean(...)方法,以启动类的Class对象创建出实例为AnnotatedGenericBeanDefinition的BeanDefinition对象,最后调用DefaultListableBeanFactory#registryBeanDefinition(...)方法将启动类的BeanDefinition对象注入到容器中。

当启动类的BeanDefinition对象注入到容器中以后,SpringBoot基于注解开发的工作已经完成了重要的第一步,那就是找到一个作为扫描起点的地方,即以启动类BeanDefinition对象作为起点,后续会根据此起点将所有候选的Bean对象都找出来 ,无论是自己写的Bean对象还是自动装配导入的配置类Bean对象。这个操作是在后面的refreshContext(...)方法执行的。

refreshContext(...)方法

refreshContext(...)最终会指向ConfigurableApplicationContext#refresh()方法,在这里即是AnnotationConfigApplicationContext#refresh()方法,不过AnnotationConfigApplicationContext并没有重写refresh()方法,而是调用父类之一的AbstractApplicationContext#refresh()方法。

refresh()方法

AbstractApplicationContext#refresh()方法里面,会执行12个方法,不过本小节并不会逐一讲解这12个方法,但是会讲解跟基于注解开发的主要原理想关联的两个重要方法:invokeBeanFactoryPostProcessors(beanFactory)方法以及finishBeanFactoryInitialization(beanFactory)方法

public class SpringApplication {
   	private void refreshContext(ConfigurableApplicationContext context) {
		....
		refresh((ApplicationContext) context);
	}
    protected void refresh(ConfigurableApplicationContext applicationContext) {
        //刷新应用上下午,Spring容器创建后的完善工作大部分都在这里完成
		applicationContext.refresh();
	}
}

public abstract class AbstractApplicationContext extends DefaultResourceLoader
		implements ConfigurableApplicationContext {
    @Override
	public void refresh() throws BeansException, IllegalStateException {
        	//准备此上下文以进行刷新.
			prepareRefresh();
			//告诉子类刷新内部 Bean 工厂
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
			//准备 Bean 工厂以用于此上下文
			prepareBeanFactory(beanFactory);
			try {
				//允许在上下文子类中对 Bean 工厂进行后处理.
				postProcessBeanFactory(beanFactory);
				//调用在上下文中注册为 Bean 的工厂处理器
				invokeBeanFactoryPostProcessors(beanFactory);
				//注册拦截 Bean 创建的 Bean 处理器。
				registerBeanPostProcessors(beanFactory);
				// 为此上下文初始化消息源
				initMessageSource();
				// 初始化此上下文的事件多播程序.
				initApplicationEventMulticaster();
				//初始化特定上下文子类中的其他特殊 Bean.
				onRefresh();
		        //检查侦听器 Bean 并注册它们
				registerListeners();
				//实例化所有剩余的(非 lazy-init)单例。
				finishBeanFactoryInitialization(beanFactory);
				//最后一步:发布对应事件
				finishRefresh();
			}cathc(Excepton e){
                // 异常处理 
            }
    }
}
invokeBeanFactoryPostProcessors(beanFactory) 方法

这个方法其实主要是找到一些实现了特定接口的Bean,回调其重写的抽象方法。比如,会去寻找实现了接口为BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessor的Bean,分别回调重写的postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)方法。

invokeBeanFactoryPostProcessors(beanFactory) 方法执行以前,SpringBoot会向容器注册一个ConfigurationClassPostProcessor的BeanDefinition对象,它正是实现了BeanDefinitionRegistryPostProcessor接口,在它的重写方法里面正是会以启动类为起点,去扫描其他地方存在的候选类并派生为BeanDefinition对象。

ConfigurationClassPostProcessor

在这个类的注释上,是这么说的:用于引导处理@Configuration。不过它能做的事情远不仅限于处理@Configuration标注的类,它会将所有标注了如@Configuration、@Component、@Import、@ComponentScan等注解的候选类找出来,并经过验证筛选后派生为BeanDefinition对象注入到容器中。

ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry(registry)方法中,会通过processConfigBeanDefinitions(registry)方法实现解析上述注解的逻辑。通过下面伪代码可以看到,首先做的第一步事情就是将传进来的BeanDefinitionRegistry对象中已经注册的BeanDefinition对象名称都找出来,经过筛选得到启动类以及它的类元数据(包括注解元数据),然后创建一个ConfigurationClassParser对象,再调用其parse(...)方法找到所有的候选类,最后创建一个ConfigurationClassBeanDefinitionReader对象并调用loadBeanDefinitions(...)方法将候选类派生为BeanDefinition对象。

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
		PriorityOrdered, ResourceLoaderAware, ApplicationStartupAware, BeanClassLoaderAware, EnvironmentAware {
    //...回调BeanDefinitionRegistryPostProcessor的抽象方法
    @Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
       //这个方法便是真正开始寻找所有候选类的起点
       processConfigBeanDefinitions(registry);
    }
    public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
	   List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
       //启动类之前被创建为BeanDefinition对象,所以在这里会找到启动类的名字
	   String[] candidateNames = registry.getBeanDefinitionNames();
       //...筛选符合特定条件的配置类,启动类将保留下来
       //创建ConfigurationClassParser对象由它以启动类为起点开始解析所有候选类
	   ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);
       //启动类在configCandidates集合里面
       Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
       //开始以启动类为起点解析 
       parser.parse(candidates);
       //...
       // 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());
	   }
       //configClasses装载了所有候选类,通过ConfigurationClassBeanDefinitionReader#loadBeanDefinitions方法为它们创建BeanDefinition对象
	   this.reader.loadBeanDefinitions(configClasses);
    }
}
parse(...)方法

ConfigurationClassParser#parse(...)方法里面便是真正处理注解的地方了,在下图可以看到,在processConfigurationClass(...)#doProcessConfigurationClass(...)方法里面将会处理@Component、@PropertySource、@ComponentScan、@Import、@ImportResource、@Bean等注解。

这里将挑选@Component、@ComponentScan、@Import三个常用的注解进行讲解,剩下的注解由于讲解起来也颇费篇幅,大家有兴趣的可以自行研究。

@Component的处理

@Component的处理是在ConfigurationClassParser#processMemberClasses(...)进行的。如果一个标注了@Component的类,含有内部成员类时,当它们符合两种情况时,会交给ConfigurationClassPostProcessor继续处理。第一种情况是,内部类标注了@Component、@ComponentScan、@Import、@ImportResource任意一个注解的。第二种情况是如果不标注上面四个注解,但是拥有@Bean标注的方法,则也符合条件变成一个候选类。

当符合条件的内部类找到以后,会把这些内部类变成候选类,逐一的递归调用processConfigurationClass(...)方法继续解析。

class ConfigurationClassParser {
  private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass,Predicate<String> filter) throws IOException {
    //获取@Component标注类中的内部成员类
    Collection<SourceClass> memberClasses = sourceClass.getMemberClasses();
    List<SourceClass> candidates = new ArrayList<>(memberClasses.size());
    for (SourceClass memberClass : memberClasses) {
    /**
       如果内部类是标注了@Component、@ComponentScan、@Import、@ImportSource注解的话,
       则是一个标准的候选类;若没标注上面四个注解,但是有@Bean方法,则也是一个候选类,所以
       这两种情况都会被扫描出来
    */
	  if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata())){
	    candidates.add(memberClass);
	  }
	}
    //递归调用processConfigurationClass进一步处理
    for(SourceClass candidate : candidates){
      processConfigurationClass(candidate.asConfigClass(configClass), filter);
    } 
  }
}   

@ComponentScan的处理

当启动类经过ConfigurationClassParser#processMemberClasses(...)方法处理@Component注解后,便会对@ComponentScan这个注解进行处理。对于@ComponentScan的处理是委托给ComponentScanAnnotationParser#parser(...)方法的。

那么这个ComponentScanAnnotationParser#parser(...)为我们做了什么事情呢?从下面伪代码看出,@ComponentScan注解是提供给ClassPathBeanDefinitionScanner这个扫描器使用的,而看它的返回值可以猜到,是通过扫描器的scan(...)方法将@Component标注的类扫描出来并封装为BeanDefinitionHolder对象。 

class ComponentScanAnnotationParser {
    public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final 
                                                            String declaringClass) {
        //创建一个扫描标注了@Component注解的类的扫描器
		ClassPathBeanDefinitionScanner scanner = new 
                                        ClassPathBeanDefinitionScanner(this.registry,
		                               componentScan.getBoolean("useDefaultFilters"), 
                                               this.environment,this.resourceLoader);
        //设置需要保留、引入的类
        for (AnnotationAttributes filter : 
                                componentScan.getAnnotationArray("includeFilters")) {
			for (TypeFilter typeFilter : typeFiltersFor(filter)) {
				scanner.addIncludeFilter(typeFilter);
			}
		}
        //设置需要排除的类
		for (AnnotationAttributes filter :  
                                componentScan.getAnnotationArray("excludeFilters")) {
			for (TypeFilter typeFilter : typeFiltersFor(filter)) {
				scanner.addExcludeFilter(typeFilter);
			}
		}  
        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()) {
            //当不在@Component指定basePackages和basePackageClasses时,会将启动类
            //的路径作为默认扫描路径
			basePackages.add(ClassUtils.getPackageName(declaringClass));
		}
       /**这里我们只需要记住,调用ClassPathBeanDefinitionScanner的doScan方法将
          标注了@Component的所有类都找出来并派生为BeanDefinition对象
       */
        return scanner.doScan(StringUtils.toStringArray(basePackages));
     }
}

在这里我们需要重点关注三个关键属性:includeFilters、excludeFilters、basePackagesincludeFilters指的是需要保留、能被扫描出来的,excludeFilters则同includeFilters相反,是排除、不能被扫描出来的,basePackages则是该ClassPathBeanDefinitionScanner扫描器的扫描路径。

ClassPathBeanDefinitionScanner扫描器在ComponentScanAnnotationParser#parser(...)方法通过new关键字创建出来的时候,会为其includeFilters注册默认的过滤器,其中就包含指定了Component注解的AnnotationTypeFilter过滤器,也就是说扫描器将会扫描出标注了@Component注解的类。

在启动类上的@SpringBootApplication注解里面的@ComponentScan注解默认的excludeFilters过滤器有两个,分别是TypeExcludeFilterAutoConfigurationExcludeFilter,也就是说当扫描器扫描时会排除这两种类。

basePackages这个属性就是扫描器的作用范围、扫描路径了。但它默认是空的,从伪代码看到,当扫描路径是空的时候,会将当前类的路径作为默认扫描路径。也就说当以启动类为起点时,扫描路径将是启动类当前的路径,和启动类同路径或其路径下的@Component的类就会被扫描出来。

includeFilters、excludeFilters、basePackages三个重要属性都设置给扫描器后,最终将进到其doScan(String... basePackages)方法,在这里面会做两件事情:找到候选类并派生为BeanDefinition对象、将这些BeanDefinition对象注册到容器中去。

第一件事,先根据basePackages扫描路径,将该路径下的所有class资源找出来,结合excludeFiltersincludeFilters过滤器,最终筛选出合格的候选类,然后为它们创建实际类型为ScannedGenericBeanDefinition的BeanDefinition对象。

第二件事情,就是先判断当前容器有没有上一步扫描出来的候选的BeanDefinition对象,如果没有就将它们注入容器。

当启动类第一次被prepareContext(...)#load(...)方法加载为BeanDefinition对象时,再到进入到ConfigurationClassPostProcessor#parse(...)#processConfigurationClass(...)#doProcessConfigurationClass(...)时,以启动类的BeanDefinition对象为起点最终进入处理@ComponentScan注解的扫描器时,启动类所在路径下的所有@Component标注的类都会被找到。也就是说,SpringBoot基于注解开发的工作已经完成了最重要的一步:寻找、发现。

@Import的处理

处理@Import注解是在ConfigurationClassParser#processImports(...)方法,简单来说@Import注解的功能就是导入一个Class对象,这个Class对象将会作为一个Bean候选类对象被处理,就像@Componet标注的类一样。在processImports(...)方法里,根据导入的Class对象的不同有不同的处理,一共有三种情况。

①Import进来的class对象实现了ImportSelector接口

对于实现了ImportSelector接口的类对象来说,还会根据其是否实现了DeferredImportSelector接口(中文是延迟导入选择)变成两种处理方式,如果是的话,则通过ConfigurationClassParser的一个类型是DeferredImportSelectorHandler的对象的handle(...)方法去处理。如果不是延迟导入,则将@Import注解中的value数组中的Class<?>对象都找出来,接着递归调用ConfigurationClassParser#processImports(...)方法。

@EnableAutoConfiguration注解的中导入AutoConfigurationImportSelector类对象便是即实现了ImportSelector接口又实现了DeferredImportSelector接口。

针对实现了DeferredImportSelector接口导入类的处理,篇幅也比较长,大家可以看这篇文章SpringBoot源码学习之SpringBoot自动装配原理

②Import进来的class对象实现了ImportBeanDefinitionRegistrar接口

实现了ImportBeanDefinitionRegistrar接口的类,将会通过反射的方式生成一个Bean对象,并将环境变量environment、bean的注册工厂registry等属性值赋值给该Bean对象。接着会把这个Bean对象放到载入类的importBeanDefinitionRegistrars集合中去。这种ImportBeanDefinitionRegistrar类型的Bean对象并将会在后面一定时机回调其重写的registerBeanDefinitions(...)方法。

@EnableAutoConfiguration注解的中导入的AutoConfigurationPackages.Registrar类,就是实现了ImportBeanDefinitionRegistrar接口,所以后面回调其registerBeanDefinitions(...)时将会创建一个beanName是AutoConfigurationPackages、类型是BasePackagesBeanDefinition的BeanDefinition对象注入容器,并且会将载入类的所在路径赋值给BasePackagesBeanDefinition。也就是说当处理启动上的这个AutoConfigurationPackages.Registrar类时,会创建出一个包含了和启动类同路径的BeanDefinition对象并注入容器,那么这个拥有启动类所在路径的BeanDefinition对象又会有什么作用呢?

其实这个也跟SpringBoot的自动装配有关系,例如当我们引入Mybatis框架时,Mybatis肯定也需要知道哪里有@Mapper注解标注的类,因此需要一个扫描器将它们扫描出来,而不手动指定扫描路径时,便会用到这个@EnableAutoConfiguration注解最终向容器注入的beanName是AutoConfigurationPackages、类型是BasePackagesBeanDefinition的Bean对象,扫描路径便是默认的启动类所在路径。

③Import进来的class对象既没有实现ImportSelector也没有实现ImportBeanDefinitionRegistrar接口

那么这种情况,将会把导入的class对象作为普通的Configuration配置类,递归调用ConfigurationClassParser#processConfigurationClass(...)方法处理,即像启动类对象一样解析@    Component的内部类、Property注解、@ComponentScan、@Import等注解。

最后,当doProcessConfigurationClass(...)方法完成以后,会将候选类的信息封装为ConfigurationClass对象并存到ConfigurationClassParser的configurationClasses集合中。后面将根据此集合内容去创建BeanDefintion对象。

本小节总结:

基于注解开发的主要原理其实可以分为两部分:发现(候选类 ——>BeanDefinition对象)和注册(BeanDefiniton——>Bean)。而讲解完三个常用注解@Component、@ComponentScan、@Import后,相信大家已经有一个清晰的认识了,就是截止现在,SpringBoot通过ConfigurationClassParser#parse(...)方法完成了发现候选类这部分的重要工作,剩下的就是将候选类变成BeanDefinition对象、Bean对象。

loadBeanDefinitions(...)方法

在上一章节讲述了SpringBoot通过ConfigurationClassParser#parse(...)方法发现候选类后,那么剩下的工作是要将它们派生为BeanDefinition对象了。这部分工作则是通过ConfigurationClassBeanDefinitionReader#loadBeanDefinitions(...)完成的。

可以看到,在loadBeanDefinitions(...)方法又是通过loadBeanDefinitionsForConfigurationClass(...)去真正处理将候选类创建为BeanDefinition对象的。

class ConfigurationClassBeanDefinitionReader {
   	public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
		TrackedConditionEvaluator trackedConditionEvaluator = new 
                                                      TrackedConditionEvaluator();
		for (ConfigurationClass configClass : configurationModel) {
			loadBeanDefinitionsForConfigurationClass(configClass, 
                                                       trackedConditionEvaluator);
		}
	}
    private void loadBeanDefinitionsForConfigurationClass(
			ConfigurationClass configClass, TrackedConditionEvaluator 
                                                     trackedConditionEvaluator) {
		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;
		}
        //将候选类自身注册为BeanDefinition对象
		if (configClass.isImported()) {
			registerBeanDefinitionForImportedConfigurationClass(configClass);
		}
        //将候选类的@Bean方法注册为BeanDefiniton对象,@Bean的处理有些特别
        //其BeanDefinition对象的BeanClass是宿主类 
		for (BeanMethod beanMethod : configClass.getBeanMethods()) {
			loadBeanDefinitionsForBeanMethod(beanMethod);
		}
        //处理@ImportResource导入的配置了Bean的xml文件
		loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
        /**处理@Import注入的实现了ImportBeanDefinitionRegistrar接口的候选类,因为在
        ConfigurationClassParser#parse(...)#processImports(...)将此类型的候选类提前创建为 
        BeanDefinition对象了,所以在这里是回调其registerBeanDefinitions(...)方法*/
  loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
	}   
}

首先是针对候选类自身,通过registerBeanDefinitionForImportedConfigurationClass(configClass)方法创建为BeanDefinition对象,对于候选类自身的处理并不复杂,只是将候选类的注解元数据对象(AnnotationMetadata)作为AnnotatedGenericBeanDefinition的构造函数创建一个实际类型为AnnotatedGenericBeanDefinition的BeanDefinition对象。它包含候选类的class信息。

接着是处理候选类的标注了@Bean注解的方法,对于@Bean的BeanDefinition,其class是原始类的class,因为@Bean方式创建的Bean对象正是通过原始类的Bean对象调用@Bean方法创建处理的,具体逻辑大家有兴趣可以自行研究,针对@Bean的处理,这里只讲结论不讲过程。

接着便是处理候选类中@ImportResource导入的资源文件,@ImportResource注解“parse(...)”方法章节没有讲述,但是我们只要知道,对于资源文件的处理方式和Spring读取XML文件的处理方式大同小异,所以有兴趣的话大家可以自行研究。

最后,便是处理候选类中的importBeanDefinitionRegistrars集合的内容,这个集合中的元素都是实现了ImportBeanDefinitionRegistrar接口的对象,并且这些对象早在parse(...)processImports(...)通过反射提前创建出来了,在loadBeanDefinitionsFromRegistrars(...)方法里面其实就是回调其重写的registerBeanDefinitions(...)方法。而不同的实例对象,其实现逻辑也不一样,想讲也无从下手。

但是前面讲过,@EnableAutoConfiguration注解中的@AutoConfigurationPackage注解最终会导入AutoConfigurationPackages.Registrar.class,而这个Registrar的重写方法其实就是往容器注入了一个名称为org.springframework.boot.autoconfigure.AutoConfigurationPackages的BeanDefinition对象,而它拥有的重要属性就是启动类的所在路径,因为这个AutoConfigurationPackages是给第三方组件当作默认扫描路径使用的,例如Mybatis,当我们不手动指定扫描路径,则会利用AutoConfigurationPackages找到默认路径去扫描@Mapper标注的类。

总结

当前面两个小节的内容讲解完后,ConfigurationClassPostProcessor#parse(...)方法和loadBeanDefinitions(...)方法其实就是做了基于注解开发的最重要的部分:将候选类找出来并派生为BeanDefinition对象!

finishBeanFactoryInitialization(beanFactory)方法

invokeBeanFactoryPostProcessors(beanFactory)方法章节中,我们知道了通过@Component、@ComponentScan、@Import、@ImportResource等注解标注的类或资源最终会通过ConfigurationClassPostProcessor#parse(...)方法和loadBeanDefinitions(...)方法将候选类都找出来并最终派生为BeanDefinition对象,那么在finishBeanFactoryInitialization(beanFactory)方法里面,就会将所有找出来的候选BeanDefinition对象创建为Bean对象。

将BeanDefinition对象变为Bean对象的过程最终是通过DefaultListableBeanFactory#preInstantiateSingletons()方法处理的,最终会通过AbstractBeanFactory#getBean(String name)方法去执行一个Bean的生命周期涉及的方法创建Bean对象。其实这部分也是直接调用Spring创建Bean的流程,跟基于注解开发的已无太多关联,因此大家有兴趣可以自行研究,本文不再研究。

要说唯一有关系的便是在AbstractAutowireCapableBeanFactory#doCreateBean(...)#populateBean(...)方法里面,会对一个半成品(只实例化了一个属性均为null的对象)的Bean对象做初始化操作(初始化就是属性填充),而这里面会用到一个AutowiredAnnotationBeanPostProcessor处理器,它会识别当前需要初始化的Bean是否存在@Autowired标注的对象属性以及@Value标注的值属性,如果存在则通过AutowiredAnnotationBeanPostProcessor将会将需要注入的对象或者配置值找出来并赋值给当前半成品的Bean对象。但是由于本文篇幅有限,对于AutowiredAnnotationBeanPostProcessor的讲解将会在另一篇文章解读。

全文总结

通过前面小节的讲解,我们终于知道SpringBoot基于注解开发的主要原理了,首先是依赖于启动类的@SpringBootApplication注解,该注解的子注解@EnableAutoConfiguration和@ComponentScan尤为重要,它们为扫描、寻找候选类的提供了客观基础。区别是@EnableAutoConfiguration主要用于扫描第三方JAR包下的META-INF/spring.factories下需要自动配置的配置类全限定类名,而@ComponentScan主要是扫描非第三方、自己在项目编写的有@Component等注解的Bean。

当SpringApplication#run(...)方法执行时,首先在prepareContext(...)方法将启动类提前创建为BeanDefinition对象,接着进入refreshContext(...)方法,根据@EnableAutoConfiguration和@ComponentScan两个注解的信息,去找到所有候选类并派生为BeanDefinition对象并最终利用Spring框架原始的创建Bean对象的那套逻辑,去将这些BeanDefinition对象创建为最终的Bean对象。​​​​​​​

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值