Spring多种加载Bean方式解析

1 定义bean的方式

常见的定义Bean的方式有:

通过xml的方式,例如:

<bean id="dictionaryRelMap" class="java.util.HashMap"/>

通过注解的方式,在Class上使用@Component等注解,例如

@Component
public class xxxServicer{
 ....
}

通过在@Configuration类下的@Bean的方式,例如

@Configuration
public class xxxConfiguration{
 @Bean
 public myBean myBean(){
   return new myBean();
 }
}

虽然这三种定义Bean的方式不一样,对应的处理细节也不一样,但是从大的逻辑上来看,都是一样。主要的流程如下图: 最关键的就是问题就是这么去找到定义Bean的方式,然后生成BeanDefinition后注册到Spring上下文中,由Spring自动创建Bean的实例。

2 BeanDefinition

BeanDefinition是一个接口,用来描述一个Bean实例,例如是SINGLETON还是PROTOTYPE,属性的值是什么,构造函数的参数是什么等。简单来说,通过一个BeanDefinition我们就可以完成一个Bean实例化。 BeanDefinition及其主要的子类:

下面简单说一下各个子类:

  1. RootBeanDefinition和ChildBeanDefinition: 这2个BeanDefinition是相对的关系,自Spring 2.5 出来以后,已经被GenericBeanDefinition代替。因为这样强迫我们在编写代码的时候就必须知道他们之间的关系。
  2. GenericBeanDefinition: 相比于RootBeanDefinition和ChildBeanDefinition在定义的时候就必须硬编码,GenericBeanDefinition的优点可以动态的为GenericBeanDefinition设置parent。
  3. AnnotatedBeanDefinition:看名字就是知道是用来读取通过注解定义Bean。

3 通过xml文件定义Bean

通过xml定义Bean是最早的Spring定义Bean的方式。因此,怎么把xml标签解析为BeanDefinition(), 入口是在org.springframework.beans.factory.xml.XmlBeanDefinitionReader这个类,但是实际干活的是在org.springframework.beans.factory.xml.BeanDefinitionParserDelegate。代码很多,但实际逻辑很简单,就是解析Spring定义的<bean> <property> 等标签 。

4 通过@Component等Spring支持的注解加载Bean

如果要使用@Component等注解定义Bean,一个前提条件是:有<context:component-scan/>或者@ComponentScan注解。但这2个方式还是有一点点区别:

4.1 <context:component-scan/>

由于<context:component-scan/>是一个xml标签,因此是在解析xml,生成的类org.springframework.context.annotation.ComponentScanBeanDefinitionParser,关键代码:

@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
    //获取base-package标签
  String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
  basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
  String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
      ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

  // 实际处理类是ClassPathBeanDefinitionScanner
  ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
  //扫描basePackage下所有的类,如果有@Component等标签就是注册到Spring中
  Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
  registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
  return null;
}

4.2 @ComponentScan

注解对应生成的类是org.springframework.context.annotation.ComponentScanAnnotationParser 其实最后实际干活的还是ClassPathBeanDefinitionScanner这个。ComponentScanAnnotationParser类的生成是伴随着@Configuration这个注解处理过程中(意思说@ComponentScan必须和@Configuration一起使用)。而处理@Configuration其实是org.springframework.context.annotation.ConfigurationClassPostProcessor。是不是感觉有点绕。

其实简单来说,在处理@Configuration的时候发现有@ComponentScan注解,就会生成ComponentScanAnnotationParser去扫描@Component注解

4.3 ClassPathBeanDefinitionScanner

上面说到了,无论注解还是标签的方式,最后都会交给ClassPathBeanDefinitionScanner这个类来处理,这个类做的就是1.扫描basePackage下所有class,如果有@Component等注解,读取@Component相关属性,生成ScannedGenericBeanDefinition,注册到Spring中。

5 通过@Bean方式

前面说了@ComponentScan是在@Configuration处理过程中的一环,既然@Bean注解也是必须和@Configuration一起使用,那么说明@Bean的处理也是在@Configuration中,其实最后是交给ConfigurationClassBeanDefinitionReader这个类来处理的,关键代码:

