基于Spring Schema的自定义注解示例

半兽人 发表于: 2017-12-28   最后更新时间: 2018-01-05 18:03:57  
{{totalSubscript}} 订阅, 6,025 游览

简述

本教程主要介绍如何扩展Spring的xml配置,让Spring能够识别我们自定义的Schema和Annotation。

这里我们要实现的功能如下,首先让Spring能够识别下面的配置。

<std:annotation-endpoint />

这个配置的要实现的功能是,配置完后能够让 Spring 扫描我们自定义的@Endpoint注解。

创建项目

首先需要创建一个Java项目,这里使用Maven创建一个quickstart项目(普通Java项目)。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="https://maven.apache.org/POM/4.0.0"
         xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>spring-schema</groupId>
    <artifactId>com.system.schema</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>1.6</java.version>
        <java.encoding>UTF-8</java.encoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

定义Schema

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="https://www.orchome.com/schema/std/ws"
            xmlns:xsd="https://www.w3.org/2001/XMLSchema"
            xmlns:beans="https://www.springframework.org/schema/beans"
            targetNamespace="https://www.orchome.com/schema/std/ws"
            elementFormDefault="qualified"
            attributeFormDefault="unqualified">

    <xsd:import namespace="https://www.springframework.org/schema/beans"/>

    <xsd:annotation>
        <xsd:documentation><![CDATA[ Namespace support for the annotation provided by cxf framework. ]]></xsd:documentation>
    </xsd:annotation>

    <xsd:element name="annotation-endpoint">
        <xsd:complexType>
            <xsd:complexContent>
                <xsd:extension base="beans:identifiedType">

                    <xsd:attribute name="name" type="xsd:string" use="optional">
                        <xsd:annotation>
                            <xsd:documentation><![CDATA[ Name of bean. Insted of id ]]></xsd:documentation>
                        </xsd:annotation>
                    </xsd:attribute>

                    <xsd:attribute name="package" type="xsd:string" use="optional">
                        <xsd:annotation>
                            <xsd:documentation><![CDATA[ Pakeage to scan. ]]></xsd:documentation>
                        </xsd:annotation>
                    </xsd:attribute>

                </xsd:extension>
            </xsd:complexContent>
        </xsd:complexType>
    </xsd:element>
</xsd:schema>

关于Sechma的知识此处不在介绍,不会用的小伙伴们可以看这篇文章:https://www.orchome.com/758 ,sechma位置在src/main/resources/META-INF/schema/stdws-1.0.xsd

定义注解

package com.system.annotation.custom;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 用于暴露服务,通过在类上加入{@code @Endpoint}注解实现服务暴露的目的。
 * 扩展Spring的Bean扫描功能,在Bean上加入此注解后会自动注册到Spring容器中。
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Endpoint {

    /**
     * 此Endpoint在Spring容器中的ID
     * @return
     */
    String id();

    /**
     * 服务发布的地址,服务器地址及端口号和项目路径
     * @return
     */
    String address();

}

在Spring中的配置

在Spring中加入命名空间,并使用标签,如下。这里要用到Spring的注解扫描功能。文件:src/main/resources/SpringAnnotaionContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://www.springframework.org/schema/beans"
       xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
       xmlns:std="https://www.orchome.com/schema/std/ws"
       xsi:schemaLocation="https://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        https://www.orchome.com/schema/std/ws
        https://www.orchome.com/schema/std/ws/stdws-1.0.xsd">

    <std:annotation-endpoint package="com.system.annotation.service"/>
</beans>

在配置中定义了要扫描的包,不依赖与context的配置。

命名空间支持

要实现命名空间支持,需要继承自NamespaceHandlerSupport

package com.system.annotation;

import com.system.annotation.process.EndpointBeanProcessor;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class AnnotationNamespaceHandler extends NamespaceHandlerSupport {

    public void init() {
        this.registerBeanDefinitionParser("annotation-endpoint", new AnnotationBeanDefinitionParser(EndpointBeanProcessor.class));
    }
}
  • registerBeanDefinitionParser 方法将配置支持添加到Spring中。
  • annotation-endpoint是配置支持的元素。
  • AnnotationBeanDefinitionParser是处理配置的类。
  • EndpointBeanProcessor是处理 @Endpoint 注解的Bean的类,后面会有详细的讲述。

处理配置

