Spring MVC - context:component-scan实现原理与实例

一、<context:component-scan/>

想必@Component,@Repository,@Service,@Controller几个常用的Type-Level的Spring MVC注解,大家都很清楚他们的意思跟用途。

标记为@Component的类,在使用注解配置的情况下,系统启动时会被自动扫描,并添加到bean工厂中去(省去了配置文件中写bean定义了),另外三个分别表示MVC三层模式中不同层中的组件,他们都是被@Component标记的,所以也会被自动扫描。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component//这里。。。
public @interface Repository {
    String value() default "";
}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component//这里。。。
public @interface Service {
    String value() default "";
}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component//这里。。。
public @interface Controller {
    String value() default "";
}

为了达到以上效果,我们还需在xml配置文件中加入如下定义

<context:component-scan base-package="com.springrock..."/>

这样Spring就可以正确的处理我们定义好的组件了,重要的是这些都是自动的,你甚至不知道他是怎么做的,做了什么?如果不了解反射,可能真的感到吃惊了,但即便如此,我也想知道它到底做了什么?什么时候做的?


二、BeanDefinitionParser

启动项目,加载spring.xml时,会注册下来解析器:

    @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());
    }

经过仔细的源码阅读,我找到了这个接口BeanDefinitionParser,文档描述说,它是一个用来处理自定义,顶级(<beans/>的直接儿子标签)标签的接口抽象。可以实现它来将自定义的标签转化为 BeanDefinition类。

BeanDefinitionParser接口注释如下:

 * Interface used by the {@link DefaultBeanDefinitionDocumentReader} to handle custom,
 * top-level (directly under {@code <beans/>}) tags.
 *//接口被用来处理自定义,顶级(`<beans/>`的直接儿子标签(DefaultBeanDefinitionDocumentReader:通过DTD和XSD读取bean的定义信息)
 * <p>Implementations are free to turn the metadata in the custom tag into as many
 * {@link BeanDefinition BeanDefinitions} as required.
 *//实现类可以将标签里面的元数据转化成 bean对应的BeanDefinition(其描述了一个bean拥有哪些属性,构造器等信息) 

 * <p>The parser locates a {@link BeanDefinitionParser} from the associated
 * {@link NamespaceHandler} for the namespace in which the custom tag resides.
 * //解析器从标签隶属的namespace里面找出相关联的bean解析器

/*注意:
①先读取标签信息,然后获取对应的BeanDefinition;
②找到对应的BeanDefinitionParser!!!*/

下面是它的接口方法定义:

BeanDefinition parse(Element element, ParserContext parserContext);

其中Element是Dom api 中的元素,ParserContext则是用来注册转换来的bean 工厂(创建scanner —//使用的是readerContext.getRegistry()和boolean 类型的useDefaultFilters ! readerContext是从parserContext获取的!!)。


ParserContext 类注释:

 * Context that gets passed along a bean definition parsing process,
 * encapsulating all relevant configuration as well as state.
 * //bean定义解析过程传递的上下文,封装所有相关的配置以及状态。
 * Nested inside an {@link XmlReaderContext}.
 * //内部嵌套了一个XmlReaderContext
  • element 示例如下图所示:

这里写图片描述


  • parserContext 如下图所示:
    • 拥有属性readerContext;

这里写图片描述


或许你开始恼火说这么多跟上面有什么关系,好吧,下面便是我真正要说的,我们来看下它有哪些实现类:

这里写图片描述

看到了吧,ComponentScanBeanDefinitionParser,正是我们想要的,他就是用来将<context:component-scan/>标签转化为bean 的解析类。那他做了什么呢?


ComponentScanBeanDefinitionParser类注释如下:

 Parser for the {@code <context:component-scan/>} element.
 //解析该标签的解析器

其parse()方法注释如下:

Parse the specified Element and register the resulting BeanDefinition(s) with the BeanDefinitionRegistry embedded in the supplied ParserContext. 
//解析具体的Element 并使用上下文(ParserContext)提供的BeanDefinitionRegistry 注册结果(BeanDefinition)到容器工厂中。
public BeanDefinition parse(Element element, ParserContext parserContext) {
        /*获取(BASE_PACKAGE_ATTRIBUTE)属性值*/
        String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
        //获取运行环境对basePackage进行解析
        basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
        /*使用分隔符解析为字符串数组*/
        String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,              ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

        // Actually scan for bean definitions and register them.
       //根据parserContext和element获取scanner 
        ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
        /*获取basePackages下对应的beanDefinitions */
        Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
        //注册到容器工厂
        registerComponents(parserContext.getReaderContext(), beanDefinitions, element);

        return null;
    }

① parserContext.getReaderContext():

这里写图片描述

② getEnvironment():

    • 首先获取reader,然后获取Environment :
public final Environment getEnvironment() {
        return this.reader.getEnvironment();
    }
      • readerContext;

这里写图片描述

      • Environment :

这里写图片描述


③ resolvePlaceholders():

    • 获取完environment后获取propertyResolver:
@Override
    public String resolvePlaceholders(String text) {
        return this.propertyResolver.resolvePlaceholders(text);
    }

方法注释如下:

Resolve ${...} placeholders in the given text, replacing them with corresponding property values as resolved by getProperty. Unresolvable placeholders with no default value are ignored and passed through unchanged.
/*意思就是解析你的basePackage,如果使用了${...}形式,就解析为对应的text值*/
@Override
    public String resolvePlaceholders(String text) {
        if (this.nonStrictHelper == null) {
            this.nonStrictHelper = createPlaceholderHelper(true);
        }
        return doResolvePlaceholders(text, this.nonStrictHelper);
    }
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
        return helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() {
            @Override
            public String resolvePlaceholder(String placeholderName) {
                return getPropertyAsRawString(placeholderName);
            }
        });
    }

