在以前文章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. VS2015创建类库项目后添加不了WPF资源字典,窗口,用户控件处理办法

    打开项目工程文件在PropertyGroup标签最后加上下面3行: <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FA ...

  2. python pdfkit html转pdf响应式轮子 django例

    pip install pdfkit 本例用django做的请求,换成对应框架即可 此方法可将html页面转成pdf下载 #!/usr/bin/env python # coding:utf-8 im ...

  3. Python threading(多线程)

    threading模块在较低级别thread模块之上构建更高级别的线程接口. 一.threading模块定义了以下函数和对象: threading.active_count() 等同于threadin ...

  4. Subline_Text3消除更新提示

    很简单的一个小问题,去除SubLine text的更新问题,在网上看了很多资料也不可以,原来是因为注册码和版本的问题,在这简单做个记录. 关于安装问题就不说啦 百度文库安装方法 安装完成之后一定要注册 ...

  5. Linux 文件目录管理命令

    1.touch 用于设置空白文件或设置文件时间 touch命令参数及作用 参 数                                                             ...

  6. Python校验用户名是否合法示例

    #校验用户名是否合法例子: #输入账号密码 #input #如果账号存在提示已经注册,如果不存在的,就可以注册 all_users [] #不能为空 #strip() #用户名长度6-12之间 #le ...

  7. 不同用户操作hadoop,Permission denied: user=root, access=WRITE, inode="/user"

    关于不能执行Hadoop命令 并报权限问题执行错误1.Permission denied: user=root, access=WRITE, inode="/":hdfs:supe ...

  8. servlet对象的生命周期

    servlet对象默认第一次访问的时候创建,服务器关闭的时候销毁.当servlet对象创建的时候会调用init方法,当对象销毁的时候,会调用destroy方法,每次访问servlet时,都会调用ser ...

  9. @Data的注解使用以及在IDEA上安装

    平时在开发过程中创建实体类的时候就经常的操作是:先写成员变量,然后再提供getXxx().setXxx()方法,然后看需要再提供toString等方法.这样一来不仅会发现每写一个实体类的话就会有很多相 ...

  10. 嵌入式linux——S3C2440介绍(二)

    一.关于S3C2440要知道的事情 (唉,刚才按错了键,弹出了网页的代码,关闭之后,白屏,什么都没有,网页返回,就没保存上,在敲一边...) 1. S3C244三星公司的芯片 2. S3C2440的构 ...