Spring学习Bean的扫描注册

Posted pur_e

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring学习Bean的扫描注册相关的知识,希望对你有一定的参考价值。

       在前面的文章《使用IDEA创建Spring mvc工程及简要分析》中,稍微讲过MVC寻找配置文件的过程,现在在这个基础上,看一下配置文件是如何加载的,着重看一下Bean的扫描注册过程。其实稍微用过Spring的人都知道,Bean可以通过Xml配置文件与注解两种方式来配置,看过本文后可以看到,这两种方式最后调用的都是相同的接口进行Bean的注册,只不过扫描的过程不一样。

一、配置文件读取

       上面文章最后提到XmlWebApplicationContext类,找到了名为mvc-dispatcher-servlet.xml的配置文件,现在继续跟踪是如何处理这个文件的。

FrameworkServlet.java:

protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
    //取上下文类,默认为XmlWebApplicationContext
    Class<?> contextClass = getContextClass();
    ...
    //配置上下文类
    configureAndRefreshWebApplicationContext(wac);

    return wac;
}

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {

    ...
    //初始化上下文,这是非常重要的一个函数,很多Spring相关属性,Bean注册,初始化都是在这里完成的
    wac.refresh();
}


AbstractApplicationContext.java:
//这个函数就直接都贴出来了,不过其他逻辑暂不分析,只看解析配置文件,注册Bean相关
@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // Prepare this context for refreshing.
        prepareRefresh();

        // Tell the subclass to refresh the internal bean factory.
        //就是这一句
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // Prepare the bean factory for use in this context.
        prepareBeanFactory(beanFactory);

        try {
            // Allows post-processing of the bean factory in context subclasses.
            postProcessBeanFactory(beanFactory);

            // Invoke factory processors registered as beans in the context.
            invokeBeanFactoryPostProcessors(beanFactory);

            // Register bean processors that intercept bean creation.
            registerBeanPostProcessors(beanFactory);

            // Initialize message source for this context.
            initMessageSource();

            // Initialize event multicaster for this context.
            initApplicationEventMulticaster();

            // Initialize other special beans in specific context subclasses.
            onRefresh();

            // Check for listener beans and register them.
            registerListeners();

            // Instantiate all remaining (non-lazy-init) singletons.
            finishBeanFactoryInitialization(beanFactory);

            // Last step: publish corresponding event.
            finishRefresh();
        }

        catch (BeansException ex) {
            logger.warn("Exception encountered during context initialization - cancelling refresh attempt", ex);

            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();

            // Reset ‘active‘ flag.
            cancelRefresh(ex);

            // Propagate exception to caller.
            throw ex;
        }
    }
}


protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
    //刷新Bean工厂
    refreshBeanFactory();
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    if (logger.isDebugEnabled()) {
        logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
    }
    return beanFactory;
}

AbstractRefreshableApplicationContext.java:
@Override
protected final void refreshBeanFactory() throws BeansException {
    if (hasBeanFactory()) {
        destroyBeans();
        closeBeanFactory();
    }
    try {
        DefaultListableBeanFactory beanFactory = createBeanFactory();
        beanFactory.setSerializationId(getId());
        customizeBeanFactory(beanFactory);
        //加载Bean配置到Bean工厂
        loadBeanDefinitions(beanFactory);
        synchronized (this.beanFactoryMonitor) {
            this.beanFactory = beanFactory;
        }
    }
    catch (IOException ex) {
        throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
    }
}

XmlWebApplicationContext.java:
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
    // Create a new XmlBeanDefinitionReader for the given BeanFactory.
    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
    ...
    //使用XmlBeanDefinitionReader去加载相关Bean配置
    loadBeanDefinitions(beanDefinitionReader);
}

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
    //这会取到我们的配置文件,也就是mvc-dispatcher-servlet.xml,终于找到你了^_^
    String[] configLocations = getConfigLocations();
    if (configLocations != null) {
        for (String configLocation : configLocations) {
            reader.loadBeanDefinitions(configLocation);
        }
    }
}

       到这里,看到了我们的配置文件mvc-dispatcher-servlet.xml,后面的代码会去分析并注册其中配置的Bean。