ClassPathBeanDefinitionScanner 类注释如下:

A bean definition scanner that detects bean candidates on the classpath, 
registering corresponding bean definitions with a given registry (BeanFactory or ApplicationContext). 

/*一个bean definition 扫描器检测classpath下的候选bean,并注册corresponding bean definitions到容器工厂中;*/

Candidate classes are detected through configurable type filters. 

/*通过可配置的 type filters 过滤候选类*/

The default filters include classes that are annotated with Spring's @Component, @Repository, @Service, or @Controller stereotype. 

/*默认的filters 包含那些被@Component, @Repository, @Service, or @Controller 注解的类!!!*/

Also supports Java EE 6's javax.annotation.ManagedBean and JSR-330's javax.inject.Named annotations, if available.
//同样支持其他的。。。

很明显他会获得<component-scan/>的base-package属性;
然后解析所需解析的包路径。
然后他会创建一个ClassPathBeanDefinitionScanner对象,并委托它来执行对路径下文件的扫描。
然后将获得的BeanDefinitions注册到bean工厂中。

是不是很清晰?


首先看获取scanner代码如下:

protected ClassPathBeanDefinitionScanner configureScanner(ParserContext parserContext, Element element) {
        //默认使用 defaultFilters
        boolean useDefaultFilters = true;
        if (element.hasAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE)) {
            useDefaultFilters = Boolean.valueOf(element.getAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE));
        }

        // Delegate(委托) bean definition registration to scanner class.注意这句话,讲明了scanner的功能
        ClassPathBeanDefinitionScanner scanner = createScanner(parserContext.getReaderContext(), useDefaultFilters);
        scanner.setBeanDefinitionDefaults(parserContext.getDelegate().getBeanDefinitionDefaults());
        scanner.setAutowireCandidatePatterns(parserContext.getDelegate().getAutowireCandidatePatterns());

        if (element.hasAttribute(RESOURCE_PATTERN_ATTRIBUTE)) {
            scanner.setResourcePattern(element.getAttribute(RESOURCE_PATTERN_ATTRIBUTE));
        }
        //后续属性配置
        try {
            parseBeanNameGenerator(element, scanner);
        }
        catch (Exception ex) {
            parserContext.getReaderContext().error(ex.getMessage(), parserContext.extractSource(element), ex.getCause());
        }

        try {
            parseScope(element, scanner);
        }
        catch (Exception ex) {
            parserContext.getReaderContext().error(ex.getMessage(), parserContext.extractSource(element), ex.getCause());
        }

        parseTypeFilters(element, scanner, parserContext);

        return scanner;
    }

    • 创建Scanner的代码:
protected ClassPathBeanDefinitionScanner createScanner(XmlReaderContext readerContext, boolean useDefaultFilters) {
        return new ClassPathBeanDefinitionScanner(readerContext.getRegistry(), useDefaultFilters);
    }
    //使用的是readerContext.getRegistry()和boolean 类型的useDefaultFilters ! readerContext是从parserContext获取的!!

