Spring BeanDefinition的加载

?前面提到AbstractRefreshableApplicationContext在刷新BeanFactory时,会调用loadBeanDefinitions方法以加载系统中Bean的定义,下面将讲解Bean定义的加载过程。

一.XML定义

?XML配置的加载由AbstractXmlApplicationContext实现,方法实现如下:

?主要是实例化了一个XmlBeanDefinitionReader对象,对其设置了Environment对象(具体过程可以上一篇)等后,调用XmlBeanDefinitionReader进行解析。直接跟踪进去,得到如下内容:

?其中参数inputSource为XML配置文件,Resource则该配置文件的描述,主要描述了该配置文件在classpath中的位置和可用于加载该配置文件的加载器。doLoadDocument方法比较简单,主要是加载指定的配置文件,返回一个JDK内置的XML解析Document对象,以便于解析XML DOM节点。

?重点看下registerBeanDefinitions方法,如下:

?该方法最终委托给了BeanDefinitonDocumentReader来完成Bean的解析。在这之前,初始化了一系列相关对象。包括:

  1. 使用DefaultBeanDefinitionDocumentReader作为BeanDefinitionDocumentReader接口的实现
  2. 创建了一个XmlReaderContext用于保存解析过程中使用到的各个相关对象,如资源描述对象Resource、ReaderEventListener事件监听器、XmlBeanDefinitionReader以及NamespaceHandlerResolver命名空间解析器。其中划重点的是DefaultNamespaceHandlerResolver,该类完成了自定义XML格式的解析,后面会有讲解。

?初始相关对象后便将解析过程委托给了DefaultBeanDefinitionDocumentReader来进行处理,该类的重点为parseBeanDefinitions方法,在调用该方法前,先初始化了一个BeanDefinitionParserDelegate对象,如下:

?并将该delegate对象传入了parseBeanDefinitions方法。BeanDefinitionParserDelegate主要提供了命名空间为http://www.springframework.org/schema/beans(下面简写为beans空间)的XML文件解析过程。该命名空间定义了4个主要的XML标签,分别为beansbeanimportalias以及这些标签对应的属性,如下为该命名空间的示例,定义了一个基础的Bean。

?需要注意的是,上面在初始化BeanDefinitionParserDelegate后会先解析XML上<beans>标签上的默认属性,包括:default-lazy-initdefault-mergedefault-autowiredefault-dependency-checkdefault-autowire-candidatesdefault-init-methoddefault-destroy-method这些全局属性。

?下面看下parseBeanBefinitoins方法:

?该方法会对当前节点所属命名空间进行判断,分为默认命名空间和自定义命名空间,其中默认命名空间指的是上面提到的beans空间。对于默认命名空间,会逐一解析每个DOM子节点,判断子节点的命名空间,最终委托给两个方法:处理默认命名空间的parseDefaultElement方法和处理自定义命名空间的parseClustomElement方法。

?parseDefautlElement方法如上, 对beans空间定义的各个标签分别进行了处理:

  1. 解析import标签时,会读取resource属性指定的配置文件,加载后再解析该文件中的bean定义。
  2. 解析alias标签时,读取标签的name和alias属性,添加到BeanRegistry缓存中。
  3. 解析bean标签时,直接委托给BeanDefinitionParserDelegate来处理,过程为:

    1) 获取id属性值作为beanName

    2) 获取name属性值作为aliases,该属性值可以配置多个,以 , 或者 ; 符进行分割,将作为该bean的别名使用;若id值为空,且name不为空,则使用第一个name值作为id值

    3) 检查beanName和aliases的唯一性

    4) 解析bean其他配置,生成GenericBeanDefinition对象

    5) 若beanName为空,则为其分配一个

    对于步骤3.d,处理过程为:

    1) 获取class属性值

    2) 获取parent属性值

    3) 初始化GenericBeanDefinition实例

    4) 解析bean节点的属性,设置到BeanDefinition中,包括:scopeabstractlazy-initautowireddependency-checkautowire-candidateprimaryinit-methoddestroy-methodfactory-methodfactory-bean

    5) 解析description子节点,获取值设置bean的描述内容

    6) 解析meta子节点列表,获取key、value值设置附加元数据信息

    7) 解析lookup-method子节点列表,获取name、bean值设置方法注入信息

    8) 解析replaced-method子节点列表,获取值设置需要动态代理的方法信息

    9) 解析constructor-arg子节点列表,获取值设置构造参数信息

    10) 解析property子节点列表,获取值设置属性信息

    11) 解析qualifier子节点列表,获取值进行设置

