component-scan做了些什么:源码解读

来源:互联网 发布:软件应用商业计划书 编辑:程序博客网 时间:2024/05/22 04:36

本文引自我的个人博客: sunmingshuai.coding.me

本文目的

本文主要解决一个问题就是component-scan会扫描注册哪些注解标注的类 被@Controller标注的类会被扫描注册吗? 当然本文也可以当作spring解析非默认空间下元素的一个教程

正文

当遇到component-scan这样非标准或者称为自定义的元素标签时 spring会通过spring.handlers文件中的对应关系
http://www.springframework.org/schema/context=
org.springframework.context.config.ContextNamespaceHandler

找到ContextNamespaceHandler 通过ContextNamespaceHandlerinit()方法自动注册一些解析器 下面列出了所有以context为命名空间的组合 例如常见的<context:componet-scan>元素 根据注册对应关系 知道其对应的解析器为ComponentScanBeanDefinitionParser

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

那我们看一下<context:component-scan>的解析器ComponentScanBeanDefinitionParser的源码 (ComponentScanBeanDefinitionParser继承自BeanDefinitionParser) spring解析<context:component-scan>的时候会调用ComponentScanBeanDefinitionParserparse()函数

//element 代表的是完整的<context:component-scan>标签//parserContext 解析的上下文环境 能拿到一些诸如readerContext registry等变量public BeanDefinition parse(Element element, ParserContext parserContext) {    //将指定base-package拆分成string数组的形式    String[] basePackages = StringUtils.tokenizeToStringArray(element.getAttribute(BASE_PACKAGE_ATTRIBUTE),            ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);    // 根据xml配置或者默认配置(如果没有指定的话)配置扫描器ClassPathBeanDefinitionScanner    ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);    //scanner扫描包下面含有特殊注解标注的类     //@Component @Named @ManagedBean (@Controller @Service @Repository @Configuration)都会被注册     Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);    // 注册组件以及一些我们常见的注解处理器(BPP) 如@Resouce @Autowired @Configuration @Value @Required @PostConstruct    //那么以后看到属性注入(IOC/DI)的时候 就不会奇怪 这些注解的处理器(BPP)是在哪里注册的了 在这里!    registerComponents(parserContext.getReaderContext(), beanDefinitions, element);    return null;}

看一下scanner的创建以及设置

    protected ClassPathBeanDefinitionScanner configureScanner(ParserContext parserContext, Element element) {    XmlReaderContext readerContext = parserContext.getReaderContext();    boolean useDefaultFilters = true;    if (element.hasAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE)) {        useDefaultFilters = Boolean.valueOf(element.getAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE));    }    // 生成scanner的时候 在其父类构造函数中调用了`registerDefaultFilters()`方法中加入了3个注解类型的typeFilter 分别是@Component     // @ManagedBean @Named 意思也就是看类上面有没有这三个注解或者注解的元注解中是否含有这三个注解之一    // 这里虽然没有加入如@Service的注解 但看@Service的定义会发现@Service注解也被@Component给标注了    ClassPathBeanDefinitionScanner scanner = createScanner(readerContext, useDefaultFilters);    scanner.setResourceLoader(readerContext.getResourceLoader());    scanner.setEnvironment(parserContext.getDelegate().getEnvironment());    // 利用spring 启动的时候根据xml文件或者默认配置(如果没有指定的话)生成的BeanDefinition规则    scanner.setBeanDefinitionDefaults(parserContext.getDelegate().getBeanDefinitionDefaults());    scanner.setAutowireCandidatePatterns(parserContext.getDelegate().getAutowireCandidatePatterns());    if (element.hasAttribute(RESOURCE_PATTERN_ATTRIBUTE)) {        scanner.setResourcePattern(element.getAttribute(RESOURCE_PATTERN_ATTRIBUTE));    }    try {        // 扫描注解定义的bean 需要定义一种beanName的生成规则  一般是驼峰命名法        parseBeanNameGenerator(element, scanner);    }    catch (Exception ex) {        readerContext.error(ex.getMessage(), readerContext.extractSource(element), ex.getCause());    }    try {    // bean的scope singleton?prototype?        parseScope(element, scanner);    }    catch (Exception ex) {        readerContext.error(ex.getMessage(), readerContext.extractSource(element), ex.getCause());    }    // 根据xml配置生成includeFilter excludeFilter 在创建scanner类的时候 就硬编码进去了3个注解类型    // 的typeFilter到includeFilter里面    // 类必须满足以下条件才会被注册 因为@Component等是被硬编码进去的 所以只有下面一种情况才会被注册了    // 不被excludeFilter匹配 并且被includeFilter匹配    parseTypeFilters(element, scanner, readerContext, parserContext);    return scanner;}