此时Scanner的值(刚创建完):

这里写图片描述

执行完set后Scanner的值:

这里写图片描述

注意这样几个属性:

beanDefinitionDefaults,beanNameGenerator,
environment,excludeFilters,
includeFilters,metadataReaderFactory,
registry,resourcePattern,
resourcePatternResolver和scopeMetadataResolver!

下面开始进行后续配置:

parseBeanNameGenerator(element, scanner);
//Set the BeanNameGenerator to use for detected bean classes. 
Default is a AnnotationBeanNameGenerator.
parseScope(element, scanner);
// Register ScopeMetadataResolver if class name provided.
parseTypeFilters(element, scanner, parserContext);
//Parse exclude and include filter elements.

parseTypeFilters()详细代码如下:

protected void parseTypeFilters(Element element, ClassPathBeanDefinitionScanner scanner, ParserContext parserContext) {
        // Parse exclude and include filter elements.
        //获取classLoader
        ClassLoader classLoader = scanner.getResourceLoader().getClassLoader();
        //拿到子元素list
        NodeList nodeList = element.getChildNodes();
        //根据子元素分别添加IncludeFilter和ExcludeFilter
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node node = nodeList.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                String localName = parserContext.getDelegate().getLocalName(node);
                try {
                    if (INCLUDE_FILTER_ELEMENT.equals(localName)) {
                        TypeFilter typeFilter = createTypeFilter((Element) node, classLoader, parserContext);
                        scanner.addIncludeFilter(typeFilter);
                    }
                    else if (EXCLUDE_FILTER_ELEMENT.equals(localName)) {
                        TypeFilter typeFilter = createTypeFilter((Element) node, classLoader, parserContext);
                        scanner.addExcludeFilter(typeFilter);
                    }
                }
                catch (Exception ex) {
                    parserContext.getReaderContext().error(
                            ex.getMessage(), parserContext.extractSource(element), ex.getCause());
                }
            }
        }
    }

至此,Scanner可以根据basePackage和IncludeFilter及ExcludeFilter进行扫描class!!!


获取Scanner后,我想你会急切的知道ClassPathBeanDefinitionScanner 做了什么?

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        //首先判断不为空
        Assert.notEmpty(basePackages, "At least one base package must be specified");
        //定义各一个空集合
        Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
        //遍历basePackages
        for (String basePackage : basePackages) {
            //Scan the class path for candidate components.
            Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
            //遍历集合中的BeanDefinition 
            for (BeanDefinition candidate : candidates) {
                ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
                candidate.setScope(scopeMetadata.getScopeName());
                //为(BeanDefinition)candidate设置Scope
                String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
                if (candidate instanceof AbstractBeanDefinition) {
                    /*Apply further settings to the given bean definition, beyond the contents retrieved from scanning the component class.*/
                        postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
                }
                if (candidate instanceof AnnotatedBeanDefinition) {
                    AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
                }
                /*Check the given candidate's bean name, determining whether the corresponding bean definition needs to be registered or conflicts with an existing definition*/
                if (checkCandidate(beanName, candidate)) {
                    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                    definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                    beanDefinitions.add(definitionHolder);
                    registerBeanDefinition(definitionHolder, this.registry);
                }
            }
        }
        return beanDefinitions;
    }

BeanDefinitionHolder 类注释如下:

Holder for a BeanDefinition with name and aliases. Can be registered as a placeholder for an inner bean. 

Can also be used for programmatic registration of inner bean definitions. If you don't care about BeanNameAware and the like, registering RootBeanDefinition or ChildBeanDefinition is good enough.

① 找到candidates (set 集合)

      • 示例如下:
[Generic bean: class [com.atguigu.springmvc.converters.EmployeeConverter]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [D:\apache-tomcat-6.0.37\webapps\springMVC-2\WEB-INF\classes\com\atguigu\springmvc\converters\EmployeeConverter.class], Generic bean: class [com.atguigu.springmvc.crud.dao.DepartmentDao]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [D:\apache-tomcat-6.0.37\webapps\springMVC-2\WEB-INF\classes\com\atguigu\springmvc\crud\dao\DepartmentDao.class], Generic bean: class [com.atguigu.springmvc.crud.dao.EmployeeDao]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [D:\apache-tomcat-6.0.37\webapps\springMVC-2\WEB-INF\classes\com\atguigu\springmvc\crud\dao\EmployeeDao.class], Generic bean: class [com.atguigu.springmvc.crud.handlers.EmployeeHandler]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [D:\apache-tomcat-6.0.37\webapps\springMVC-2\WEB-INF\classes\com\atguigu\springmvc\crud\handlers\EmployeeHandler.class], Generic bean: class [com.atguigu.springmvc.crud.handlers.TagsController]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [D:\apache-tomcat-6.0.37\webapps\springMVC-2\WEB-INF\classes\com\atguigu\springmvc\crud\handlers\TagsController.class], Generic bean: class [com.atguigu.springmvc.test.SpringMVCTest]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [D:\apache-tomcat-6.0.37\webapps\springMVC-2\WEB-INF\classes\com\atguigu\springmvc\test\SpringMVCTest.class], Generic bean: class [com.atguigu.springmvc.test.SpringMVCTestExceptionHandler]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [D:\apache-tomcat-6.0.37\webapps\springMVC-2\WEB-INF\classes\com\atguigu\springmvc\test\SpringMVCTestExceptionHandler.class]]

② 遍历candidates–BeanDefinition candidate :

这里写图片描述

③ 使用scopeMetadataResolver获取candidate的scopeMetadata:

这里写图片描述

④ 根据candidate和registry获取beanName

这里写图片描述

⑤ 判断是否是AbstractBeanDefinition实例,若是,则进行后置处理:

postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);

/*Apply further settings to the given bean definition, beyond the contents retrieved from scanning the component class.*/

⑥ 检测candidate,判断是否需要注册和已经存在的相冲突

⑦ 获取BeanDefinitionHolder definitionHolder

这里写图片描述

⑧ 使用反射,对definitionHolder创建代理;

static BeanDefinitionHolder applyScopedProxyMode(
            ScopeMetadata metadata, BeanDefinitionHolder definition, BeanDefinitionRegistry registry) {

        ScopedProxyMode scopedProxyMode = metadata.getScopedProxyMode();
        if (scopedProxyMode.equals(ScopedProxyMode.NO)) {
            return definition;
        }
        boolean proxyTargetClass = scopedProxyMode.equals(ScopedProxyMode.TARGET_CLASS);
        return ScopedProxyCreator.createScopedProxy(definition, registry, proxyTargetClass);
    }

⑨ 添加到set集合,并将specified bean 注册registry(工厂)

Register the specified bean with the given registry. 

Can be overridden in subclasses, e.g. to adapt the registration process or to register further bean definitions for each scanned bean.
    /**
     * 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();
        registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

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

重点是继承自父类ClassPathScanningCandidateComponentProvider 的findCandidateComponents方法,意思就是找到候选组件,然后注册到工厂中,那么它是怎么找到候选组件的呢?

我们再看看

public Set<BeanDefinition> findCandidateComponents(String basePackage) {
        Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
        try {
            //拿到拼接后的路径
            String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                    resolveBasePackage(basePackage) + "/" + this.resourcePattern;
                    /*Resolve the given location pattern into Resource objects. 
Overlapping resource entries that point to the same physical resource should be avoided, as far as possible. The result should have set semantics.

何为Resource? Interface for a resource descriptor that abstracts from the actual type of underlying resource,
 such as a file or class path resource.
*/
            Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
            for (Resource resource : resources) {
                //判断是否为可读
                if (resource.isReadable()) {
                    try {
                        /*Obtain a MetadataReader(元数据读取器) for the given resource.*/
                        MetadataReader metadataReader = this.metadataReaderFactory.                                                               getMetadataReader(resource);
                        /*判断resource生成的metadataReader是否为候选组件*/
                        if (isCandidateComponent(metadataReader)) {
                            ScannedGenericBeanDefinition sbd = 
                                              new ScannedGenericBeanDefinition(metadataReader);
                                              /*判断ScannedGenericBeanDefinition 是否为候选组件*/
                            if (isCandidateComponent(sbd)){
                                candidates.add(sbd);
                            }
                        }
                    }
                }
            }
        }
        return candidates;
    }
  • 首先获取路径下的资源Resource;
  • 然后判断资源是否可读,并且获取可读资源的MetadataReader对象,
  • 然后再调用isCandidateComponent(MetadataReader)判段是否是候选组件,如果是,则生成该metadataReader的ScannedGenericBeanDefinition对象。
  • 最后判断ScannedGenericBeanDefinition是否为候选组件,如果是则添加到工厂中。

