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

224 阅读5分钟
原文链接: mp.weixin.qq.com

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

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

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


                            <?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解析器进行处理:


                                                                                                                        public class 
                                                                                                                            ScannerNameSpaceHandler extends NamespaceHandlerSupport { 
                                                                                                                                @Override public 
                                                                                                                                    void init() {  registerBeanDefinitionParser(
                                                                                                                                        "scan", new CustomScannerBeanDefinitionParser()); }}
                                                                                                                                            

3、自定义CustomScannerBeanDefinitionParser解析器:


                                                                                                                                                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配置类:


                                                                                                                                                                                                    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位置,这样就实现了标签和后台逻辑关联,其内容见下:

                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        Spring.schemashttp\://www.simon.org/schema/scan.xsd=META-INF/custom-scan.xsd
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    Spring.handlershttp\://www.simon.org/schema/scan=customschema.demo03.ScannerNameSpaceHandler
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        

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

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

                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                @Target(ElementType.TYPE)
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    @Retention(RetentionPolicy.RUNTIME)
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        @Documented
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            @Indexed
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                public 
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    @interface MyComponent { 
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        String 
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            value
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                () 
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    default "";}
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        

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

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

                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        <?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、测试用例:

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

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

长按识别关注, 持续输出原创