【重写SpringFramework】组件加载与AnnotationConfigApplicationContext(chapter 3-4)
1. 前言
上一节介绍了 Spring 是如何将资源读取到内存中的,这里的资源是指广泛存在于各种介质中的数据。对于字节码文件来说,只拿到输入流或字节数组还不够,还需要知道类的信息。一是 JVM 类加载机制,二是通过 ASM 框架,Spring 对这两种解析方式进行了统一的抽象,相关的 API 定义在 Spring 核心包中。
在加载 class 文件、解析类的信息这两步之后,最后一步注册 BeanDefinition 的操作是现成的,调用 BeanDefinitionRegistry 接口的 registerBeanDefinition 方法即可。现在的问题是,之前我们一直使用 RootBeanDefinition 来创建实例,但这只是方便测试的权宜之计,一个大型应用可能有成百上千个组件,我们不可能一个个地手动创建。鉴于此,Spring 提供了强大易用的组件,不仅可以批量地处理字节码文件,还可以将以上三个步骤封装起来,提供一站式的解决方案。
2. AnnotatedBeanDefinition
在 BeanDefinition 的继承体系中,AnnotatedBeanDefinition 接口的作用是以注解声明的方式获取 BeanDefinition。由于一部分实现类在 context 模块,当时没有展开来讲。AnnotatedBeanDefinition 接口定义了两个方法,简单介绍如下:
getMetadata方法:获取类的元数据getFactoryMethodMetadata方法:获取工厂方法的元数据(部分子类不支持工厂方法,直接返回 null)
public interface AnnotatedBeanDefinition extends BeanDefinition {
AnnotationMetadata getMetadata();
MethodMetadata getFactoryMethodMetadata();
}
AnnotatedBeanDefinition 接口有三个实现类,它们的结构和功能基本类似,主要是从用途和解析方式两个方面来分析。其特点如下:
AnnotatedGenericBeanDefinition:加载指定的类或元数据,支持反射和 ASM 解析(存在于 beans 模块中)ScannedGenericBeanDefinition:通过扫描的方式加载BeanDefinition,仅支持 ASM 解析ConfigurationClassBeanDefinition:仅限配置类内部使用,负责处理 BeanMethod,支持反射和 ASM 解析(本节不涉及)