三、includeFilters,excludeFilters

可以看到经历了两次筛选,才找到最终的候选Bean,我们来看第一个过滤做了什么?

  • 判断是否为候选组件
/**
     * Determine whether the given class does not match any exclude filter
     * and does match at least one include filter.
     * @param metadataReader the ASM ClassReader for the class
     * @return whether the class qualifies as a candidate component
     */
    protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
        /*判断excludeFilters*/
        for (TypeFilter tf : this.excludeFilters) {
            if (tf.match(metadataReader, this.metadataReaderFactory)) {
                return false;
            }
        }
        /*/*判断includeFilters*/*/
        for (TypeFilter tf : this.includeFilters) {
            if (tf.match(metadataReader, this.metadataReaderFactory)) {
                return isConditionMatch(metadataReader);
            }
        }
        return false;
    }

我们看到这里有两个实例变量excludeFilters, includeFilters,然后用他们两个去匹配传递进来的MetadataReader,如果与excludeFilter匹配成功返回false, 与includeFilter匹配成功返回true。那么这两个filter分别是什么呢?我们打上断点,调试运行发现

这里写图片描述


默认情况下includeFilters是一个含有两个值得List,分别是@Component注解和@ManageBean注解,而excludeFilter是个空List。

好吧,现在豁然开朗了吧,原来就是它来筛选我们的@Component标记的类。

当然我们可以自定义这两个filters,只需在<context:component-scan/>标签下加两个子标签即可, 像这样:

<context:component-scan base-package="com.springrock">
       <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
       <context:include-filter type="annotation" expression="com.springrock.whatever.youcustomized.annotation"/>
</context:component-scan>

四、BeanDefinitionRegistry

上面代码中我们看到还有一个isCandidateComponent方法,它主要是判断当前类是否是具体的,而非抽象类和接口,以及是否是可以独立创建的没有依赖的?鉴于与我们目前讨论的主题不相关,所以略去,感兴趣的话,可以自己查看下源码。

好了,我们既然知道了Spring是怎样通过<context:component-scan/>来扫描,过滤我们的组件了,但是他是怎样将我们定义的组件收集起来供后面的请求处理呢?

我们来看下上面doScan方法中有

//注册到工厂中
registerBeanDefinition(definitionHolder, this.registry);

这样一行代码,很明显是将beanDefinition注册到registry中了。