需要实现BeanDefinitionParser

package com.system.annotation;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;

public class AnnotationBeanDefinitionParser implements BeanDefinitionParser {

    private final Class<?> beanClass;

    public AnnotationBeanDefinitionParser(Class<?> beanClass) {
        this.beanClass = beanClass;
    }

    public BeanDefinition parse(Element element, ParserContext parserContext) {
        RootBeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClass(beanClass);
        beanDefinition.setLazyInit(false);

        String id = element.getAttribute("id");
        if(id == null || id.length() == 0 ){
            String name = element.getAttribute("name");
            if(!StringUtils.isEmpty(name)) id = name;
            else id = beanClass.getName();
        }

        if (parserContext.getRegistry().containsBeanDefinition(id))  {
            throw new IllegalStateException("Duplicate spring bean id " + id);
        }
        parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);

        String annotationPackage = element.getAttribute("package");
        if(!StringUtils.isEmpty(annotationPackage))
            beanDefinition.getPropertyValues().add("annotationPackage", annotationPackage);

        return beanDefinition;
    }
}

BeanDefinitionParser的应用参见Spring官方文档。

Bean注册工具类

package com.system.annotation.process;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;

public class BeanRegistry implements ApplicationContextAware{

    private ApplicationContext applicationContext;

    private ConfigurableApplicationContext configurableApplicationContext;

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
        if(applicationContext instanceof ConfigurableApplicationContext){
            this.configurableApplicationContext = (ConfigurableApplicationContext)this.applicationContext;
        }
    }

    public BeanRegistry(){

    }

    public BeanRegistry(ApplicationContext applicationContext){
        this.setApplicationContext(applicationContext);
    }

    public BeanDefinition register(Class<?> clazz){
        if(configurableApplicationContext == null)return null;
        BeanDefinitionRegistry beanDefinitonRegistry =
                (BeanDefinitionRegistry)configurableApplicationContext.getBeanFactory();

        BeanDefinitionBuilder beanDefinitionBuilder = this.createBuilder(clazz);
        BeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
        beanDefinitonRegistry.registerBeanDefinition(clazz.getName(),beanDefinition);

        return beanDefinition;
    }

    private BeanDefinitionBuilder createBuilder(Class<?> clazz){
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
        return beanDefinitionBuilder;
    }

}

处理@Endpoint

package com.system.annotation.process;

import com.system.annotation.custom.Endpoint;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.StringUtils;

public class EndpointBeanProcessor implements
        BeanFactoryPostProcessor, DisposableBean, BeanPostProcessor, ApplicationContextAware {

    private final String COMMA_SPLIT_PATTERN = ",";

    private ApplicationContext applicationContext;

    private String annotationPackage;

    private String[] annotationPackages;

    private BeanRegistry beanRegistry;

    public void setAnnotationPackage(String annotationPackage) {
        this.annotationPackage = annotationPackage;
        if (!StringUtils.isEmpty(this.annotationPackage))
            this.annotationPackages = this.annotationPackage.split(this.COMMA_SPLIT_PATTERN);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        System.out.println("setApplicationContext...");

        this.applicationContext = applicationContext;
        this.beanRegistry = new BeanRegistry(this.applicationContext);
    }

    /**
     * 扫描{@link com.system.annotation.custom.Endpoint}注解
     */
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        if (annotationPackage == null || annotationPackage.length() == 0) {
            return;
        }

        if (beanFactory instanceof BeanDefinitionRegistry) {
            BeanDefinitionRegistry beanDefinitionRegistry = (BeanDefinitionRegistry) beanFactory;

            ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(beanDefinitionRegistry, true);
            AnnotationTypeFilter filter = new AnnotationTypeFilter(Endpoint.class);
            scanner.addIncludeFilter(filter);
            scanner.scan(annotationPackages);
        }
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
        return bean;
    }

    /**
     * 实例化之后,打印扫描的类
     *
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException {
        if (!this.isMatchPackage(bean)) return bean;

        Endpoint endpoint = bean.getClass().getAnnotation(Endpoint.class);
        if (endpoint != null) {
            System.out.println(bean.getClass());
        }

        return bean;
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("destroy...");
    }

    /**
     * 包是否匹配
     *
     * @param bean
     * @return
     */
    private boolean isMatchPackage(Object bean) {
        if (annotationPackages == null || annotationPackages.length == 0) {
            return true;
        }
        String beanClassName = bean.getClass().getName();
        for (String pkg : annotationPackages) {
            if (beanClassName.startsWith(pkg)) {
                return true;
            }
        }
        return false;
    }
}

