Spring自定义标签解析

Spring源码分析:

  1. Spring源码:源码编译及阅读源码入门
  2. Spring源码:XML默认标签解析
  3. Spring源码:自定义标签解析
  4. Spring源码:Bean工厂的后置处理器invokeBeanFactoryPostProcessors

自定义标签解析

除了 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,其中之一的作用就是可以表示继承关系。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

汪了个王

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值