首先进入方法BeanDefinitionReaderUtils.registerBeanDefinition():


    /**
     * Register the given bean definition with the given bean factory.
     * //使用给定的bean Factory注册bean
     * @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();
        //重点在这里!!!!注册到registry(工厂)
        registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

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

那这个registry是什么呢?是一个BeanDefinitionRegistry,下面是它的接口定义及继承结构:

public interface BeanDefinitionRegistry extends AliasRegistry {
    void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException;
    void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
    BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
    boolean containsBeanDefinition(String beanName);
    String[] getBeanDefinitionNames();
    int getBeanDefinitionCount();
    boolean isBeanNameInUse(String beanName);
}

这里写图片描述

我们可以看到接口中定义了诸多beandefinition的注册,删除,获取等方法,并且Spring为我们提供了三个内部实现,那么运行时,使用了那个实现呢?

DefaultListableBeanFactory,是的就是它。

它就是SpringMVC 中管理Bean的工厂了,我们来看下,它的registerBeanDefinition是怎样实现的?

//---------------------------------------------------------------------
    // Implementation of BeanDefinitionRegistry interface
    //---------------------------------------------------------------------

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

        Assert.hasText(beanName, "Bean name must not be empty");
        Assert.notNull(beanDefinition, "BeanDefinition must not be null");

        if (beanDefinition instanceof AbstractBeanDefinition) {
            try {
                //进行验证
                ((AbstractBeanDefinition) beanDefinition).validate();
            }
            catch (BeanDefinitionValidationException ex) {
                throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                        "Validation of bean definition failed", ex);
            }
        }

        BeanDefinition oldBeanDefinition;
        //根据beanName冲map中获取oldBeanDefinition
        oldBeanDefinition = this.beanDefinitionMap.get(beanName);
        //判断是否为null
        if (oldBeanDefinition != null) {
            if (!isAllowBeanDefinitionOverriding()) {
                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 (!beanDefinition.equals(oldBeanDefinition)) {
                if (this.logger.isInfoEnabled()) {
                    this.logger.info("Overriding bean definition for bean '" + beanName +
                            "' with a different definition: replacing [" + oldBeanDefinition +
                            "] with [" + beanDefinition + "]");
                }
            }
            else {
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Overriding bean definition for bean '" + beanName +
                            "' with an equivalent definition: replacing [" + oldBeanDefinition +
                            "] with [" + beanDefinition + "]");
                }
            }
            this.beanDefinitionMap.put(beanName, beanDefinition);
        }
        else {
            if (hasBeanCreationStarted()) {
                // Cannot modify startup-time collection elements anymore (for stable iteration)
                synchronized (this.beanDefinitionMap) {
                    this.beanDefinitionMap.put(beanName, beanDefinition);
                    List<String> updatedDefinitions = new ArrayList<String>(this.beanDefinitionNames.size() + 1);
                    updatedDefinitions.addAll(this.beanDefinitionNames);
                    updatedDefinitions.add(beanName);
                    this.beanDefinitionNames = updatedDefinitions;
                    if (this.manualSingletonNames.contains(beanName)) {
                        Set<String> updatedSingletons = new LinkedHashSet<String>(this.manualSingletonNames);
                        updatedSingletons.remove(beanName);
                        this.manualSingletonNames = updatedSingletons;
                    }
                }
            }
            else {
                // Still in startup registration phase
                this.beanDefinitionMap.put(beanName, beanDefinition);
                this.beanDefinitionNames.add(beanName);
                this.manualSingletonNames.remove(beanName);
            }
            this.frozenBeanDefinitionNames = null;
        }

        if (oldBeanDefinition != null || containsSingleton(beanName)) {
            resetBeanDefinition(beanName);
        }
    }

从上面的代码可以看出,所有的beanDefinition都由实例变量beanDefinitionMap来保存管理,他是一个ConcurrentHashMap,beanName作为键,beanDefinition对象作为值。

到这我们知道了我们的bean是怎样被注册管理的了。

但是问题又来了,我们的系统是在什么时候读取<context:component-scan/>标签,并且扫描我们的bean组件的呢?

当然是从ContextLoaderListener开始了入手分析了。

五、ContextLoader

首先看几个接口:

EventListener

/**
 * A tagging interface that all event listener interfaces must extend.
 * @since JDK1.1
 */
public interface EventListener {
}

② ServletContextListener:

/** 
     * Implementations of this interface receive notifications about
     * changes to the servlet context of the web application they are
     * part of.
     * To receive notification events, the implementation class
     * must be configured in the deployment descriptor for the web
     * application.
     * @see ServletContextEvent
     * @since   v 2.3
     */

public interface ServletContextListener extends EventListener {
    /**
     ** Notification that the web application initialization
     ** process is starting.
     ** All ServletContextListeners are notified of context
     ** initialization before any filter or servlet in the web
     ** application is initialized.
     */

    public void contextInitialized ( ServletContextEvent sce );

    /**
     ** Notification that the servlet context is about to be shut down.
     ** All servlets and filters have been destroy()ed before any
     ** ServletContextListeners are notified of context
     ** destruction.
     */
    public void contextDestroyed ( ServletContextEvent sce );
}

③ ContextLoaderListener:

 /*Bootstrap listener to start up and shut down Spring's root {@link WebApplicationContext}.
 * Simply delegates to {@link ContextLoader} as well as to {@link ContextCleanupListener}.
 *
 * <p>This listener should be registered after {@link org.springframework.web.util.Log4jConfigListener}
 * in {@code web.xml}, if the latter is used.
 *
 * <p>As of Spring 3.1, {@code ContextLoaderListener} supports injecting the root web
 * application context via the {@link #ContextLoaderListener(WebApplicationContext)}
 * constructor, allowing for programmatic configuration in Servlet 3.0+ environments.
 * See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
 **/
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

    public ContextLoaderListener() {
        }

    /*Create a new {@code ContextLoaderListener} with the given application context. This
     * constructor is useful in Servlet 3.0+ environments where instance-based
     * registration of listeners is possible through the {@link javax.servlet.ServletContext#addListener}
     * API.*/
    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }
    /**
     * Initialize the root web application context.
     */
    @Override
    public void contextInitialized(ServletContextEvent event) {
        initWebApplicationContext(event.getServletContext());
    }

    /**
     * Close the root web application context.
     */
    @Override
    public void contextDestroyed(ServletContextEvent event) {
        closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }

}