?上面解析完bean的配置后,会再处理子节点中其他命名空间的配置,使用NamespaceHandler的decorate方法,用以修改Bean定义内容,这部分将使用下面的内容,放在后面一起讲。

  1. 解析beans标签时,会进行递归处理

?如上,默认命名空间主要用于解析bean的定义,经过上面的处理,bean定义的解析就已经完成,会将实例对象注册到上下文中进行保存

?下面介绍parseClustomElement方法,顾名思义,该方法主要用来处理自定义命名空间的XML标签的,可以当做是spring XML配置处理的一种扩展手段,如下,为该方法的内容:

?主要委托给了NamespaceHandlerResolver,通过查找到对应节点对应命名空间的Handler,调用该Handler的parse方法进行处理。

?前面说过,NamespaceHandlerResolver使用了DefaultNamespaceHandlerResolver作为实现,跟踪resolve方法进去如下:

?过程为:

  1. 获取已有的Handler处理列表,返回结果为一个Map,Key为XML命名空间,值可能为代表对应Handler类型的Spring对象或者已经实例化后的Handler对象,取决于之前是否已经调用过
  2. 若指为Handler对象,则直接返回
  3. 若为String对象,表明未初始化过,则初始化该类,并执行init初始化方法,然后将其重新返回Map中

?对于第(1)步中Handler处理列表的获取,Spring会扫描classpath中所有位于META-INF中的spring.handlers配置文件,将所有内容读取到一个Map中并返回。

?如上,为spring-context模块提供的spring.handlers文件,提供了该模块自定义命名空间标签的支持。如下为自定义命名空间的例子:

?主要引入了context空间的spring-configured标签和annotation-config标签。

?至此,介绍了XML配置下的bean解析。

二、注解配置

?下面介绍Spring以注解的方式进行bean加载的过程,如下,为开启注解加载所需要的配置:

?根据前面的内容,component-scanhttp://www.springframework.org/schema/context命名空间中的标签,处理对象在spring-context模块的spring.handlers文件中定义,对应的是类org.springframework.context.config.ContextNamespaceHandler,如下:

?查看该类,可以知道,component-scan由ComponentScanBeanDefinitionParser处理,如下:

?主要过程为:

  1. 获取base-package属性内容赋值给basePackage
  2. 替换basePackage中的占位符内容
  3. 根据 , ; \t \n 分割符分割basePackage,得到多个包路径
  4. 解析component-scan配置内容,返回ClassPathBeanDefinitionScanner对象

    1) 解析设置use-default-filters参数

    2) 解析设置resource-pattern参数

    3) 解析设置name-generator参数

    4) 解析设置scope-resolver、scoped-proxy等参数

    5) 解析设置include-filterexclude-filter等参数, ClassPathBeanDefinitionScanner对象再初始化时默认增加了org.springframework.stereotype. Component、javax.annotation.ManagedBean和javax.inject.Named几种注解

  5. 调用ClassPathBeanDefinitionScanner的doScan方法执行扫描,将符合的类并注册到上下文中然后返回
  6. 解析annotation-config参数,如果为true(默认为true)则自动注册一系列用于后置解析的注解处理类定义到上下文中
    这里重点看下第(5)步和第(6)步

?第(5)执行扫描时,会遍历第(3)步返回的所有路径,对于每个路径,会调用父类ClassPathScanningCandidateComponentProvider的findCandidateComponents方法,返回该路径下所有符合要求的Bean,如下:

?ClassPathScanningCandidateComponentProvider会找到指定路径所有的类,包装为Resource[]对象,对于每个Resource,会使用SimpleMetadataReaderFactory工厂类为每个Resouce对象新建一个SimpleMetadataReader对象,该对象用于解析类的信息,需要注意的是,在初始化SimpleMetadataReader对象的时候就会执行解析动作,将结果存为ClassMetadata数据和AnnotationMetadata数据,前者用于存储类的定义信息,包括类名,是否接口,包含的属性等信息,后者则包含所有的注解信息。获取SimpleMetadataReader对象后,会判断该类是否符合component-scanner定义的include-filterexclude-filter中定义的内容,注意,默认包含了@Component等对象,所以默认会加载所有有@Component注解且所有有@Component元注解注解(如@Service@Repository)的类。若符合要求,则将该类包装为ScannedGenericBeanDefinition对象,同时会检查该类不能为一个接口且不能依赖一个内部非静态类,若符合,则添加到待返回列表中。

?执行完上面的findCandidateComponents方法后,会为其分配一个beanName,用于内部使用,之后会调用AnnotationConfigUtils的processCommonDefinitionAnnotations方法,该方法会对上面返回的BeanDefinition解析一些基本的注解属性并进行设置,包括@Lazy@Primary@DependsOn@Role@Description。完成该步后会判断该bean是否已经存在,若不存在,则添加到上下文中。

?第(6)步的代码如下:

?重点在后半部分,会调用AnnotationConfigUtils的registerAnnotationConfigProcessors方法,该方法添加了一系列用于后置解析的注解处理类定义到上下文中,包括:

  1. 添加ConfigurationClassPostProcessor处理器(BeanDefinitionRegistryPostProcessor),添加@Configuration功能,对应bean为org.springframework.context.annotation.internalConfigurationAnnotationProcessor
  2. 添加AutowiredAnnotationBeanPostProcessor处理器(SmartInstantiationAwareBeanPostProcessor、MergedBeanDefinitionPostProcessor),添加@Autowired@Value功能,对应bean为org.springframework.context.annotation.internalAutowiredAnnotationProcessor
  3. 添加RequiredAnnotationBeanPostProcessor处理器(SmartInstantiationAwareBeanPostProcessor、MergedBeanDefinitionPostProcessor),添加@Required功能,对应bean为org.springframework.context.annotation.internalRequiredAnnotationProcessor
  4. 添加CommonAnnotationBeanPostProcessor处理器(InstantiationAwareBeanPostProcessor 、DestructionAwareBeanPostProcessor、MergedBeanDefinitionPostProcessor、BeanPostProcessor),添加@PostConstruct@PreDestroy功能,对应bean为org.springframework.context.annotation.internalCommonAnnotationProcessor
  5. 添加PersistenceAnnotationBeanPostProcessor处理器(InstantiationAwareBeanPostProcessor、DestructionAwareBeanPostProcessor、MergedBeanDefinitionPostProcessor),添加@PersistenceUnit@PersistenceContext功能,对应bean为org.springframework.context.annotation.internalPersistenceAnnotationProcessor
  6. 添加EventListenerMethodProcessor,添加@EventListener功能,对应bean为org.springframework.context.event.internalEventListenerProcessor
  7. 添加DefaultEventListenerFactory,对应bean为org.springframework.context.event.internalEventListenerFactory

?以上各个bean在添加前都会先判断是否已经存在改定义,若不存在才增加,因而可以通过在添加相应bean的方式,修改对应的处理功能。

?PS:第(6)步添加注解后置处理的方法其实也是annotation-config这个标签功能的主要处理方法。由上可知annotation-config的处理类为AnnotationConfigBeanDefinitionParser,该类内部其实也是调用了AnnotationConfigUtils的registerAnnotationConfigProcessors方法来完成注解的功能,具体代码如下:

三、接口回调

?结合之前Spring启动的内容,接上上面的内容,可以得到如下的接口回调顺序

?因为InstantiationAwareBeanPostProcessor、DestructionAwareBeanPostProcessor等接口继承自MergedBeanDefinitionPostProcessor接口,MergedBeanDefinitionPostProcessor接口继承自BeanPostrProcessor,实现类上存在重叠,这里先根据讲解顺序排序。

个人公众号:啊驼

原文地址:https://www.cnblogs.com/cxyAtuo/p/11622367.html

时间: 2024-08-29 09:04:46

Spring BeanDefinition的加载的相关文章

深入Spring 自定义注解加载和使用

