文章目录
  1. 1. 使用注解初始化bean的种类
    1. 1.1. AnnotationConfigApplicationContext启动初始化
    2. 1.2. 使用<\context:annotation-config/> 标签
    3. 1.3. 使用<\context:component-scan/>标签
  2. 2. 解析标签

实际开发项目中,配置bean一般都是使用注解,还是单独拿出来详细分析下,也把前面没有说的注入方式,全部集中到这里分析了。

使用注解初始化bean的种类

注解初始化bean大致有三种配置:

AnnotationConfigApplicationContext启动初始化

使用AnnotationConfigApplicationContext或者AnnotationConfigWebApplicationContext启动的时候注册带Configuration注解的类,如下所示:

 @Configuration
 public class AppConfig {
     @Bean
     public MyBean myBean() {
         // instantiate, configure and return bean ...
     }
 }

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
 ctx.register(AppConfig.class);
 ctx.refresh();
 MyBean myBean = ctx.getBean(MyBean.class);

使用<\context:annotation-config/> 标签

在配置文件中声明:

<beans>
   <context:annotation-config/>
   <bean class="com.acme.AppConfig"/>
</beans>

使用<\context:component-scan/>标签

@Configuration
 public class AppConfig {
     @Bean
     public MyBean myBean() {
         // instantiate, configure and return bean ...
     }
 }

这3种方式都是将bean集中到一个配置对象,现在比较流行的一种就是第三种方式,使用context:component-scan/,现在就是这个标签为例,深入分析spring如何构造内置对象,以及把这些包路径下的bean注册到容器中的。

解析标签

参考《spring源码分析之基于xml配置文件的bean初始化分析》,在XmlBeanDefinitionReader创建XmlReaderContext对象的时候,就把默认路径META-INF/spring.handlers赋值给DefaultNamespaceHandlerResolver。DefaultBeanDefinitionDocumentReader在解析xml有两种类型标签,一种是符合schema规范的标签,另外一种就是非bean标签,参考DefaultBeanDefinitionDocumentReader:

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    if (delegate.isDefaultNamespace(root)) {
        NodeList nl = root.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            if (node instanceof Element) {
                Element ele = (Element) node;
                //判断是否符合bean标签约束
                if (delegate.isDefaultNamespace(ele)) {
                    parseDefaultElement(ele, delegate);
                }
                else {
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }
    else {
        delegate.parseCustomElement(root);
    }
}

DefaultNamespaceHandlerResolver就根据xml命名空间获取到对应的NamespaceHandler,然后初始化并扫描包,把带注解的bean注册到BeanFactory,参考BeanDefinitionParserDelegate:

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
    String namespaceUri = getNamespaceURI(ele);
    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
    if (handler == null) {
        error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
        return null;
    }
    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

context:component-scan/属于”http://www.springframework.org/schema/context" 命名空间,在spring.handlers文件中对应的是org.springframework.context.config.ContextNamespaceHandler:

@Override
public void init() {
    registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
    registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
    registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
    registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
    registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
    registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
    registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
    registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
}

这下就简单了,component-scan标签对应的是ComponentScanBeanDefinitionParser解析器,查看parse方法:

@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
    String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
    basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
    String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
            ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

    // Actually scan for bean definitions and register them.
    //初始化时,默认的注解过滤器包含Component以及JEE标准的两个注解(ManagedBean、Named)
    ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
    Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
    registerComponents(parserContext.getReaderContext(), beanDefinitions, element);

    return null;
}

有时候业务扩展了,里面的bean也随之增多,但是有可能最开始配置的扫描是整个项目,或者各个模块都配置了扫描包的标签,那会不会有影响呢?如果是全局扫描启动肯定有影响,spring内部是把<context:component-scan base-package=”…”/>base-packeage配置路径下的所有.class文件扫描,然后把这些路径缓存起来,再用初始化配置的注解filter过滤需要的bean,刚才分析了,如果没有特别的属性声明,默认就扫描Component注解(Controller、Service、Repository是Component的子类带这些注解的都会扫描),参考ClassPathScanningCandidateComponentProvider代码:

public Set<BeanDefinition> findCandidateComponents(String basePackage) {
。。。
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
                        if (isCandidateComponent(metadataReader)) {
                            //创建AnnotatedBeanDefinition类型的bean
                            ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                            sbd.setResource(resource);
                            sbd.setSource(resource);
                            if (isCandidateComponent(sbd)) {
                                if (debugEnabled) {
                                    logger.debug("Identified candidate component class: " + resource);
                                }
                                candidates.add(sbd);
。。。
}

在ClassPathScanningCandidateComponentProvider内部可以看到有个CachingMetadataReaderFactory,这个类在获取元素读取器的时候,是根据类路径去读取的,但是缓存了类路径到LinkedHashMap,所以如果有多个类路径重复了,后面重复的路径读取的是缓存中的MetadataReader,所以<context:component-scan base-package=”…”/>配置了相同的路径,效率不会有影响。最后生成ScannedGenericBeanDefinition,并注册到DefaultListableBeanFactory,参考ComponentScanBeanDefinitionParser:

protected void registerComponents(
        XmlReaderContext readerContext, Set<BeanDefinitionHolder> beanDefinitions, Element element) {

    Object source = readerContext.extractSource(element);
    CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), source);

    for (BeanDefinitionHolder beanDefHolder : beanDefinitions) {
        compositeDef.addNestedComponent(new BeanComponentDefinition(beanDefHolder));
    }

    // Register annotation config processors, if necessary.
    boolean annotationConfig = true;
    if (element.hasAttribute(ANNOTATION_CONFIG_ATTRIBUTE)) {
        annotationConfig = Boolean.valueOf(element.getAttribute(ANNOTATION_CONFIG_ATTRIBUTE));
    }
    if (annotationConfig) {
        //注册BeanPostProcessor,实例化bean依赖会用到
        Set<BeanDefinitionHolder> processorDefinitions =
                AnnotationConfigUtils.registerAnnotationConfigProcessors(readerContext.getRegistry(), source);
        for (BeanDefinitionHolder processorDefinition : processorDefinitions) {
            compositeDef.addNestedComponent(new BeanComponentDefinition(processorDefinition));
        }
    }

    readerContext.fireComponentRegistered(compositeDef);
}

AnnotationConfigUtils.registerAnnotationConfigProcessors(readerContext.getRegistry(), source)这段代码就是把配置bean相关的注解注册到DefaultListableBeanFactory。注解与xml有点区别就是在注入的时候,使用的是BeanPostProcessor实现类通过反射注入。就拿CommonAnnotationBeanPostProcessor来分析,会处理jee api中的注解,如Resource、WebServiceRef、EJB。这些BeanPostProcessor只是生成了BeanDefinition并没有实例化,spring内部针对这些内置对象,在AbstractApplicationContext会执行registerBeanPostProcessors方法,将这些BeanPostProcessor实例化,这样Ioc容器内部就有各种用于bean处理的BeanPostProcessor了,在bean注入前后这些BeanPostProcessor都会对bean进行相关处理,BeanPostProcessor相关类图:

下面就直接分析注入了,由于CommonAnnotationBeanPostProcessor属于InstantiationAwareBeanPostProcessor的子类,所以初始化时就会被执行,AbstractAutowireCapableBeanFactory形成bean的方法中,就会执行注解方式的注入了,参考代码:

protected void populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw) {
。。。
if (hasInstAwareBpps || needsDepCheck) {
        PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
        if (hasInstAwareBpps) {
            //解析bean内部的注解
            for (BeanPostProcessor bp : getBeanPostProcessors()) {
                if (bp instanceof InstantiationAwareBeanPostProcessor) {
                    InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
                    pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
                    if (pvs == null) {
                        return;
                    }
                }
            }
        }
        if (needsDepCheck) {
            checkDependencies(beanName, mbd, filteredPds, pvs);
        }
    }

}

在CommonAnnotationBeanPostProcessor的postProcessPropertyValues方法中就对bean就行注入了:

@Override
public PropertyValues postProcessPropertyValues(
        PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {

    InjectionMetadata metadata = findResourceMetadata(beanName, bean.getClass(), pvs);
    try {
        //处理bean内部是使用JSR注解的注入
        metadata.inject(bean, beanName, pvs);
    }
    catch (Throwable ex) {
        throw new BeanCreationException(beanName, "Injection of resource dependencies failed", ex);
    }
    return pvs;
}

由于在初始化CommonAnnotationBeanPostProcessor的时候,只针对JEE API中的注解,所以生成的InjectionMetadata也只能包含这些注入元素了,参考InjectionMetadata的内部类InjectedElement通过属性注入,当然也支持方法注入,这里贴部分代码:

protected void inject(Object target, String requestingBeanName, PropertyValues pvs) throws Throwable {
        if (this.isField) {
            Field field = (Field) this.member;
            ReflectionUtils.makeAccessible(field);
            //属性注入
            field.set(target, getResourceToInject(target, requestingBeanName));
        }
        else {
            if (checkPropertySkipping(pvs)) {
                return;
            }
            try {
                Method method = (Method) this.member;
                ReflectionUtils.makeAccessible(method);
                method.invoke(target, getResourceToInject(target, requestingBeanName));
            }
            catch (InvocationTargetException ex) {
                throw ex.getTargetException();
            }
        }
    }

至此一个完整的bean就初始化完了。

文章目录
  1. 1. 使用注解初始化bean的种类
    1. 1.1. AnnotationConfigApplicationContext启动初始化
    2. 1.2. 使用<\context:annotation-config/> 标签
    3. 1.3. 使用<\context:component-scan/>标签
  2. 2. 解析标签