我们查看源码发现ContextLoaderListener将web application context的初始化动作委托给了ContextLoader了,那ContextLoader做了什么呢?

/**
     * Initialize Spring's web application context for the given servlet context,
     * using the application context provided at construction time, or creating a new one
     * according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
     * "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
     * @param servletContext current servlet context
     * @return the new WebApplicationContext
     * @see #ContextLoader(WebApplicationContext)
     * @see #CONTEXT_CLASS_PARAM
     * @see #CONFIG_LOCATION_PARAM
     */
    public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
        if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
            throw new IllegalStateException(
                    "Cannot initialize context because there is already a root application context present - " +
                    "check whether you have multiple ContextLoader* definitions in your web.xml!");
        }

        Log logger = LogFactory.getLog(ContextLoader.class);
        servletContext.log("Initializing Spring root WebApplicationContext");
        if (logger.isInfoEnabled()) {
            logger.info("Root WebApplicationContext: initialization started");
        }
        long startTime = System.currentTimeMillis();

        try {
            // Store context in local instance variable, to guarantee that
            // it is available on ServletContext shutdown.
            if (this.context == null) {
            /*如果context 为null,则根据servletContext创建context*/
                this.context = createWebApplicationContext(servletContext);
            }
            if (this.context instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
                if (!cwac.isActive()) {
                    // The context has not yet been refreshed -> provide services such as
                    // setting the parent context, setting the application context id, etc
                    if (cwac.getParent() == null) {
                        // The context instance was injected without an explicit parent ->
                        // determine parent for root web application context, if any.
                        ApplicationContext parent = loadParentContext(servletContext);
                        cwac.setParent(parent);
                    }
                    /*配置并刷新WebApplicationContext*/
                    configureAndRefreshWebApplicationContext(cwac, servletContext);
                }
            }
            /*在servletContext中设置属性*/
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

            ClassLoader ccl = Thread.currentThread().getContextClassLoader();
            if (ccl == ContextLoader.class.getClassLoader()) {
                currentContext = this.context;
            }
            else if (ccl != null) {
                currentContextPerThread.put(ccl, this.context);
            }

            if (logger.isDebugEnabled()) {
                logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
                        WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
            }
            if (logger.isInfoEnabled()) {
                long elapsedTime = System.currentTimeMillis() - startTime;
                logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
            }

            return this.context;
        }
        catch (RuntimeException ex) {
            logger.error("Context initialization failed", ex);
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
            throw ex;
        }
        catch (Error err) {
            logger.error("Context initialization failed", err);
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
            throw err;
        }
    }

上面的代码片段便是ContextLoader中initWebApplicationContext方法中的关键一段。

首先会创建一个WebApplicationContext对象;
然后configure 并且refresh这个WebApplicactionContext对象;

是不是在这个configureAndRefreshWebApplicationContext方法中进行了配置文件的加载和组件的扫描呢?

必须是啊。。。

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
        if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
            // The application context id is still set to its original default value
            // -> assign a more useful id based on available information
            String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
            if (idParam != null) {
                wac.setId(idParam);
            }
            else {
                // Generate default id...
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                        ObjectUtils.getDisplayString(sc.getContextPath()));
            }
        }

        wac.setServletContext(sc);
        String initParameter = sc.getInitParameter(CONFIG_LOCATION_PARAM);
        if (initParameter != null) {
            wac.setConfigLocation(initParameter);
        }
        customizeContext(sc, wac);
        wac.refresh();
    }

方法的最后有一个调用了wac的refresh方法,这个wac呢就是前面创建的WebApplicationContext对象,也就是我们这个Web应用的上下文对象。具体是什么呢?我们看一下createWebapplicationContext方法

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
        Class<?> contextClass = determineContextClass(sc);//这里是关键
        ConfigurableWebApplicationContext wac =
                (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
        return wac;
    }

这个方法先确定我们context的类型,调用了determineContextClass方法,