前言 在工作中经常使用Spring的相关框架,免不了去看一下Spring的实现方法,了解一下Spring内部的处理逻辑.特别是开发Web应用时,我们会频繁的定义@Controller,@Service等JavaBean组件,通过注解,Spring自动扫描加载了这些组件,并提供相关的服务.Spring是如何读取注解信息,并注入到bean容器中的,本文就是通过嵌入Spring的Bean加载,来描述Spring的实现方法.完整的例子都在Github上了. 自定义注解 先看一个最简单的例子,在使用Spr

监听器如何获取Spring配置文件(加载生成Spring容器)

Spring容器是生成Bean的工厂,我们在做项目的时候,会用到监听器去获取spring的配置文件,然后从中拿出我们需要的bean出来,比如做网站首页,假设商品的后台业务逻辑都做好了,我们需要创建一个监听器,在项目启动时将首页的数据查询出来放到application里,即在监听器里调用后台商品业务逻辑的方法,也就是说我们需要在监听器里获取Spring中配置的相应的bean.先把监听器创建出来: 1. 创建InitDataListener 创建一个监听器InitDataListener继承Serv

spring不断重新加载所有类文件

第一次写博文,可能有点兴奋和啰嗦,先说说使用的环境:resin+spring+struts,服务器是测试用的,由运维同事维护,里面有几个项目,仅供开发的同事修改. 事情从昨天下午开始, 查看测试服务器log时,发现spring不断的加载类文件,但却没有任何错误信息提示,初步判断应该是某开发的同事修改了配置文件导致的问题,但看了一遍没发现什么问题,难道服务器作了什么动作?于是问了一名运维同事,说没作什么修改,一听心血来潮,因为这个问题可以加入我的问题库里面. 因为配置正常,怀疑是某些类加载不正常导

Spring mvc应用 加载静态资源的几种方式

总结几种Spring mvc应用加载静态资源的方式 1.使用服务器的默认Servlet处理 对于不同的服务器,处理静态资源的servlet-name不一样,需要去看服务器具体的配置文件 比如resin-3.1.12,通过查看app-default.xml可以看到默认处理jsp的servlet-name为>resin-jsp 所以可以通过在web.xml中添加静态资源的访问 <servlet-mapping>        <servlet-name>resin-jsp<

【Spring】Junit加载Spring容器作单元测试(转)

[Spring]Junit加载Spring容器作单元测试 阅读目录 > 基本的搭建 > 常见的用法 如果我们需要对我们的Service方法作单元测试,恰好又是用Spring作为IOC容器的,我们可以这么配置Junit加载Spring容器,方便做单元测试. > 基本的搭建 (1)引入所需的包 <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> &

spring 如何动态加载properties文件的内容

1. 在xml中配置你的properties路径: <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <property name="basenames"> <list> <!-- 指定资源文件基名称 jdbc为文件名,不包含扩展名 --&g

spring 配置文件被加载两次

如下web.xml示例: 1.用spring的配置加载contextConfigLocation 2.配置spring-mvc的contextConfigLocation <servlet> <servlet-name>spring-mvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init

Spring Boot 配置加载顺序详解

使用 Spring Boot 会涉及到各种各样的配置,如开发.测试.线上就至少 3 套配置信息了.Spring Boot 可以轻松的帮助我们使用相同的代码就能使开发.测试.线上环境使用不同的配置. 在 Spring Boot 里面,可以使用以下几种方式来加载配置.本章内容基于 Spring Boot 2.0 进行详解. 1.properties文件: 2.YAML文件: 3.系统环境变量: 4.命令行参数: 等等-- 我们可以在 Spring Beans 里面直接使用这些配置文件中加载的值,如:

Spring Boot 启动加载数据 CommandLineRunner

实际应用中,我们会有在项目服务启动的时候就去加载一些数据或做一些事情这样的需求. 为了解决这样的问题,spring Boot 为我们提供了一个方法,通过实现接口 CommandLineRunner 来实现. 很简单,只需要一个类就可以,无需其他配置. 创建实现接口 CommandLineRunner 的类 Spring Boot应用程序在启动后,会遍历CommandLineRunner接口的实例并运行它们的run方法.也可以利用@Order注解(或者实现Order接口)来规定所有CommandL