spring可以说是目前全球使用最为广泛的java框架之一,spring提供的ioc,aop等功能大大的提高了开发的效率。spring在提高效率的同时,提供很多了扩展槽。自定义标签就是扩展槽之一,其他框架可以无缝的嵌入到spring中,方便开发者使用,比如阿里巴巴之前开源的rpc框架dubbo就实现了自己的标签<dubbo:application>、<dubbo:registry>、 <dubbo:service>
等。下面我们会介绍如何在spring中使用自定义标签。首先我们先介绍下我们要实现的标签的功能:
用户使用标签,在spring容器启动结束后,打印出jvm和系统的一些参数。
定义标签名称
完事开头难,起名字尤其难,为了方便开发者使用,一个好的名字既能明确表达框架作者的意图又能方便记忆。既然是打印虚拟机相关的参数,所以我们将标签定义为<vm>
,子标签定义为info
,这样完整的标签就是<vm:info>
。之所以我们需要定义子标签,是为了后面扩容其他功能。
定义Namespace
如果你之前阅读过一些spring的源码,应该知道每一个标签对应着一个Namespace,spring会根据Namespace查找Handler进行处理。所以接下来我们需要定一个Namespace,根据spring的命名规则,我们将其定义为http://www.springframework.org/schema/vm
。
定义Handler
有了Namespace后,就需要定义Handler了。
public class VMHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("info", new JavaInfoBeanDefinitionParser());
}
}
参照<context>
,<vm>
标签对应的Handler也继承自NamespaceHandlerSupport
,NamespaceHandlerSupport
的功能不做过多的介绍了。VMHandler的初始化函数主要负责注册子标签的Parser,在这里就是info
。
定义Parser
Spring调用Handler的时候,会根据子标签的名字调用到具体的Parser,上面我们已经定义了info
对应的Parser,下面看一下其具体实现。
public class JavaInfoBeanDefinitionParser implements BeanDefinitionParser {
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
BeanDefinitionBuilder bdb = BeanDefinitionBuilder.genericBeanDefinition(JavaInfoTool.class);
bdb.setDependencyCheck(DEPENDENCY_CHECK_SIMPLE);
bdb.setScope(SCOPE_SINGLETON);
BeanDefinition definition = bdb.getRawBeanDefinition();
BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, "vm-java-info", null);
registerBeanDefinition(holder, parserContext.getRegistry());
return definition;
}
}
Parser对应的功能比较简单,注册BeanDefinition,这个BeanDefinition对应的类是JavaInfoTool,也就是我们实现打印vm信息的类,同时给这个BeanDefinition定义一个id,id是vm-java-info
。
定义JavaInfoTool
为了完成在Spring容器启动后打印vm信息的功能,JavaInfoTool需要保证在最后执行,最直接的方法是继承ApplicationListener,在Spring容器状态发生变化的时候调用。
public class JavaInfoTool implements ApplicationListener<ContextRefreshedEvent> {
public void printJavaInfo() {
Properties properties = System.getProperties();
for (Map.Entry entry : properties.entrySet()) {
System.out.printf("%20s -> %s\r\n", entry.getKey(), entry.getValue());
}
}
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if(event.getApplicationContext().getParent() == null) {
printJavaInfo();
}
}
}
实现打印vm信息的代码逻辑比较简单,这里就不做过多介绍了。
添加配置文件
到这里代码层面的工作都差不多完成了,但是还是不能直接使用。我们需要定义三个文件,
- spring.handlers
- spring.schemas
- spring-vm-xx.xsd
spring.handlers
这个文件用于指定Namespace对应的Handler,Spring在启动过程中会扫描这个文件,加载对应关系。
http\://www.springframework.org/schema/vm=com.lpmoon.spring.VMInfoHandler
spring.schemas
这个文件用于定义对应Schema文件所在位置,
http\://www.springframework.org/schema/vm/spring-vm-2.5.xsd=com/lpmoon/spring/spring-vm-2.5.xsd
http\://www.springframework.org/schema/vm/spring-vm-3.0.xsd=com/lpmoon/spring/spring-vm-3.0.xsd
http\://www.springframework.org/schema/vm/spring-vm-3.1.xsd=com/lpmoon/spring/spring-vm-3.1.xsd
http\://www.springframework.org/schema/vm/spring-vm-3.2.xsd=com/lpmoon/spring/spring-vm-3.2.xsd
http\://www.springframework.org/schema/vm/spring-vm-4.0.xsd=com/lpmoon/spring/spring-vm-4.0.xsd
http\://www.springframework.org/schema/vm/spring-vm-4.1.xsd=com/lpmoon/spring/spring-vm-4.1.xsd
http\://www.springframework.org/schema/vm/spring-vm-4.2.xsd=com/lpmoon/spring/spring-vm-4.2.xsd
http\://www.springframework.org/schema/vm/spring-vm-4.3.xsd=com/lpmoon/spring/spring-vm-4.3.xsd
http\://www.springframework.org/schema/vm/spring-vm.xsd=com/lpmoon/spring/spring-vm-4.3.xsd
spring-vm-xx.xsd
因为我们需要支持从2-x到4-x多个版本的Spring,所以我们需要根据spring.schemas重定义的schema创建多个版本的xsd文件,文件放置在com/lpmoon/spring/
目录下。
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.springframework.org/schema/vm"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:tool="http://www.springframework.org/schema/tool"
targetNamespace="http://www.springframework.org/schema/vm"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines the vm elements for the Spring Framework's application
context support.
]]></xsd:documentation>
</xsd:annotation>
<xsd:element name="info">
<xsd:annotation>
<xsd:documentation><![CDATA[
java info.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation>
<tool:exports type="com.lpmoon.spring.JavaInfoTool"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
</xsd:schema>
上面的文件定义了vm标签,以及子标签info的含义。
打包
到这里所有的文件都已经定义好了,我们来看一看具体的文件结构,
|____pom.xml
|____src
| |____main
| | |____java
| | | |____com
| | | | |____lpmoon
| | | | | |____spring
| | | | | | |____JavaInfoBeanDefinitionParser.java
| | | | | | |____JavaInfoTool.java
| | | | | | |____spring-vm-2.5.xsd
| | | | | | |____spring-vm-3.0.xsd
| | | | | | |____spring-vm-3.1.xsd
| | | | | | |____spring-vm-3.2.xsd
| | | | | | |____spring-vm-4.0.xsd
| | | | | | |____spring-vm-4.1.xsd
| | | | | | |____spring-vm-4.2.xsd
| | | | | | |____spring-vm-4.3.xsd
| | | | | | |____VMHandler.java
| | |____resources
| | | |____META-INF
| | | | |____spring.handlers
| | | | |____spring.schemas
为了方便他人使用我们需要将我们定义的文件打包成jar文件,配置pom文件如下,
<build>
<plugins>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>2.5</version>
<executions>
<execution>
<id>copy-xmls</id>
<phase>process-sources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/target/classes</outputDirectory>
<resources>
<resource>
<directory>${basedir}/src/main/java</directory>
<includes>
<include>**/*.xsd</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
上面的配置的主要作用是将xsd文件打包到对应的目录下,否则使用该jar启动的时候会报错。执行
mvn clean install
后对应的jar包里面的内容为
|____com
| |____lpmoon
| | |____spring
| | | |____JavaInfoBeanDefinitionParser.class
| | | |____JavaInfoTool.class
| | | |____spring-vm-2.5.xsd
| | | |____spring-vm-3.0.xsd
| | | |____spring-vm-3.1.xsd
| | | |____spring-vm-3.2.xsd
| | | |____spring-vm-4.0.xsd
| | | |____spring-vm-4.1.xsd
| | | |____spring-vm-4.2.xsd
| | | |____spring-vm-4.3.xsd
| | | |____VMInfoHandler.class
|____META-INF
| |____spring.handlers
| |____spring.schemas
使用
建立项目,在对应的配置文件中添加如下配置,
<vm:info/>
然后启动代码,可以看到如下输出,
java.runtime.name -> Java(TM) SE Runtime Environment
sun.boot.library.path -> /Library/Java/JavaVirtualMachines/jdk1.8.0_111.jdk/Contents/Home/jre/lib
......