这里已经实现了注解的扫描。然后需要在postProcessAfterInitialization方法中写业务处理代码。AfterInitialization表示Bean已经创建并且注入属性。

可以在spring容器实例化bean之后,在执行bean的初始化方法前后,添加一些自己的处理逻辑。

继承的几个类的大致介绍:
  • BeanFactoryPostProcessor: Spring允许BeanFactoryPostProcessor在容器实例化任何其它bean之前读取配置元数据,并可以根据需要进行修改,例如可以把bean的scope从singleton改为prototype,也可以把property的值给修改掉。可以同时配置多个BeanFactoryPostPro* cessor,并通过设置'order'属性来控制各个BeanFactoryPostProcessor的执行次序。
  • BeanPostProcessor: 可以在spring容器实例化bean之后,在执行bean的初始化方法前后,添加一些自己的处理逻辑。
  • DisposableBean : Spring提供了一些标志接口,用来改变BeanFactory中的Bean的行为,InitializingBean和DisposableBean。实现这些接口将会导致BeanFactory调用前一个接口的afterPropertiesSet()方法,调用后一个接口的destory()方法,从而使得bean可以在初始化和析构后做一些特定的动作。
  • ApplicationContextAware: 当一个类实现了这个接口(ApplicationContextAware)之后,这个类就可以方便获得ApplicationContext中的所有bean。换句话说,就是这个类可以直接获取spring配置文件中,所有有引用到的bean对象。

让Spring识别扩展

首先在classpath的META-INF下创建spring.handlers,内容如下

http\://www.orchome.com/schema/std/ws=com.system.annotation.AnnotationNamespaceHandler

在这个文件中指明了哪个命名空间需要哪个类来处理。
然后再创建spring.schemas

http\://www.orchome.com/schema/std/ws/stdws-1.0.xsd=META-INF/schema/stdws-1.0.xsd

指明了Sechma文件的位置,Spring会使用这里制定的xsd文件来验证配置是否正确。

测试

创建接口

package com.system.annotation.service;

import javax.jws.WebService;

@WebService
public interface HelloService {

    public String syHi(String name);
}

实现类

package com.system.annotation.service;

import com.system.annotation.custom.Endpoint;

import javax.jws.WebService;

@Endpoint(address="HelloService", id = "HelloServiceEndpoint")
@WebService(endpointInterface= "com.system.annotation.service.HelloService")
public class HelloServiceImpl implements HelloService{

    @Override
    public String syHi(String name) {
        return "Hello "+name;
    }

}

测试用例

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:applicationContext2.xml"})
public class InitializationTest {

    @Test
    public void test(){

    }

}

在处理类中有一段代码是将有@Endpoint注解的类都打印出来,所以如果类名被打印出来就表示配置正常了。

运行测试用例,控制台能够看到

class com.codestd.spring.cxf.ws.HelloServiceImpl

本次扩展基本实现。

本此教程的内容可以在Spring官方文档第42章中找到。

项目结果如下:


├── README.md
├── pom.xml
├── spring-schema.iml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── system
    │   │           └── annotation
    │   │               ├── AnnotationBeanDefinitionParser.java
    │   │               ├── AnnotationNamespaceHandler.java
    │   │               ├── custom
    │   │               │   └── Endpoint.java
    │   │               ├── process
    │   │               │   ├── BeanRegistry.java
    │   │               │   └── EndpointBeanProcessor.java
    │   │               └── service
    │   │                   ├── HelloService.java
    │   │                   ├── HelloServiceImpl.java
    │   │                   └── HelloServiceImpl2.java
    │   └── resources
    │       ├── META-INF
    │       │   ├── schema
    │       │   │   └── stdws-1.0.xsd
    │       │   ├── spring.handlers
    │       │   └── spring.schemas
    │       └── SpringAnnotaionContext.xml
    └── test
        └── java
            └── TestAnnotationSchema.java

项目源码git地址,可直接运行:https://github.com/orchome/spring-schema

更新于 2018-01-05
在线,5小时前登录

查看java更多相关的文章或提一个关于java的问题,也可以与我们一起分享文章