• 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及其主要的子类:

    • RootBeanDefinition和ChildBeanDefinition: 这2个BeanDefinition是相对的关系,自Spring 2.5 出来以后,已经被GenericBeanDefinition代替。因为这样强迫我们在编写代码的时候就必须知道他们之间的关系。
    • GenericBeanDefinition: 相比于RootBeanDefinition和ChildBeanDefinition在定义的时候就必须硬编码,GenericBeanDefinition的优点可以动态的为GenericBeanDefinition设置parent。
    • 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> 等标签 。 之前写过一篇文章介绍过如何自定义Spring标签 ,并解析后注册到Spring中——传送门

    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中),实例化这些BeanDefinition是在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实例化BeanFactory,代码是在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接口的方法(以及Init-method)和BeanPostProcessors的调用顺序

    7 总结

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

     
  • 相关阅读:
    VS项目配置halcon、opencv等(附加包含目录、包含目录等)
    Qt 异常总结(ubuntu环境)
    linux简单分区
    U盘启动盘制作工具
    Ubuntu常用命令
    Ubuntu安装后的基本设置(系统主题,终端字体,更换源,浏览器等)
    makefile
    Ubuntu终端执行单个cpp
    Windbg内核调试之四: Dump文件分析
    浅议Windows 2000/XP Pagefile组织管理
  • 原文地址:https://www.cnblogs.com/lizo/p/6759080.html
Copyright © 2020-2023  润新知