3. AnnotatedBeanDefinitionReader
3.1 概述
AnnotatedBeanDefinitionReader 组件的作用是加载指定的 Bean,从功能上看与手动创建 BeanDefinition 实例差不多,但实际上有两点区别:
- 自动注册了常用的组件,比如处理依赖注入和生命周期的
BeanPostProcessor组件 - 支持对
@Role、@Primary等辅助性注解进行解析,并设置成BeanDefinition对应的role、primary等属性
AnnotatedBeanDefinitionReader 持有一个 BeanDefinitionRegistry 实例,实际上是 Spring 容器,用于注册 BeanDefinition。在构造方法中,通过 AnnotationConfigUtils 工具类注册了常用的 BeanPostProcessor 组件。
public class AnnotatedBeanDefinitionReader {
private final BeanDefinitionRegistry registry;
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
this.registry = registry;
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
}
AnnotationConfigUtils 是一个工具类,提供了与注解配置相关的方法。registerAnnotationConfigProcessors 方法的作用是注册相关的 BeanPostProcessor,比如支持依赖注入以及生命周期相关的组件。
public class AnnotationConfigUtils {
public static void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) {
//注册支持@Autowired和@Value注解的组件
if(!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)){
RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME);
}
//注册支持@PostConstruct和@PreDestroy注解的组件
if (!registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(InitDestroyAnnotationBeanPostProcessor.class);
registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME);
}
}
}
3.2 register方法
register 方法用于注册指定的类,参数是 Class 类型,说明是通过反射的方式来加载类。
public void register(Class<?>... annotatedClasses) {
for (Class<?> annotatedClass : annotatedClasses) {
registerBean(annotatedClass, null);
}
}
registerBean 方法完成了实际的加载逻辑,整个过程分为三步,如下所示:
-
根据条件判断是否加载,有关条件判定的内容将在后续介绍,先略过。
-
调用工具类
AnnotationConfigUtils的processCommonDefinitionAnnotations方法,根据@Role、@Primary等注解来设置相关属性。 -
生成 Bean 的名称,将
BeanDefinition注册到 Spring 容器中。
private void registerBean(Class<?> annotatedClass, String name) {
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
//1. 根据条件判定Bean是否加载(TODO 先略过,详见条件判定一节)
//2. 根据@Role、@Primary等注解的信息来设置BeanDefinition的相关属性
AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
//3. 注册BeanDefinition
String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd));
registry.registerBeanDefinition(beanName, abd);
}
4. ClassPathBeanDefinitionScanner
4.1 概述
ClassPathBeanDefinitionScanner 的作用是扫描指定目录及其子目录下的文件,对符合条件的类进行过滤,并注册到 Spring 容器中。扫描组件是一个批量操作,Spring 提供了一组注解来标识哪些类应该被加载。@Component 是一个元注解,可以声明在子注解上,因此 @Component 代表的是一组注解。cn.stimd.spring.context.stereotype 目录中定义了一部分子注解,如下所示:
-
@Controller表示该组件是一个处理网络请求的接口类 -
@Service表示组件是一个业务类 -
@Repository表示组件与持久化有关,比如数据库相关的增删改查等操作
这些注解的共同特点是携带「语义化」,所谓语义化是指组件在系统内充当了某一角色。与之对比,@Component 没有具体的语义,泛指所有可以被 Spring 容器管理的组件。理论上,我们可以使用 @Component 注解完成所有的工作,但对一部分有着特殊用途的组件进行分类是良好的编程风格。
4.2 基本结构
ClassPathBeanDefinitionScanner 是 ClassPathScanningCandidateComponentProvider 的子类,两者的分工不同,父类只负责扫描类文件并转换成 BeanDefinition 集合,子类则将所有流程整合在一起。父类 ClassPathScanningCandidateComponentProvider 的主要属性如下所示:
includeFilters:包含规则,允许哪些类被加载,比如声明了@Component等注解excludeFilters:排除规则,不允许哪些类被加载,这个需要自定义的指定resourcePatternResolver:用于路径解析和资源加载metadataReaderFactory:创建基于 ASM 元数据解析的工厂
一个目录下可能有多个类,那么哪些类应该加载,哪些类不应该加载,需要一个判断的依据。Spring 提供了 TypeFilter 来对类型进行过滤,includeFilters 属性是允许加载的过滤器集合,excludeFilters 是禁止加载的过滤器集合。为了方便称呼,将它们称为「包含规则」和「排除规则」。registerDefaultFilters 是一个重要的方法,作用是向包含规则中添加了一个过滤器,允许加载声明了 @Component 注解的类。
public class ClassPathScanningCandidateComponentProvider implements ResourceLoaderAware {
private final List<TypeFilter> includeFilters = new LinkedList<>();
private final List<TypeFilter> excludeFilters = new LinkedList<>();
private ResourcePatternResolver resourcePatternResolver;
private MetadataReaderFactory metadataReaderFactory;
private Environment environment;
//注册默认的包含过滤器
protected void registerDefaultFilters(){
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
}
}
子类 ClassPathBeanDefinitionScanner 持有一个 BeanDefinitionRegistry 实例,作用是将已加载 BeanDefinition 集合注册到 Spring 容器中。该类在构造方法中调用了父类的 registerDefaultFilters 方法,支持注册声明了 @Component 注解的组件。
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
private final BeanDefinitionRegistry registry;
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, Environment environment) {
this.registry = registry;
registerDefaultFilters(); //注册默认的TypeFilter,允许加载@Component声明的类
setResourceLoader((registry instanceof ResourceLoader ? (ResourceLoader) registry : null));
setEnvironment(environment);
}
}
4.3 scan 方法
子类 ClassPathBeanDefinitionScanner 的 scan 方法是入口方法,实际的处理逻辑是由 doScan 方法完成的,然后使用工具类 AnnotationConfigUtils 注册相关的后处理器。
public void scan(String... basePackages){
doScan(basePackages);
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
doScan 方法具体的处理过程分为三步,其中,后两步与 AnnotatedBeanDefinitionReader 基本相同,我们重点关心第一步是如何实现的。具体步骤如下:
- 扫描指定目录下,加载文件并转换成
BeanDefinition - 生成 Bean 的名称,并根据
@Role、@Primary等注解来设置相关属性 - 将
BeanDefinition注册到 Spring 容器中
protected Set<BeanDefinitionHolder> doScan(String[] basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = new HashSet<>();
for(String basePackage : basePackages){
//1. 扫描指定目录,加载class文件,具体逻辑由父类完成
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
//2. 解析@Lazy、@Primary、@DependsOn、@Role、@Description等注解,为BeanDefinition添加相应属性
String beanName = this.beanNameGenerator.generateBeanName(candidate); //生成Bean的名称
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
//3. 检查是否与已存在的Bean冲突,如果没有则将BeanDefinition添加到容器中
if(!this.registry.containsBeanDefinition(beanName)){
registry.registerBeanDefinition(beanName, candidate);
BeanDefinitionHolder holder = new BeanDefinitionHolder(candidate, beanName);
beanDefinitions.add(holder);
}
}
}
return beanDefinitions;
}
4.4 加载和解析组件
findCandidateComponents 方法是由父类 ClassPathScanningCandidateComponentProvider 实现的,可以分为三步:
-
加载指定目录的资源。
packageSearchPath表示需要扫描的路径,格式为classpath*:/xx/**/*.class,表示任意类路径下的、指定包路径下的、任意层级目录下的、任意名称的 class 文件。 -
使用
MetadataReaderFactory以 ASM 的方式获取注解元数据。 -
过滤出符合条件的类。这里有两层检查,首先是特殊规则的检查,然后是通用的检查。当候选项通过所有的检查,就可以作为
BeanDefinition稍后注册到 Spring 容器中。
//扫描指定目录,加载候选的组件
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
//1. 加载指定目录下的资源
basePackage = basePackage.replace('.', '/'); //将包名的.分隔符换成/
//格式为classpath*:/xx/**/*.class,表示任意类路径下的、指定包路径下的、任意层级目录下的、任意名称的class文件
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + basePackage + '/' + DEFAULT_RESOURCE_PATTERN;
Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
for (Resource resource : resources) {
//2. 将加载的字节流以ASM的方式转换成Class元数据
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
//3. 过滤出符合条件的类,创建BeanDefinition
//3.1 根据TypeFilter和Condition过滤元数据
if(isCandidateComponent(metadataReader)){
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
//3.2 检查是否为实现类或静态内部类
if(isCandidateComponent(sbd)){
candidates.add(sbd);
}
}
}
return candidates;
}
我们需要对加载的元数据进行过滤,首先排除掉符合 excludeFilters 的候选项,然后让符合 includeFilters 的候选项通过。由此可见,排除规则的优先级高于包含规则。包含规则有一条默认的检查,之前已经提到过,就是检查类是否声明了 @Component 注解。此外,还需要进行条件判定,这部分内容先略过。
第一个 isCandidateComponent 方法是自定义检查,通过定义包含和排除规则,再配合条件判定,非常灵活。第二个 isCandidateComponent 方法是通用的检查,必须确保通过扫描加载的是一个普通类或静态内部类。
//3.1 根据排除或包含规则、以及条件判定,过滤出符合条件的候选项
private boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
//排除需要过滤掉的项(exclude规则的优先级高于include规则)
for(TypeFilter tf: this.excludeFilters){
if(tf.match(metadataReader, this.metadataReaderFactory)){
return false;
}
}
//加载指定规则的项,比如使用@Component注解声明的类
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return true;
//进一步检查Condition(TODO 详见条件判定一节)
}
}
return false;
}
//3.2 通用检查,确保Bean是普通类或静态内部类
//1) 必须是独立的,即普通类或静态内部类
//2) 必须是具体的,即非抽象类或接口
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
return metadata.isIndependent() && metadata.isConcrete();
}
最后,回顾一下组件扫描的主要流程,如图所示。

