IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Spring自定义标签解析 -> 正文阅读

[Java知识库]Spring自定义标签解析

Spring源码分析:

  1. Spring源码之XML默认标签解析
  2. Spring自定义标签 component-scan 源码解析

自定义标签解析

除了 Spring 默认的标签,还可以自定义标签,如最常使用的 context:component-scan,并且通过改标签的命名空间来解析。

那么什么是命名空间?

在 XML 中,元素名称是由开发者定义的,当两个不同的文档使用相同的元素名时,就会发生命名冲突。

如 Spring 的 XML 配置文件,配置项都在 <beans> 标签中,但这时开发者如果自定义一个自己的标签,同样命名为 <beans>,便造成了冲突,XML 解析器无法分辨这些冲突的命名。因此,对于 Spring 来说在最基本的配置中,含有 xmlns,xmlns:xsi,xsi:schemaLocation 三项,这三项是 Spring 最基本的命名空间,其含义如下:

  1. xmlns 表示是该 XML 文件的默认命名空间;
  2. xmlns:xsi 表示该 XML 文件遵守 xml 规范;
  3. xsi:schemaLocation 表示具体用到的 schema 资源。

如下配置文件:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
   http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/aop
   http://www.springframework.org/schema/aop/spring-aop-3.2.xsd"
       default-lazy-init="false">

    <bean id="SpringXml" class="net.javatv.bean.SpringXml"/>

    <context:component-scan base-package="net.javatv.custom"/>

</beans>

自定义类

import lombok.Data;
import org.springframework.stereotype.Component;

@Data
@Component
public class CustomBean {

    private String msg = "11";
}

然后再去看自定义标签的解析,在DefaultBeanDefinitionDocumentReader#parseBeanDefinitions()中的parseCustomElement()

image-20211026143205386

这个方法中,会看到有一个 NamespaceHandler,这就是用来解析xml中具体标签的一个 handler 类,是一个接口,具体的解析操作在自定义个性化标签时需要自己实现,如下:

image-20211026144315667

这个handler对象的创建会去调用名称空间解析器的 resolve 方法得到,而这个 resolver 在前面创建 ReaderContext的时候创建的,默认类型为:DefaultNamespaceHandlerResolver,点开 resolve 方法如下:

image-20211026145217587

这个方法中,会先去实例化 NamespaceHandler,实例化的过程是通过反射,反射所需要的class是通过配置文件配置的,NamespaceHandler 对象通过 SPI 机制获取 spring 中所有 jar 包里面的 META-INF/spring.handlers 文件,并且建立映射关系 类似于如下图所示:

image-20211026150003302

也就是说,如果我们自定义一个标签的话也就是通过上面的方式,在自己的项目 META-INF/spring.handlers 的结构中添加配置,当然,这在 Spring Boot 中一个注解就能解决。

在实例化 NamespaceHandler 之后,再去加载具体的用于解析自定义标签的 Parser 类,也就是代码中的 init()方法:

image-20211026151022153

到此,都是对一些类进行了初始化,还没有进行真正的解析操作,在回头看BeanDefinitionParserDelegate#parseCustomElement(),当 NamespaceHandler 对象通过标签 component-scan拿到对应的解析器:

image-20211026152758315

在拿到 ComponentScanBeanDefinitionParser#parse()解析器后开始解析:

image-20211026155038813

这个方法中就是最最后真正解析标签中的属性,并注册bean定义的方法。此处有一个细节:

问:上述方法中的第二行中,调用了一个resolvePlacehodler方法,第一步已经得到了base-package的属性了,此处为啥还要再解析一次呢?

答:因为 base-package 的值支持占位符,即:<context:component-scan base-package="${javatv.path}"/>,所以需要解析出真正的包路径。

configureScanner()方法中,这个方法同自定义标签中的方法,就是解析每个节点的值,然后组装成一个scanner 对象,并返回。然后调用doScan()方法,这个方法就是根据解析到的 xml 中的各种属性以及内部 bean 定义,执行具体的 BeanDefinition 的定义,即:把真正的 BeanDefinition 注册到 bean 定义注册中心,代码如下:

image-20211026161402541

通过层层递归扫描 base-package 下的包,先扫描出 classpath:/base-package.class 结尾的所有文件,然后再根据过滤器扫描出具有@Service@Component 注解的类添加到对应的集合 Set<BeanDefinition>完成 BeanDefinition 的注册。

先看看findCandidateComponents()中的scanCandidateComponents()即可看到 Bean 的定义过程 :

image-20211026170244142

值得注意的是,在定义之前,在方法isCandidateComponent()中会判断扫描出来的类是否满足条件:

image-20211026173229818

该方法涉及到component-scan的属性:

  • useDefaultFilters:默认为 true,此时Spring扫描类时发现如果其被标注为@Component、@Repository、@Service、@Controller则自动实例化为bean并将其添加到上下文中,如果设置为false,即使将其标注为@Component或者其他,Spring都会忽略。
  • includeFilters:指定扫描时需要实例化的类型,我们可以从名字看到这是一个Filter,你可以自己定义该Filter,Spring为我们提供了一套方便的实现,我们可以根据标注、类、包等相关信息决定当扫描到该类时是否需要实例化该类,需要注意的是如果你仅仅想扫描如@Controller不仅要加includeFilters,还需要将useDefaultFilters 设置为false。
  • excludeFilter,指定扫描到某个类时需要忽略它,实现和上一个Filter一样,区别只是如果Filter匹配,Spring会忽略该类

因此,Spring每扫描一个类,都会经过 includeFilters 以及 excludeFilters,如果某个Filter匹配,就执行相应的操作(实例化或者忽略)。而在源码中也有体现,即configureScanner()创建的ClassPathBeanDefinitionScanner,其构造方法如下:

image-20211026203531704

进入registerDefaultFilters()方法:

image-20211026203724537

可以看到源码中是把有 @Component 注解的类添加到 includeFilters ,那么和其他的注解有什么关系呢?比如@Controller并没有添加进去为什么也能加载?

当然,这个问题很好回答,我们到@Component的包下可以看到:

image-20211026204613401

@Controller继承自 @Component,其他几个也相同,这里出现了一个在开发中基本没使用过的标签@AliasFor,其中之一的作用就是可以表示继承关系。

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-10-28 12:16:13  更:2021-10-28 12:16:49 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 0:35:58-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码