在以前文章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. 《深度探索C++对象模型》读书笔记(二)

    第三章:Data语意学 这一章主要讲了类中的数据在内存中是如何分配的,包括(多重)继承和多态. 让我们首先从一段代码开始: class X{}; class Y :virtual public X{} ...

  2. udt的java实现

    udt协议是什么? 我就不回答了,可以网上搜索,一直都是c++的,java的实现已经很久没有修改了 经过测试,java版本有些一问题,现在已经将其修复,已经上传到csdn 另外自己根据实际的应用,再次 ...

  3. Java异常处理——受控(checked)的异常(throws语句)

    受控与不受控的异常 1.throws语句中声明的异常称为受控(checked)的异常,通常直接派生自Exception类. 2.RuntimeException(其基类为Exception) 和Err ...

  4. majingwei 利用xml导出word文件---换行

    xml不能识别<br>,需要将换行标记转换成<w:br/>

  5. [UnityShader基础]06.#pragma multi_compile

    参考链接: https://blog.csdn.net/qq826364410/article/details/81774741 https://docs.unity3d.com/Manual/SL- ...

  6. 通过日志来看Spring跨库更新操作的事务

    场景介绍: 一个项目俩个数据源,连接俩个不同的库 数据源初始化 @Configuration @MapperScan(basePackages = "com.qing.mapper.paym ...

  7. loadrunner参数化使用mysql数据源失败解决方法

    操作系统:win7 在64位的操作系统上,如果你想要连接32位mysql,避免安装mysql connector/odbc 64位,否则即使配置ODBC数据源连接正常,但loadrunner无法正常调 ...

  8. LeetCode 102. Binary Tree Level Order Traversal 二叉树的层次遍历 C++

    Given a binary tree, return the level order traversal of its nodes' values. (ie, from left to right, ...

  9. JAVA 解决 SpringBoot 本地读取文件成功,打包后读取文件失败的方法

    SpringBoot 的日常开发中,我们会发现当我们使用  InputStream input = getClass.getResource(path) 读取文件或者模板时,在 ida 中运行 测试的 ...

  10. Java学习--枚举

    枚举类型enum,地位等同于class,interface 使用enum定义的枚举类型,也是一种变量类型,可用于声明变量 枚举的一些特征 1.它不能有public的构造函数,这样做可以保证客户代码没有 ...