5. ApplicationContext 实现
AnnotationConfigApplicationContext 是通过注解声明的方式进行配置,这是本章最重要的 ApplicationContext 实现类。该类继承自 GenericApplicationContext,最大的特点是持有 AnnotatedBeanDefinitionReader 和 ClassPathBeanDefinitionScanner 这两个组件,拥有了多种方式来加载 Bean 的能力。
public class AnnotationConfigApplicationContext extends GenericApplicationContext {
private final AnnotatedBeanDefinitionReader reader;
private final ClassPathBeanDefinitionScanner scanner;
public AnnotationConfigApplicationContext(){
this.scanner = new ClassPathBeanDefinitionScanner(this);
this.reader = new AnnotatedBeanDefinitionReader(this);
}
}
6. 测试
6.1 加载指定的类
首先是准备工作。UserService 类使用 @Service 注解声明,表示一个业务类。UserController 使用 @Controller 注解声明,表示一个接口类。UserController 还通过依赖注入的方式持有 UserService 的实例。
//测试类,模拟业务层
@Service
public class UserService {
public User getUser(){
return new User();
}
}
//测试类,模拟控制层
@Controller
public class UserController {
@Autowired
private UserService userService;
public User getUser(){
return this.userService.getUser();
}
}
测试方法比较简单,首先创建 AnnotationConfigApplicationContext 实例,然后调用 register 方法完成了 UserService 的注册,再也不需要直接创建 RootBeanDefinition 实例。
//测试方法
@Test
public void testAnnotatedReader() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(UserService.class);
context.refresh();
UserService userService = context.getBean("userService", UserService.class);
System.out.println("annotatedReader测试: " + userService.getUser());
}
从测试结果可以看到,UserService 被注册到了 Spring 容器中。
annotatedReader测试: context.basic.User@59e84876
6.2 类路径扫描
创建 AnnotationConfigApplicationContext 实例,调用 scan 方法扫描指定的目录,加载所有声明了 @Component 注解(及其子注解)的类,包括 UserController 和 UserService。由于 AnnotationConfigUtils 工具类自动注册了 AutowiredAnnotationBeanPostProcessor,在获取 Bean 的流程中完成了依赖注入的功能。
//测试方法
@Test
public void testClassPathScanner(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.scan("context.basic");
context.refresh();
UserController userController = (UserController) context.getBean("userController");
System.out.println("ClasspathScan测试: " + userController.getUser());
}
从测试结果可以看到,Spring 容器自动加载了 UserController 和 UserService 两个单例,并完成了依赖注入的操作。
ClasspathScan测试: context.basic.User@26be92ad
7. 总结
我们知道,加载 Bean 的流程分为三步,资源加载、资源解析和注册 BeanDefinition,上一节实现了前两步,而第三步是现成的,本节的目标是将这三步整合起来。Spring 将这三个步骤封装到 AnnotatedBeanDefinitionReader 和 ClassPathBeanDefinitionScanner 中,AnnotationConfigApplicationContext 作为最重要的实现类之一,持有这两个组件,从而拥有了通过注解声明的方式来启动应用的能力。
此外,Spring 使用注解元数据来描述类的信息,RootBeanDefinition 并不能体现出注解元数据的特性,而是通过 AnnotatedBeanDefinition 接口的实现类完成的。具体来说,AnnotatedGenericBeanDefinition 类专门用于 AnnotatedBeanDefinitionReader 组件,ScannedGenericBeanDefinition 类专门用于 ClassPathBeanDefinitionScanner 组件。
8. 项目结构
新增修改一览,新增(15),修改(1)。
context
└─ src
├─ main
│ └─ java
│ └─ cn.stimd.spring.context
│ ├─ annotation
│ │ ├─ AnnotatedBeanDefinitionReader.java (+)
│ │ ├─ AnnotationBeanNameGenerator.java (+)
│ │ ├─ AnnotationConfigApplicationContext.java (+)
│ │ ├─ AnnotationConfigUtils.java (+)
│ │ ├─ ClassPathBeanDefinitionScanner.java (+)
│ │ ├─ ClassPathScanningCandidateComponentProvider.java (+)
│ │ ├─ Primary.java (+)
│ │ ├─ Role.java (+)
│ │ └─ ScannedGenericBeanDefinition.java (+)
│ └─ stereotype
│ ├─ Component.java (+)
│ ├─ Controller.java (+)
│ ├─ Repository.java (+)
│ └─ Service.java (+)
└─ test
└─ java
└─ context
└─ basic
├─ BasicTest.java (*)
├─ UserController.java (+)
└─ UserService.java (+)
注:+号表示新增、*表示修改
注:项目的 master 分支会跟随教程的进度不断更新,如果想查看某一节的代码,请选择对应小节的分支代码。
欢迎关注公众号【Java编程探微】,加群一起讨论。
原创不易,觉得内容不错请关注、点赞、收藏。
转载自:https://juejin.cn/post/7402060970426368009