private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass,
    TrackedConditionEvaluator trackedConditionEvaluator) {

    //如果自己是通过@Import注解定义的,那么需要把自己注册到Spring中
  if (configClass.isImported()) {
    registerBeanDefinitionForImportedConfigurationClass(configClass);
  }
  //这里就是处理方法上的@Bean
  for (BeanMethod beanMethod : configClass.getBeanMethods()) {
    loadBeanDefinitionsForBeanMethod(beanMethod);
  }
  //处理@ImportResource,里面解析xml就是上面说到的解析xml的XmlBeanDefinitionReader
  loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
  loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

6 把BeanDefinition实例化

前面分别说了怎么把不同定义Bean的方式转换为BeanDefinition加入到Spring中去(确切来说是保持在BeanFactory的BeanDefinitionMap中),实例是在ApplicationContext最后阶段,关键代码在DefaultListableBeanFactory中

 @Override
 public void preInstantiateSingletons() throws BeansException {
   for (String beanName : beanNames) {
    RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
    if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
      if (isFactoryBean(beanName)) {
        final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
        boolean isEagerInit;
        if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
          isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
            @Override
            public Boolean run() {
              return ((SmartFactoryBean<?>) factory).isEagerInit();
            }
          }, getAccessControlContext());
        }
        else {
          isEagerInit = (factory instanceof SmartFactoryBean &&
                ((SmartFactoryBean<?>) factory).isEagerInit());
        }
        if (isEagerInit) {
          getBean(beanName);
        }
      }
      else {
        getBean(beanName);
      }
    }
  }
}

通过getBean最后最后实例的代码,在AbstractAutowireCapableBeanFactory中

protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
  //处理xxAware接口
  if (System.getSecurityManager() != null) {
    AccessController.doPrivileged(new PrivilegedAction<Object>() {
      @Override
      public Object run() {
        invokeAwareMethods(beanName, bean);
        return null;
      }
    }, getAccessControlContext());
  }
  else {
    invokeAwareMethods(beanName, bean);
  }
  Object wrappedBean = bean;
  if (mbd == null || !mbd.isSynthetic()) {
    // 调用BeanPostProcessors#postProcessBeforeInitialization
    wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
  }
  try {
    //初始化,先判断是否是InitializingBean,
    invokeInitMethods(beanName, wrappedBean, mbd);
  }
  catch (Throwable ex) {
    throw new BeanCreationException(
        (mbd != null ? mbd.getResourceDescription() : null),
        beanName, "Invocation of init method failed", ex);
  }
  if (mbd == null || !mbd.isSynthetic()) {
    // 调用BeanPostProcessors#postProcessAfterInitialization
    wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
  }
  return wrappedBean;
}

从上面初始化可以看出,InitializeBean和BeanPostProcessors的调用顺序

7 总结