XmlBeanDefinitionReader.java:
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    ...
    //读取配置文件mvc-dispatcher-servlet.xml为InputStream,及额外的异常处理后,继续加载
    return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
    ... 
}

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
    //加载XML文件
    Document doc = doLoadDocument(inputSource, resource);
    //继续加载
    return registerBeanDefinitions(doc, resource);

}

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    documentReader.setEnvironment(getEnvironment());
    int countBefore = getRegistry().getBeanDefinitionCount();
    /*加载Bean配置,这里有一个重要处理,会加载XML不同命名空间对应的处理类
     在函数createReaderContext(resource)中,创建DefaultNamespaceHandlerResolver
     后面在处理自定义节点时,会调用这里的处理函数
    */
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    return getRegistry().getBeanDefinitionCount() - countBefore;
}

       这里的DefaultNamespaceHandlerResolver还是需要详细说一下的,这个类会添加不同命名空间的处理函数:

  • 有一个命名空间对应处理类的配置
    public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = “META-INF/spring.handlers”;
    在各个spring的jar包中会找到这个文件
  • 如spring-context-4.1.1.RELEASE-sources.jar!/META-INF/spring.handlers,文件中有配置:
http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
  • 这样,不同的Xml节点,会调用各自命名空间的处理类,如:
<context:component-scan base-package="com.springapp.mvc"/>

调用:
org.springframework.context.config.ContextNamespaceHandler
来处理

  • 处理类中,针对不同的节点,也会注册不同的处理类:
registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
  • 后续处理到context:component-scan节点时,就会调用ComponentScanBeanDefinitionParser处理类来处理,这个等后面提到解析不同节点时会再提到
  • 知道了这个流程,我们就可以注册自己的命名空间,添加自己的处理类
DefaultBeanDefinitionDocumentReader.java:
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;
    logger.debug("Loading bean definitions");
    Element root = doc.getDocumentElement();
    //继续加载
    doRegisterBeanDefinitions(root);
}


protected void doRegisterBeanDefinitions(Element root) {

    ...
    //这个函数在碰到<beans>会有递归调用,有一部分保证递归调用正确的逻辑

    //默认空函数
    preProcessXml(root);
    //扫描注册Bean
    parseBeanDefinitions(root, this.delegate);
    //默认空函数
    postProcessXml(root);
    ...
}


/**
 * Parse the elements at the root level in the document:
 * "import", "alias", "bean".
 * @param root the DOM root element of the document
 */
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    if (delegate.isDefaultNamespace(root)) {
        //默认命名空间"http://www.springframework.org/schema/beans",调用默认处理,主要是import,alias,bean节
        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;
                if (delegate.isDefaultNamespace(ele)) {
                    parseDefaultElement(ele, delegate);
                }
                else {
                    //调用自定义处理,如component-scan节
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }
    else {
        //调用自定义处理,如component-scan节
        delegate.parseCustomElement(root);
    }
}

       从这里开始分两种不同处理

  • 一种是默认命名空间,如bean节,这种就是直接在Xml文件中配置Bean定义
  • 另一种如component-scan配置,在Xml中配置后,会根据配置,到包中去分析注解,也就是最终Bean是通过注解来定义的

二、XML文件中定义Bean

       在XML中最简单的Bean定义,定义一个bean name为loginService的bean

<bean id="loginService" class="com.springapp.mvc.service.impl.LoginServiceImpl"/>

       接着上一节看一下,是如何解析的:

DefaultBeanDefinitionDocumentReader.java:

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {

    if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
        //处理<import>
        importBeanDefinitionResource(ele);
    }
    else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
        //处理<alias>
        processAliasRegistration(ele);
    }
    else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
        //处理<bean>
        processBeanDefinition(ele, delegate);
    }
    else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
        // recurse
        //递归处理<beans>
        doRegisterBeanDefinitions(ele);
    }
}

