前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Spring源码】- 07 扩展点之自定义标签

【Spring源码】- 07 扩展点之自定义标签

作者头像
Reactor2020
发布2023-03-22 18:57:38
2730
发布2023-03-22 18:57:38
举报
文章被收录于专栏:【云原生 • Prometheus】

Spring中正逐渐采用注解方式取代XML配置方式,所以,使用XML配置的机会正越来越少。然后,如果你开发的工具模块可能会被很多系统使用,考虑到兼容性问题,就需要提供XML方式集成,这时就需要自定义标签;还有,你在看一些开源源码时,一般也是提供自定义标签方式集成。所以,还是可以去了解一下自定义标签实现。

Spring中使用自定义标签还是比较简单,下面我们就实现一个自定义标签<scan>,其功能类似<context:component-scan base-package="aa.bb"/>标签:将指定包路径下带有指定注解的Bean扫描注册。

1、首先,在resources/META-INF目录下定义一个xsd文件,描述自定义<scan>标签属性:

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.simon.org/schema/scan"
   xmlns:xsd="http://www.w3.org/2001/XMLSchema"
   targetNamespace="http://www.simon.org/schema/scan"
   elementFormDefault="qualified">

 <xsd:complexType name="scan">
  <xsd:attribute name="id" type="xsd:string">
   <xsd:annotation>
    <xsd:documentation>
     <![CDATA[ The unique identifier for a bean. ]]>
    </xsd:documentation>
   </xsd:annotation>
  </xsd:attribute>
  <xsd:attribute name="annotation" type="xsd:string">
   <xsd:annotation>
    <xsd:documentation>
     <![CDATA[ The unique identifier for a bean. ]]>
    </xsd:documentation>
   </xsd:annotation>
  </xsd:attribute>
  <xsd:attribute name="base-package" type="xsd:string">
   <xsd:annotation>
    <xsd:documentation>
     <![CDATA[ The price for a bean. ]]>
    </xsd:documentation>
   </xsd:annotation>
  </xsd:attribute>
 </xsd:complexType>

 <xsd:element name="scan" type="scan">
  <xsd:annotation>
   <xsd:documentation><![CDATA[ The service config ]]></xsd:documentation>
  </xsd:annotation>
 </xsd:element>

</xsd:schema>

2、自定义NamespaceHandler,注册<scan>使用CustomScannerBeanDefinitionParser解析器进行处理:

代码语言:javascript
复制
public class ScannerNameSpaceHandler extends NamespaceHandlerSupport {
 @Override
 public void init() {
  registerBeanDefinitionParser("scan", new CustomScannerBeanDefinitionParser());
 }
}

3、自定义CustomScannerBeanDefinitionParser解析器:

代码语言:javascript
复制
public class CustomScannerBeanDefinitionParser extends AbstractBeanDefinitionParser {

 @Override
 protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
  BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(CustomScannerConfigurer.class);

  ClassLoader classLoader = ClassUtils.getDefaultClassLoader();

  try {
   String annotationClassName = element.getAttribute("annotation");
   if (StringUtils.hasText(annotationClassName)) {
    Class<? extends Annotation> annotationClass = (Class<? extends Annotation>) classLoader
      .loadClass(annotationClassName);
    builder.addPropertyValue("annotationClass", annotationClass);
   }
  } catch (Exception ex) {
   XmlReaderContext readerContext = parserContext.getReaderContext();
   readerContext.error(ex.getMessage(), readerContext.extractSource(element), ex.getCause());
  }
  builder.addPropertyValue("basePackage", element.getAttribute("base-package"));
  return builder.getBeanDefinition();
 }

}

parseInternal()方法解析标签,然后生成一个BeanDefinitionSpring会自动将其注册到IoC容器中。如果标签只会注册单个Bean,这里是需要返回注册Bean对应的BeanDefinition即可;如果是多个情况,这里一般是注册一个配置类,将标签配置的属性注入到配置类中,然后由配置类统一处理。

4、自定义CustomScannerConfigurer配置类:

代码语言:javascript
复制
public class CustomScannerConfigurer
  implements BeanDefinitionRegistryPostProcessor, InitializingBean {

 private String basePackage;

 private Class<? extends Annotation> annotationClass;

 @Override
 public void afterPropertiesSet() throws Exception {
  //参数校验
  notNull(this.basePackage, "Property 'basePackage' is required");
 }

 @Override
 public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
 }

 @Override
 public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
  ClassPathBeanDefinitionScanner scanner =
    new ClassPathBeanDefinitionScanner(registry, false);
  scanner.addIncludeFilter(new AnnotationTypeFilter(annotationClass));
  scanner.setIncludeAnnotationConfig(false);
  int beanCount = scanner.scan(basePackage);
  registry.getBeanDefinitionNames();
 }


 public String getBasePackage() {
  return basePackage;
 }

 public void setBasePackage(String basePackage) {
  this.basePackage = basePackage;
 }

 public Class<? extends Annotation> getAnnotationClass() {
  return annotationClass;
 }

 public void setAnnotationClass(Class<? extends Annotation> annotationClass) {
  this.annotationClass = annotationClass;
 }
}

CustomScannerConfigurer实现了BeanDefinitionRegistryPostProcessor, InitializingBean两个接口,之前分析过这两个接口。重点在BeanDefinitionRegistryPostProcessor这个接口,其是一个BeanFactoryPostProcessor类型扩展,可以向IoC容器注册BeanDefinition。在postProcessBeanDefinitionRegistry()方法中创建一个ClassPathBeanDefinitionScanner对象,并将标签中配置设置进去,即可实现扫描指定包路径下带有指定注解的Bean

5、xsd是标签描述文件,NamespaceHandler则是标签后台处理逻辑入口,现在需要将两者进行关联,在resources/META-INF目录下创建两个文件:Spring.schemasSpring.handlers,分别指定xsd文件位置和NamespaceHandler位置,这样就实现了标签和后台逻辑关联,其内容见下:

代码语言:javascript
复制
Spring.schemas
http\://www.simon.org/schema/scan.xsd=META-INF/custom-scan.xsd
代码语言:javascript
复制
Spring.handlers
http\://www.simon.org/schema/scan=customschema.demo03.ScannerNameSpaceHandler

自定义标签描述以及对于的后台处理逻辑都配置完成,下面我们就开始进行测试。

1、首先,定义个注解,用于在扫描Bean时过滤使用:

代码语言:javascript
复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface MyComponent {
 String value() default "";
}

2、在customschema.demo03.bean包路径下定义三个类:TestService01TestService02TestService03,将后面两个类使用@MyComponent注解标注下;

3、编写SpringXml配置文件,这里就可以使用我们刚才自定义的标签:

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:custom="http://www.simon.org/schema/scan"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.simon.org/schema/scan http://www.simon.org/schema/scan.xsd">

 <custom:scan id="scan" annotation="customschema.demo03.MyComponent"
     base-package="customschema.demo03.bean"/>

</beans>

4、测试用例:

代码语言:javascript
复制
@Test
public void test01() {
 ApplicationContext context = new ClassPathXmlApplicationContext("custom-schema.xml");
 Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);
}

从输出结果就可以看到,TestService01由于没有带有@MyComponent注解,所以没有注册,TestService02TestService03都会被注册到容器中。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-04-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Reactor2020 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档