前言
会有一系列的文章介绍common-*.jar的各种用法,这些工具类jar包都已上传在maven中央库。可以直接通过maven坐标引入使用。源码可以参见:https://gitee.com/rjzjh/common
使用 场景
common-.jar的所有模块就是基于java开发,跟spring没有关系。在现在,springboot和springcloud非常流行的情况下,如果不对spring进行支持,则会大大限制它的使用场景,也就是说这些公用模块在springboot中使用会觉得非常的不自然。common-spring就是为了方便common-.jar的相关模块在spring和springboot中的方便自然的使用 而开发的。里面会有不少的spring的技巧。下面所有的代码都在下面模块,使用时只需引入maven坐标即可:
<dependency>
<groupId>net.wicp.tams</groupId>
<artifactId>common-spring-autoconfig</artifactId>
<version>最后版本</version>
</dependency>
以下所有的代码都可以在我的开源库中找到:https://gitee.com/rjzjh/common
内存中的Property对象由spring管理
经常性的存在这种场景,我们写好了一些工具类,这些工具类即可以在非spring项目里使用,也可以在spring项目里使用 ,为了做的更灵活些,这些工具类需要各种配置项,一般来说,我们会把它们放到内存的某个变量 中,这样在所有的java项目里就可以直接使用,这在非spring项目中感觉没什么,但在spring项目中,spring有自己的一套属性管理方案,且可以通过 @value等许多的annotation工具直接在项目代码中注入,在使用上更加的便捷和自然。在内存在的配置项是不被spring所管理的,也就是说我们需要一个附加的模块把这部分数据也交由spring来管理,这就产生了一个内存里的Property对象与spring管理的配置项互相同步的问题。
common-*.jar所有的模块配置项都是由内存对象Conf.props 对象管理的,见内存配置中心,那么下面我们来看看这个内存对象如何做到被spring所管理的,只需3步就可搞定。
首先,定义好PropertySource,
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import net.wicp.tams.common.Conf;
@Order(0)
public class TamsPropertySource implements PropertySourceLocator {
@Override
public PropertySource<?> locate(Environment environment) {
CompositePropertySource composite = new CompositePropertySource("tams");
PropertiesPropertySource mapPropertySource = new PropertiesPropertySource("config", Conf.copyProperties());
composite.addPropertySource(mapPropertySource);
return composite;
}
}
引入依赖配置
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<!-- cloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
<optional>true</optional>
</dependency>
定义好springboot的Configuration:
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author andy.zhou
*/
@Configuration
public class TamsConfigBootstrapConfiguration {
@Configuration
@EnableConfigurationProperties
protected static class TamsPropertySourceConfiguration {
@Bean
public TamsPropertySource consulPropertySourceTams() {
return new TamsPropertySource();
}
}
}
在spring.factories文件定义好配置:
org.springframework.cloud.bootstrap.BootstrapConfiguration=
net.wicp.tams.common.spring.property.TamsConfigBootstrapConfiguration
好了,现在内存配置中心所有的配置项都被spring管理了。同理,由spring管理的配置项也需要覆盖内存中的配置项。它发生在setEnvironment阶段。其中最核心的代码就是如何找到spring管理的所有配置项,注意,不是最终有效的配置项(有可能存在配置项冲突,有些配置项被覆盖了)。common-spring-autoconfig模块对覆盖规则也做了处理,具体代码如下:
@Override
public void setEnvironment(Environment environment) {
this.springAssit = new SpringAssit(environment);
Properties inputpamas = new Properties();
Properties tpProps = Conf.copyProperties();
String[] addSingleAry = StringUtil.isNull(Conf.get("common.spring.autoconfig.addSingle")) ? new String[0]
: Conf.get("common.spring.autoconfig.addSingle").split(",");
for (String needAddConf : addSingleAry) {
if (!tpProps.containsKey(needAddConf)) {
tpProps.put(needAddConf, "null");
}
}
Map<String, String> allMap = springAssit.findAllProps();
String[] addPreAry = StringUtil.isNull(Conf.get("common.spring.autoconfig.addPre")) ? new String[0]
: Conf.get("common.spring.autoconfig.addPre").split(",");
for (String keystr : allMap.keySet()) {
boolean needAdd = tpProps.containsKey(keystr);
if (!needAdd) {
for (String addPreEle : addPreAry) {
if (StringUtil.isNotNull(addPreEle) && keystr.startsWith(addPreEle)) {
needAdd = true;
break;
}
}
}
if (needAdd) {
inputpamas.put(keystr, allMap.get(keystr));
}
}
log.info("input parmas:{}", inputpamas.toString());
Conf.overProp(inputpamas);
common-spring-autoconfig模块并不是把spring管理的所有配置项都放到 Conf类管理的内存配置项中,而是它们的并集,然后再用spring管理的配置项对Conf类管理的内存配置项覆盖。但有些情况比较特殊,工具类中没有相关配置项(有可能默认值在程序里写死,并不在配置文件里定义默认值),这样交集就没有这个配置项了。就样就会导致spring的配置项覆盖失败,所以 common-spring-autoconfig模块引入了两个配置解决此问题:
common.spring.autoconfig.addPre =abc.edf :只要spring中的配置项的key是以它定义的值为前缀,那就就给与覆盖,如:spring中有定义配置项:abc.edf.cccc=1 和 abc.edf.ddd=2 都会发生覆盖。
common.spring.autoconfig.addSingle=abc.edf: 只覆盖指定的配置项的key,如:abc.edf=1会被覆盖,abc.edf.ccc=2则不会被覆盖。
自定义命名空间自定义注解
我们在spring项目的spring配置文件里经常看到自定义的命名空间,如下示例:
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:tams="http://tams.wicp.net/schema/tams"
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.xsd
http://tams.wicp.net/schema/tams http://tams.wicp.net/schema/tams/tams.xsd">
<context:annotation-config />
<context:property-placeholder />
<tams:annotation package="net.wicp.tams.demo.springboot1" />
</beans>
其中 xmlns:tams=“http://tams.wicp.net/schema/tams” 就是自定义的命名空间,对于开源组织或是公司内部用基础平台,经常需要开发一个spring的基础组件,自定义命名空间可以方便的组织好这些基础组件。使用时像上面<tams:annotation package=“net.wicp.tams.demo.springboot1” />类似,很有辨识度,也较为方便。我这里不讲原理性的东西,只是说明一下,common-spring.jar包里如何实现自定义的命名空间和注解,为有需求的同学提供最简要的参考。
定义好schemas
在META-INF/spring.schemas文件里定义:
http\://tams.wicp.net/schema/tams/tams.xsd=tams.xsd
在source根目录下定义文件:tams.xsd文件:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://tams.wicp.net/schema/tams"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="http://tams.wicp.net/schema/tams">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:import namespace="http://www.springframework.org/schema/beans"/>
<xsd:import namespace="http://www.springframework.org/schema/tool"/>
<xsd:annotation>
<xsd:documentation>
<![CDATA[ tams的spring名称空间. ]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType name="annotationType">
<xsd:attribute name="id" type="xsd:ID">
<xsd:annotation>
<xsd:documentation><![CDATA[ The unique identifier for a bean. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="package" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[ The scan package. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:element name="annotation" type="annotationType">
<xsd:annotation>
<xsd:documentation><![CDATA[ The annotation config ]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:schema>
定义好处理器handle
在META-INF/spring.handlers文件里定义:
http\://tams.wicp.net/schema/tams=net.wicp.tams.common.spring.schema.AnnotationNamespaceHandler
里面Handler的实现如下:
public class AnnotationNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("annotation", new TamsBeanDefinitionParser(AnnotationBean.class));
}
}
handle实现
指示自定义的命名空间下,annotation元素的处理逻辑,先看下TamsBeanDefinitionParser:
public class TamsBeanDefinitionParser implements BeanDefinitionParser {
private final Class<?> beanClass;
public TamsBeanDefinitionParser(Class<?> beanClass) {
this.beanClass = beanClass;
}
public BeanDefinition parse(Element element, ParserContext parserContext) {
return parse(element, parserContext, beanClass);
}
private BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass) {
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition();
rootBeanDefinition.setBeanClass(beanClass);
rootBeanDefinition.setLazyInit(false);
String annotationPackage = element.getAttribute("package");
rootBeanDefinition.getPropertyValues().addPropertyValue("annotationPackage", annotationPackage);
String generatedBeanName = beanClass.getName();
parserContext.getRegistry().registerBeanDefinition(generatedBeanName, rootBeanDefinition);
return rootBeanDefinition;
}
}
这个较为简单就是把要处理的Class类注册为spring管理的bean,并把xml所传入的参数值与这个bean对应的属性进行绑定。这个bean不是一个普通的javabean,它是实现了好几个spring接口的bean,这些接口都是spring生命周里的不同阶段的各种钩子net.wicp.tams.common.spring.autoconfig.beans.AnnotationBean,其中,重要的也就两个方法:
- postProcessBeanFactory 在BeanFactory创建后执行,会扫描xml里指定的package里的所有class文件 ,把它们符合条件的spring bean创建处理。
- postProcessAfterInitialization 这个方法会在初始化完Bean后调用这个方法,让我们有办法影响这个spring的bean。我们的注解要实现的逻辑就在这里处理较为合理。
里面还有处理注解的逻辑:
// 自定义处理类配置
Map<String, String> pre = Conf.getPre("common.spring.autoconfig.annotation", true);
for (String key : pre.keySet()) {
try {
int indexOf = key.indexOf(".");
String elementtype = key.substring(0, indexOf);
String className = key.substring(indexOf + 1);
ElementType elementType = ElementType.valueOf(elementtype);
Class<? extends Annotation> annotationClass = (Class<? extends Annotation>) Class.forName(className);
switch (elementType) {
case FIELD:
cusFieldMap.put(annotationClass, pre.get(key));
break;
case TYPE:
cusTypeMap.put(annotationClass, pre.get(key));
break;
default:
break;
}
} catch (Exception e) {
log.error("error", e);
}
}
由于我们的注解都分散到各common模块的jar包中,那么就需地做一层封装,支持各模块动态地处理自己所管理到的注解类。
注解现在简单地分为2类(现在只支持2类),拿一个示例模块:common-binlog-alone来说:
#common.spring.autoconfig.annotation|+TYPE|FIELD+.类名=处理类
common.spring.autoconfig.annotation.TYPE.net.wicp.tams.common.binlog.alone.annotation.BinlogListener=net.wicp.tams.common.binlog.alone.annotation.BinlogListenerDo
common.spring.autoconfig.contextInit.binlogalone=net.wicp.tams.common.binlog.alone.annotation.ContextInitDo
配置一指示作用于 Type的注解类:“net.wicp.tams.common.binlog.alone.annotation.BinlogListener”,它的处理器为:“net.wicp.tams.common.binlog.alone.annotation.BinlogListenerDo”,只要是“net.wicp.tams.common.spring.autoconfig.beans.TypeBean”子类都可以做为Type的注解类的处理器。同理,只要是“net.wicp.tams.common.spring.autoconfig.beans.FieldBean”子类都可以做为FIELD注解(只能作用于FIELD的注解)的处理器。
多说一句,上面示例“common.spring.autoconfig.contextInit.binlogalone”表示context准备好时要执行的动作,它的实现类要实现接口“net.wicp.tams.common.spring.autoconfig.IContextInit”
自定义@Enable模块装配的
现在注解盛行,很多的spring项目基本上都消灭了xml定义,使用全注解的方式。Spring Framework 3.1 开始支持”@Enable 模块驱动“。所谓“模块”是指具备相同领域的功能组件集合, 组合所形成一个独立的单元。在使用SpringBoot的时候,我们也会使用到@Enable *** 注解的地方,只要在Main方法上使用了此注解,springboot便会自动组装此模块的相关组件。看一个示例:
@EnableTams(packages="net.wicp.tams.demo.springboot1")
public class DemoTamsSpringboot1Application {
public static void main(String[] args) {
SpringApplication.run(DemoTamsSpringboot1Application.class, args);
}
}
下面就说说common-spring.jar是如何实现@Enable模块装配的.
enableTams注解类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({ AnnotationRegistrar.class, AnnotationImportSeletor.class })
public @interface EnableTams {
// 逗号分隔
String packages();
}
重点就是AnnotationRegistrar这个类:
public class AnnotationRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
MultiValueMap<String, Object> allAnnotationAttributes = importingClassMetadata
.getAllAnnotationAttributes("net.wicp.tams.common.spring.annotation.EnableTams");
String packages = String.valueOf(allAnnotationAttributes.getFirst("packages"));
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition();
rootBeanDefinition.setBeanClass(AnnotationBean.class);
rootBeanDefinition.setLazyInit(false);
rootBeanDefinition.getPropertyValues().addPropertyValue("annotationPackage", packages);
String generatedBeanName = AnnotationBean.class.getName();
registry.registerBeanDefinition(generatedBeanName, rootBeanDefinition);
}
}
这个类就跟前一章节“自定义命名空间”介绍的handler实现类:TamsBeanDefinitionParser 有异曲同工的效果,都是把AnnotationBean.class注册为spring管理的bean,接下去就和“自定义命名空间”那节一样了。交给了同一个类:AnnotationBean.class。