/**
 * Process the given bean element, parsing the bean definition
 * and registering it with the registry.
 */
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    /*
        处理<bean>内部各种属性定义如id/class,子节点如constructor-arg/property等
        这些都是对bean进行限制或说明的定义,非常重要的函数
    */
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    if (bdHolder != null) {
        //处理自定义的属性,可以通过之前说过的配置命名空间来实现,默认为空
        bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
        try {
            // Register the final decorated instance.
            //bean定义完成,真正注册bean的地方
            BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
        }
        catch (BeanDefinitionStoreException ex) {
            getReaderContext().error("Failed to register bean definition with name ‘" +
                    bdHolder.getBeanName() + "‘", ele, ex);
        }
        // Send registration event.
        getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
    }
}

       这里又分两部分来说,一个是Bean的定义解析,一个是Bean的注册,注册稍微简单一些

1. Bean定义解析

       Bean的定义很复杂,主要是可选配置项太多,当然,做为一个框架容器,这是不可避免的,即使我们平时使用的选项很少,在真正用到的时候无法实现,就不美了。
       这里不会详细去分析配置如何解析,只会跟踪相关代码位置,后续有用得到的,才可能会去研究。

/**
 * Parses the supplied {@code &lt;bean&gt;} element. May return {@code null}
 * if there were errors during parse. Errors are reported to the
 * {@link org.springframework.beans.factory.parsing.ProblemReporter}.
 */
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
    String id = ele.getAttribute(ID_ATTRIBUTE);
    String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

    ...
    //检查beanName的唯一性
    if (containingBean == null) {
        checkNameUniqueness(beanName, aliases, ele);
    }
    //解析定义
    AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);

    ...
}

//真正解析Bean定义的函数
/**
 * Parse the bean definition itself, without regard to name or aliases. May return
 * {@code null} if problems occurred during the parsing of the bean definition.
 */
public AbstractBeanDefinition parseBeanDefinitionElement(
        Element ele, String beanName, BeanDefinition containingBean) {

    this.parseState.push(new BeanEntry(beanName));

    String className = null;
    if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
        className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
    }

    try {
        String parent = null;
        if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
            parent = ele.getAttribute(PARENT_ATTRIBUTE);
        }
        //创建Bean定义,通过类名与parent定义
        AbstractBeanDefinition bd = createBeanDefinition(className, parent);
        //解析Bean属性,如scope/lazy-init等
        parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
        bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
        //解析meta
        parseMetaElements(ele, bd);
        //处理lookup-method子节点配置
        parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
        //处理方法替换replaced-method子节点配置
        parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
        //处理constructor-arg子节点配置
        parseConstructorArgElements(ele, bd);
        //处理property子节点配置
        parsePropertyElements(ele, bd);
        //处理qualifier子节点配置
        parseQualifierElements(ele, bd);

        bd.setResource(this.readerContext.getResource());
        bd.setSource(extractSource(ele));

        return bd;
    }
    catch (ClassNotFoundException ex) {
        error("Bean class [" + className + "] not found", ele, ex);
    }
    catch (NoClassDefFoundError err) {
        error("Class that bean class [" + className + "] depends on not found", ele, err);
    }
    catch (Throwable ex) {
        error("Unexpected failure during bean definition parsing", ele, ex);
    }
    finally {
        this.parseState.pop();
    }

    return null;
}

2.Bean注册

BeanDefinitionReaderUtils.java

/**
 * Register the given bean definition with the given bean factory.
 * @param definitionHolder the bean definition including name and aliases
 * @param registry the bean factory to register with
 * @throws BeanDefinitionStoreException if registration failed
 */
