在以前文章Spring自定义标签实现中,曾说过,在sprin g 配置文件中,除了be an beans import 常用的标签意外,其他的标签都是遵循Spring 自定义标签的扩展机制进行实现功能的,

component-scan标签也不例外,关于component-scan标签的实现逻辑则是在Spring-context 包下 org.springframework.context.config.ContextNamespaceHandler 类中定义

我们看一看它的parse 解析方法:

接下来就分析它的parse 方法:主要就是三步:

.找到 我们定义的 base-package 属性内容
.定义扫描器
.扫描包内容

第一步是拿到  base-package 属性来确定我们需要扫描的包的路径,不同的包我们可以用,;进行分割 <context:component-scan base-package="com.project" />

 String[] basePackages = StringUtils.tokenizeToStringArray(element.getAttribute("base-package"), ",; \t\n");

第二步 确定 ClassPathBeanDefinitionScanner 扫描器,这个扫描器大有学问

我们点进去 ClassPathBeanDefinitionScanner scanner = this.configureScanner(parserContext, element);
 XmlReaderContext readerContext = parserContext.getReaderContext();
boolean useDefaultFilters = true;
if (element.hasAttribute("use-default-filters")) {
useDefaultFilters = Boolean.valueOf(element.getAttribute("use-default-filters"));
} ClassPathBeanDefinitionScanner scanner = this.createScanner(readerContext, useDefaultFilters);

这里就创建了 ClassPathBeanDefinitionScanner对象,并且使用了默认的拦截器 useDefaultFilters,这个拦截器大有作用,我们往下看一些父类方法的调用

 return new ClassPathBeanDefinitionScanner(readerContext.getRegistry(), useDefaultFilters);
this(registry, useDefaultFilters, getOrCreateEnvironment(registry));
super(useDefaultFilters, environment);

我们继续,除去一些定义:往下


    if (useDefaultFilters) {
registerDefaultFilters();
}
this.environment = environment;

我们使用了默认的拦截器,这里的 useDefaultFilters 是true ,然后执行  registerDefaultFilters();注册默认的拦截器,我们看一看这个默认的注册方法,很关键

/**
* Register the default filter for {@link Component @Component}.
* <p>This will implicitly register all annotations that have the
* {@link Component @Component} meta-annotation including the
* {@link Repository @Repository}, {@link Service @Service}, and
* {@link Controller @Controller} stereotype annotations.
* <p>Also supports Java EE 6's {@link javax.annotation.ManagedBean} and
* JSR-330's {@link javax.inject.Named} annotations, if available.
*
*/
@SuppressWarnings("unchecked")
protected void registerDefaultFilters() {
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) cl.loadClass("javax.annotation.ManagedBean")), false));
logger.debug("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
}
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) cl.loadClass("javax.inject.Named")), false));
logger.debug("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-330 API not available - simply skip.
}
}

就是上面这段代码,我们姑且先不看代码,先看一看它的注释翻译:

/**
* Register the default filter for {@link Component @Component}.
* <p>This will implicitly register all annotations that have the
* {@link Component @Component} meta-annotation including the
* {@link Repository @Repository}, {@link Service @Service}, and
* {@link Controller @Controller} stereotype annotations.
* <p>Also supports Java EE 6's {@link javax.annotation.ManagedBean} and
* JSR-330's {@link javax.inject.Named} annotations, if available.
*
*/

 注册一个带有@Component 的默认拦截器,将隐士的注册所有的带有@Component 的元注解,包含@Repository @Service @Controller 注解

通过这个解释,我们可以得知,默认的拦截器会拦截带有@Component 注解的类,我们看一下@Controller @Repository @Service的定义

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Component {
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {

看一看出来什么呢,这三个注解都实现了@Componet 的注解,所以都会被拦截住;

其实通过它的注释我们就已经明白了,我们就看一下代码逻辑吧,这个 includeFilters 其实是一个链表

private final List<TypeFilter> includeFilters = new LinkedList<TypeFilter>();

进行注册Component 拦截器:

this.includeFilters.add(new AnnotationTypeFilter(Component.class));

第三步具体的逻辑大家可以往下点点;我们有了扫描器了,我们其实可以进行第三步扫描了:

Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);

这个方法,扫描到匹配的class文件,装入Set<File>容器里:可以看出获取文件如果是文件目录采用了递归的方式进行下一层寻找

protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<File> result) throws IOException {
if (logger.isDebugEnabled()) {
logger.debug("Searching directory [" + dir.getAbsolutePath() +
"] for files matching pattern [" + fullPattern + "]");
}
File[] dirContents = dir.listFiles();
if (dirContents == null) {
if (logger.isWarnEnabled()) {
logger.warn("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");
}
return;
}
for (File content : dirContents) {
String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {
if (!content.canRead()) {
if (logger.isDebugEnabled()) {
logger.debug("Skipping subdirectory [" + dir.getAbsolutePath() +
"] because the application is not allowed to read the directory");
}
}
else {
doRetrieveMatchingFiles(fullPattern, content, result);
}
}
if (getPathMatcher().match(fullPattern, currPath)) {
result.add(content);
}
}
}

转换成Resource【】数组:

return result.toArray(new Resource[result.size()]);

将每一个resource 封装成 MetadataReader 对象,就可以获取这个class文件的 所有信息,比如是不是接口啊,实现类等信息,这个封装过程需要好好看一下,asm的操作

MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);

然后就是拦截器的判断:

isCandidateComponent(metadataReader)

拦截器的过滤匹配

/**
* 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 {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return isConditionMatch(metadataReader);
}
}
return false;
}
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}

通过这个的学习,大家可以去看一下Mybatis 的包扫描了,它则是继承了Spring的扫描类,但并没有使用默认的拦截器,而是配置了自己的一套拦截器及拦截接口等一些配置;

 

 

Spring component-scan 标签的实现的更多相关文章

  1. [Spring Boot] Use Component Scan to scan for Bean

    Component Scan is important concept when we want to create Bean. Currently we know what, for the cla ...

  2. Spring <context:component-scan>标签属性 use-default-filters 以及子标签 include-filter使用说明

    Spring <context:component-scan>标签作用有很多,最基本就是 开启包扫描,可以使用@Component.@Service.@Component等注解: 今天要作 ...

  3. 基于Spring开发——自定义标签及其解析

    1. XML Schema 1.1 最简单的标签 一个最简单的标签,形式如: <bf:head-routing key="1" value="1" to= ...

  4. Spring源码学习-容器BeanFactory(三) BeanDefinition的创建-解析Spring的默认标签

    写在前面 上文Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作中Spring对XML解析后创建了对应的Docum ...

  5. Spring中bean标签的属性和值:

    Spring中bean标签的属性和值: <bean name="user" class="com.pojo.User" init-method=" ...

  6. Spring——使用自定义标签

    文章内容参考了<Spring源码深度解析>一书.自己照着书中内容做了一遍,不懂的地方以及采坑的地方会在文中记录. 推荐一篇post,关于Spring配置文件的命名空间: https://w ...

  7. Spring表单标签

    虽然我们可以使用HTML原生的form表单标签来轻松的写出一个表单,其实我一直都是这样做的,但是使用Spring表单标签可以更方便我们完成例如:验证失败后表单数据的回填功能(虽然你可以使用EL+JST ...

  8. spring:使用<prop>标签为Java持久属性集注入值

    spring:使用<prop>标签为Java持久属性集注入值 使用 spring 提供的<prop>为Java持久属性集注入值,也就是向 java.util.Propertie ...

  9. not registered via @EnableConfigurationProperties or marked as Spring component

    利用@ConfigurationProperties(prefix = "")来绑定属性时报错: not registered via @EnableConfigurationPr ...

  10. Spring IoC 自定义标签解析

    前言 本系列全部基于 Spring 5.2.2.BUILD-SNAPSHOT 版本.因为 Spring 整个体系太过于庞大,所以只会进行关键部分的源码解析. 本篇文章主要介绍 Spring IoC 容 ...

随机推荐

  1. 解除Portal for ArcGIS与ArcGIS Server的联合

    将ArcGIS Server站点添加到Portal中,可以实现ArcGIS Server站点的单点登录特性,并可以与Portal共享Server站点发布的内容,同时通过将联合服务器注册为托管服务器后还 ...

  2. 搭建Hadoop

    最近开始学习hadoop现实是完全分布式 安利大佬博客学习 https://blog.csdn.net/downing114/article/details/60956979 在Ubuntu上学习  ...

  3. Java 基于WebMagic 开发的网络爬虫

    第一次接触爬虫,之所以选择WebMagic,是因为文档齐全.用法简单.而且框架一直在维护. WebMagic是一个简单灵活的Java爬虫框架.基于WebMagic,我们可以快速开发出一个高效.易维护的 ...

  4. EF core2.1+MySQL报错'Void Microsoft.EntityFrameworkCore.Storage.Internal.RelationalParameterBuilder..ctor(Microsoft.EntityFrameworkCore.Storage.IRelationalTypeMapper)

    一.使用.net core 2.0 EF mysql 运行一直报错如下: An unhandled exception occurred while processing the request. M ...

  5. Hive实现交叉二维分析的小语句

    1. 梳理出你要的列和行维度 列维度: 每一周 行维度: 年级 + 学科 + 班型 2. 对数据按周增序进行聚合 (即根据列维度) ,生成list concat_ws 和 collect_list ( ...

  6. 一行js代码识别Selenium+Webdriver及其应对方案

    有不少朋友在开发爬虫的过程中喜欢使用Selenium + Chromedriver,以为这样就能做到不被网站的反爬虫机制发现. 先不说淘宝这种基于用户行为的反爬虫策略,仅仅是一个普通的小网站,使用一行 ...

  7. 嵌入式linux——点亮led灯(二)

    刚才在jz2440板子上写了一个点亮中间led的程序,前前后后十几分钟才好.最终代码 本节内容: 1. 汇编点灯 2. C点灯 3. 参数选择点灯 4. 按键点灯 1. 汇编点灯 .text .glo ...

  8. python大法好——飞机大战

    import pygame from pygame.locals import * import time def key_control(hero_temp): # 获取事件,比如按键等 for e ...

  9. 使用Idea从github上获取项目

    转载自:https://www.cnblogs.com/30go/p/7909246.html 整体分三步: 下载和安装git 配置idea 从git获取项目 详细步骤: 1. 下载和安装git 下载 ...

  10. 基于MPI的大规模矩阵乘法问题

    转载请注明出处. /* Function:C++实现并行矩阵乘法; Time: 19/03/25; Writer:ZhiHong Cc; */ 运行方法:切到工程文件x64\Debug文件下,打开命令 ...