下面在看一下真正的扫描过程doScan 我们发现spirng中真正干活的一般都是以do开头 前面的那么多只是为了做铺垫 单一职责

    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) {        //寻找被@Component或者@Named注解标注的类 或者 类的注解的元注解中含有这两个注解 例如常见的@Respsitory @Controller @Service        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);        // 对于生成的AnnotatedBeanDefinition做一些转化工作 例如前面说到的应用bd(BeanDefinition)默认配置         // 处理@Primary @Lazy @DependsOn @Role注解等        // 转化成BeanDefinitionHolder对象        for (BeanDefinition candidate : candidates) {            ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);            candidate.setScope(scopeMetadata.getScopeName());            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);                //注册到容器中                registerBeanDefinition(definitionHolder, this.registry);            }        }    }    return beanDefinitions;}

看一下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;        //这里的resouces代表的就是路径下的各个类了        Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);        boolean traceEnabled = logger.isTraceEnabled();        boolean debugEnabled = logger.isDebugEnabled();        for (Resource resource : resources) {            if (traceEnabled) {                logger.trace("Scanning " + resource);            }            if (resource.isReadable()) {                try {// 这一步的作用就是能够不必加载class但能拿到class各种属性 并且这里也不能载入class(有很多是单例模式) 还没到那一步// 只是为了做匹配 生成bd而已                    MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);// 根据includeFilters excludeFilters做匹配 因为篇幅的限制 不详谈 有兴趣的可以研究一下typeFilter的实现                    if (isCandidateComponent(metadataReader)) {                        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);                        }                        else {                            if (debugEnabled) {                                logger.debug("Ignored because not a concrete top-level class: " + resource);                            }                        }                    }                    else {                        if (traceEnabled) {                            logger.trace("Ignored because not matching any filter: " + resource);                        }                    }                }                catch (Throwable ex) {                    throw new BeanDefinitionStoreException(                            "Failed to read candidate component class: " + resource, ex);                }            }            else {                if (traceEnabled) {                    logger.trace("Ignored because not readable: " + resource);                }            }        }    }    catch (IOException ex) {        throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);    }    return candidates;}

到这里 我们算是差不多看完了scanner的扫描注册过程 扫描注册过程不过循环base-package路径下面的所有类 看是否有标注特定注解(硬编码)的类 将这些类转化成bd 供我们后续使用 这里要注意的就是这里不能将类实例化
我们再回到ComponentScanBeanDefinitionParserparse()方法 看下registerComponents()方法
registerComponents()方法中除了组件外 还有一个比较重要的事情 就是注册我们后面属性填充(IOC)需要用到的一些BPP 例如@Resouce @Autowired等

    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) {        //这里注册我们需要的BPP            Set<BeanDefinitionHolder> processorDefinitions =                    AnnotationConfigUtils.registerAnnotationConfigProcessors(readerContext.getRegistry(), source);            for (BeanDefinitionHolder processorDefinition : processorDefinitions) {                compositeDef.addNestedComponent(new BeanComponentDefinition(processorDefinition));            }        }        readerContext.fireComponentRegistered(compositeDef);    }public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(            BeanDefinitionRegistry registry, Object source) {        Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<BeanDefinitionHolder>(4);        // @Configuration        if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {            RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);            def.setSource(source);            beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));        }        //@Autowired @Value        if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {            RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);            def.setSource(source);            beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));        }        //@Required        if (!registry.containsBeanDefinition(REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {            RootBeanDefinition def = new RootBeanDefinition(RequiredAnnotationBeanPostProcessor.class);            def.setSource(source);            beanDefs.add(registerPostProcessor(registry, def, REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));        }        //@PostConsruct @Resouce 等java注解        // Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.        if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {            RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);            def.setSource(source);            beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));        }        //        // Check for JPA support, and if present add the PersistenceAnnotationBeanPostProcessor.        if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) {            RootBeanDefinition def = new RootBeanDefinition();            try {                def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME,                        AnnotationConfigUtils.class.getClassLoader()));            }            catch (ClassNotFoundException ex) {                throw new IllegalStateException(                        "Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex);            }            def.setSource(source);            beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME));        }        return beanDefs;    }

总的来说 component-scan 做了什么事情就比较清晰了 下面做一下总结

  • 根据配置生成扫描类ClassPathBeanDefinitionScanner 扫描注册包下符合匹配规则的类
  • 扫描注册被@Controller @Service @Respsitory @Component @Configuration等标注的类 因为@Controller @Service @Respsitory注解的元注解包含@Component 也就是说@Controller @Service @Respsitory本身被@Component标注 而匹配规则中有一条就是元注解包含@Component @Named 或者 @ManagedBean
  • 注册处理如@Resouce @Autowired @PostConstruct等的BPP

当然由于篇幅限制 并没有把所有的知识点都讲到 很多也是点到 那些东西需要读者有时间的时候自己跟着代码走一遍了
那么又出现一个问题:
既然@Controller注解标注的类也被像普通bean一样注册了 那么在web项目中 又是怎么知道哪些是我们需要的Controller类呢?
且听下回分析…

原创粉丝点击