protected Class<?> determineContextClass(ServletContext servletContext) {
        //public static final String CONTEXT_CLASS_PARAM = "contextClass";
        String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
        if (contextClassName != null) {
            try {
                return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
            }
        }
        else {//defaultStrategies 是关键
            contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
            try {
                return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
            }
        }
    }

这个方法先判断我们servletContext中有没有contextClass这个初始化属性(在web.xml的init-param标签中配置),通常我们不会配置这个属性。
那肯定是null了,所以它接着去查看defaultStrategy中有没有相应属性,那这个defaultStrategy是什么呢?
下面是ContextLoader中一个静态代码块,也就说只要ContextLoader被加载,defaultStrategy便会被赋值。

static {
        try {
            //private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
            ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH,
                                             ContextLoader.class);
            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
        }
    }

很明显,系统是去ClassPath下读取一个ContextLoader.properties的属性文件,并赋值给defaultStrategy,这个属性文件如下:

org.springframework.web.context.WebApplicationContext                            =====================
org.springframework.web.context.support.XmlWebApplicationContext

这里写图片描述


啊哈,终于找到了,原来是XmlWebApplicationContext啊,这就是我们的WebApplicationContext具体实现对象。

既然找到他了,那我们看看他的refresh()方法做了什么呢?

public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
            prepareBeanFactory(beanFactory);
            try {
                postProcessBeanFactory(beanFactory);
                invokeBeanFactoryPostProcessors(beanFactory);
                registerBeanPostProcessors(beanFactory);
                initMessageSource();
                initApplicationEventMulticaster();
                onRefresh();
                registerListeners();
                finishBeanFactoryInitialization(beanFactory);
                finishRefresh();
            }
        }
    }

六、Bean Factory

这么多代码中,只有第二行与我们当前讨论的主题有关,这一行会尝试获取一个新鲜的BeanFactory,这个BeanFactory与我们之前说的那个BeanDefinitionRegistry有什么关系呢?继续看代码:

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        refreshBeanFactory();
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        return beanFactory;
    }

在getBeanFactory之前,先进行了一个refreshBeanFactory的操作来刷新当前的BeanFactory,我们以此来看一下:

@Override
    protected final void refreshBeanFactory() throws BeansException {
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }
        try {
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            customizeBeanFactory(beanFactory);
            loadBeanDefinitions(beanFactory);
            synchronized (this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
        }
    }

代码依旧很清晰,先判断有没有BeanFactory,如果有,销毁所有Bean,关闭BeanFactory,然后重新创建一个BeanFactory,并将其赋给beanFactory实例变量。
有没有发现这个beanFactory是个DefaultListableBeanFactory啊?

我们上边讲到的bean definition registry也是个DefaultListableBeanFactory记得吗?

他们会不会是同一个呢?答案是yes。

重点就在这个loadBeanDefinition(beanFactory)方法上了,很明显:加载Bean Definition到bean工厂中,是不是与我们上边讲到的对上了?

loadBeanDefinition中,Spring会读取xml配置文件,然后会读取里面的bean定义,这一切都是委托给了文章开头的BeanDefinitionParser来完成的,可以看到除了<context:component-scan/>的Parser,还有<mvc:annotation-driven/>的parser,还有<interceptors/>的parser等。是不是比较清晰了?

当然,我们的问题及好奇心远不止这些,这篇文章只是讲解了其中的一小个:系统的初始化做了什么,在什么时候加载我们定义的beans,我们定义的bean被放到了哪里? 等等,现在问题又来了,我们怎样使用我们的bean呢?或者说如果被标记为@Autowire的属性,是怎样被自动装配的呢?@RequestMapping怎样工作的呢?Spring怎样正确调用controller来处理请求呢?等等,后面的文章我们一一解答。


【7】使用实例

实例一:

  • type = annotation
<context:component-scan base-package="com.hh">
<!-- 不包含Repository注解 -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>

<context:component-scan base-package="com.hh">
<!-- 包含如下两个注解 -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service" />

<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />

</context:component-scan>

实例二:

  • type = regex
<context:component-scan base-package="com.hh">

    <context:exclude-filter type="regex" expression="com\.hh\.wechat\.dev\.domain\.spring\.[^.]+(Controller)"/>

    <context:exclude-filter type="regex" expression="com\.hh\.wechat\.main\.domain\.spring\.[^.]+(Controller)"/>

</context:component-scan>
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流烟默

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值