综上分析,Spring加载Bean其实大的思想都是一样的,先读取相关信息生成BeanDefinition,然后通过BeanDefinition初始化Bean。如果知道了上面了套路以后,就可以清楚怎么自定义Xml标签或者自定义注解向Spring中注入Bean。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Spring配置使用之Bean生命周期详解

    基本概念 Spring 中的 Bean 的生命周期,指的是 Bean 从创建到销毁的过程. 下面来探究下几个有关 Bean 生命周期配置的属性. lazy-init lazy-init 表示延迟加载 Bean,默认在 Spring IoC 容器初始化时会实例化所有在配置文件定义的 Bean,若启用了 lazy-init 则在调用 Bean 时才会去创建 Bean. 定义 Bean: public class Animals { public Animals(){ System.out.print

  • Spring自动扫描无法扫描jar包中bean的解决方法

    发现问题 前几天用eclipse打包了一个jar包,jar包里面是定义的Spring的bean. 然后将jar包放到lib下,设置spring的自动扫描这个jar包中的bean,可谁知根本无法扫描到bean,显示错误就是找不到bean,当时就纳闷儿了,为什么扫描不到,结果搜索之后才发现,用eclipse打包jar包要勾选"Add directory entries"才能被Spring正确扫描到,居然有这个说法,呵呵- 不知道 勾选"Add directory entries&

  • SpringMVC中使用bean来接收form表单提交的参数时的注意点

    这是前辈们对于SpringMVC接收表单数据记录下来的总结经验: SpringMVC接收页面表单参数 springmvc请求参数获取的几种方法 下面是我自己在使用时发现的,前辈们没有记录的细节和注意点: 使用bean来接收form表单提交的参数时,pojo中必须含有默认的(即空的)构造函数,同时,需要设置到bean中的变量必须有setter方法. 注:以下代码均为示例代码,非本人实际运行代码,请自行补充. 例如:我有一个bean类是User,具有变量username和password.同时,表单

  • 详解Spring简单容器中的Bean基本加载过程

    本篇将对定义在 XMl 文件中的 bean,从静态的的定义到变成可以使用的对象的过程,即 bean 的加载和获取的过程进行一个整体的了解,不去深究,点到为止,只求对 Spring IOC 的实现过程有一个整体的感知,具体实现细节留到后面用针对性的篇章进行讲解. 首先我们来引入一个 Spring 入门使用示例,假设我们现在定义了一个类 org.zhenchao.framework.MyBean ,我们希望利用 Spring 来管理类对象,这里我们利用 Spring 经典的 XMl 配置文件形式进行

  • 详解Spring中使用xml配置bean的细节

    整理总结刚学的关于spring的xml配置bean的知识. 在ApplicationContext.xml文件中使用bean节点配置bean,bean的属性id在IOC容器中必须是唯一的. <bean id="helloWorld" class="com.test.spring.beans.HelloWorld"> <property name="name" value="Spring"></pr

  • Spring多种加载Bean方式解析

    1 定义bean的方式 常见的定义Bean的方式有: 通过xml的方式,例如: <bean id="dictionaryRelMap" class="java.util.HashMap"/> 通过注解的方式,在Class上使用@Component等注解,例如 @Component public class xxxServicer{ .... } 通过在@Configuration类下的@Bean的方式,例如 @Configuration public c

  • Spring动态加载bean后调用实现方法解析

    前言 在使用Spring的过程中,我们有时候并不想通过xml的方式进行bean的注入,希望在不改变原有的程序结构的基础上添加我们需要的bean,这个时候我们可以利用Spring的spring-beans的jar包里提供的BeanFactoryPostProcessor接口类,通过实现这个接口类,我们可以动态的加载所需要的bean,特别是在引入已经打包在jar里面的程序而没有办法做出修改的情况下,这个接口就为我们提供了及其方便的入口. 因为我是在其他项目的基础上测试的这个接口,所以引入的jar有其

  • spring启动加载程序的几种方法介绍

    关于在spring  容器初始化 bean 和销毁前所做的操作定义方式有三种: 第一种:通过注解@PostConstruct 和 @PreDestroy 方法 实现初始化和销毁bean之前进行的操作 import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; public class DataInitializer{ @PostConstruct public void initMethod() throw

  • 解析spring加载bean流程的方法

    spring作为目前我们开发的基础框架,每天的开发工作基本和他形影不离,作为管理bean的最经典.优秀的框架,它的复杂程度往往令人望而却步.不过作为朝夕相处的框架,我们必须得明白一个问题就是spring是如何加载bean的,我们常在开发中使用的注解比如@Component.@AutoWired.@Socpe等注解,Spring是如何解析的,明白这些原理将有助于我们更深刻的理解spring.需要说明一点的是spring的源码非常精密.复杂,限于篇幅的关系,本篇博客不会细致的分析源码,会采取抽丝剥茧

  • Spring中的spring.factories文件用法(Spring如何加载第三方Bean)

    目录 Spring的spring.factories文件用法 问题 解决 SpringBoot的扩展机制之Spring Factories 什么是 SPI机制 Spring Boot中的SPI机制 Spring Factories实现原理是什么 Spring Factories在Spring Boot中的应用 Spring的spring.factories文件用法 在springBoot中,它自动扫描包的时候,只会扫描自己模块下的类. 问题 如果我们不想被Spring容器管理的Bean的路径下不

  • 关于Spring配置文件加载方式变化引发的异常详解

    目录 问题背景 过程 定位 根因 配置加载顺序 解决 问题背景 我们项目的配置文件一直是通过Apollo进行管理,但是近期由于某些特殊的部署需求,需要使用K8S的原生对象来获取配置,如此一来的话,就需要使用环境变量spring.config.location来指定application.properties文件的路径,以便动态的获取配置. 说明:项目是一个dubbo项目,配置文件中主要包括一些基础组件的配置.以及dubbo相关的配置. 这时候问题来了,在所有配置及代码都没有变化的情况下,如果不指

  • spring boot中的静态资源加载处理方式

    1.spring boot默认资源处理 Spring Boot 默认为我们提供了静态资源处理,使用 WebMvcAutoConfiguration 中的配置各种属性. spring boot默认加载文件的路径是: /META-INF/resources/ /resources/ /static/ /public/ 这些目录下面, 当然我们也可以从spring boot源码也可以看到Java代码: private static final String[] CLASSPATH_RESOURCE_L

  • 使用spring@value加载时机

    目录 spring@value加载时机 说说@Value注入时问题 @Value设置默认值 使用@Value注入配置值的时候报错 spring@value加载时机 @value是spring中一个用来注入类的成员变量的一种注解,其从配置文件中注入配置的值,需要在spring中配置下需要注入这个类PropertyPlaceholderConfigurer,有多种出入方式,如: <bean id="propertyConfigurer" class="PropertyPla

  • 详解Spring ApplicationContext加载过程

    1.找准入口,使用ClassPathXmlApplicationContext的构造方法加载配置文件,用于加载classPath下的配置文件 //第一行,执行完成之后就完成了spring配置文件的加载,刷新spring上下文 ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext( "classpath:spring-mvc.xml"); //获取实例Bean Person person=con

  • spring boot 加载web容器tomcat流程源码分析

    我本地的springboot版本是2.5.1,后面的分析都是基于这个版本 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.1</version> <relativePath/> <!-- lookup parent fr

随机推荐