public static void registerBeanDefinition(
        BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
        throws BeanDefinitionStoreException {

    // Register bean definition under primary name.
    String beanName = definitionHolder.getBeanName();
    //注册Bean
    registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

    // Register aliases for bean name, if any.
    String[] aliases = definitionHolder.getAliases();
    if (aliases != null) {
        for (String aliase : aliases) {
            registry.registerAlias(beanName, aliase);
        }
    }
}

org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition:

@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
        throws BeanDefinitionStoreException {

    ...

    synchronized (this.beanDefinitionMap) {
        oldBeanDefinition = this.beanDefinitionMap.get(beanName);
        if (oldBeanDefinition != null) {
            //如果Bean已经有定义
            if (!this.allowBeanDefinitionOverriding) {
                //配置不允许覆盖定义,异常报错
                throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                        "Cannot register bean definition [" + beanDefinition + "] for bean ‘" + beanName +
                        "‘: There is already [" + oldBeanDefinition + "] bound.");
            }
            else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
                // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Overriding user-defined bean definition for bean ‘" + beanName +
                            " with a framework-generated bean definition ‘: replacing [" +
                            oldBeanDefinition + "] with [" + beanDefinition + "]");
                }
            }
            else {
                if (this.logger.isInfoEnabled()) {
                    this.logger.info("Overriding bean definition for bean ‘" + beanName +
                            "‘: replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");
                }
            }
        }
        else {
            this.beanDefinitionNames.add(beanName);
            this.frozenBeanDefinitionNames = null;
        }
        //添加Bean定义,其实就是一个map,key是beanName
        this.beanDefinitionMap.put(beanName, beanDefinition);
    }

    if (oldBeanDefinition != null || containsSingleton(beanName)) {
        //如果Bean被新定义覆盖,将之前所有注入过的,销毁掉,以后需要时再重新创建
        resetBeanDefinition(beanName);
    }
}

三、使用自动扫描注解来定义Bean

       使用XML配置Bean,当项目文件非常多时,会非常麻烦,所以在Spring在2.5以后版本,提供了使用注解来进行Bean配置。首先,我们在配置文件中配置:

<!--配置包解析配置-->
<context:component-scan base-package="com.springapp.mvc"/>

       这是最简单的配置,其他选项以后再分析,先看看是如何实现自动扫描注解添加Bean的。

       第一节其实已经提到过,有一个专门解析context:component-scan节点的类:org.springframework.context.annotation.ComponentScanBeanDefinitionParser

@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.
    ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
    //扫描所有文件中的注解,生成Bean定义,并进行注册
    Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
    registerComponents(parserContext.getReaderContext(), beanDefinitions, element);

    return null;
}


org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan:
/**
 * Perform a scan within the specified base packages,
 * returning the registered bean definitions.
 * <p>This method does <i>not</i> register an annotation config processor
 * but rather leaves this up to the caller.
 * @param basePackages the packages to check for annotated classes
 * @return set of beans registered if any for tooling registration purposes (never {@code null})
 */
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Assert.notEmpty(basePackages, "At least one base package must be specified");
    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
    for (String basePackage : basePackages) {
        //找到包下所有Bean定义
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
        for (BeanDefinition candidate : candidates) {
            ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
            candidate.setScope(scopeMetadata.getScopeName());
            //生成beanName,后面的操作和Xml文件中添加Bean的流程就是一样的了
            String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
            if (candidate instanceof AbstractBeanDefinition) {
                postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
            }
            if (candidate instanceof AnnotatedBeanDefinition) {
                AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
            }
            if (checkCandidate(beanName, candidate)) {
                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                beanDefinitions.add(definitionHolder);
                //进行Bean注册,里面调用的是和第二节注册时相同的方法
                registerBeanDefinition(definitionHolder, this.registry);
            }
        }
    }
    return beanDefinitions;
}

       后面太细的就不再跟进去了。这种方法扫描的是@Component及其特例化的注解@Repository、@Service 和 @Controller所注解的类,当前这4个注解是完全一样的,只是用来标记Bean。

四、总结

       以后这么细的去看框架代码会比较少了,因为这样看代码反而钻了牛角尖,学不到多少东西,甚至还不如多写多用,多看相关框架的书籍,学习框架的设计思路。

以上是关于Spring学习Bean的扫描注册的主要内容,如果未能解决你的问题,请参考以下文章

Spring 学习历程

Spring学习笔记之装配Bean

Spring 3.0 学习-DI 依赖注入_创建Spring 配置-使用一个或多个XML 文件作为配置文件,使用自动注入(byName),在代码中使用注解代替自动注入,使用自动扫描代替xml中bea(

spring 注解驱动开发组件注册

SSM整合学习笔记

Spring注解——使用@ComponentScan自动扫描组件