公用spring模块

公用spring模块

前言

会有一系列的文章介绍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坐标即可:

1
2
3
4
5
<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,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;
}
}

引入依赖配置

1
2
3
4
5
6
7
8
9
10
		<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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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模块对覆盖规则也做了处理,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@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配置文件里经常看到自定义的命名空间,如下示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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文件里定义:

1
http\://tams.wicp.net/schema/tams/tams.xsd=tams.xsd

在source根目录下定义文件:tams.xsd文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?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文件里定义:

1
http\://tams.wicp.net/schema/tams=net.wicp.tams.common.spring.schema.AnnotationNamespaceHandler

里面Handler的实现如下:

1
2
3
4
5
public class AnnotationNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("annotation", new TamsBeanDefinitionParser(AnnotationBean.class));
}
}

handle实现

指示自定义的命名空间下,annotation元素的处理逻辑,先看下TamsBeanDefinitionParser:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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。我们的注解要实现的逻辑就在这里处理较为合理。

里面还有处理注解的逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 自定义处理类配置
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来说:

1
2
3
#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便会自动组装此模块的相关组件。看一个示例:

1
2
3
4
5
6
@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注解类

1
2
3
4
5
6
7
8
9
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({ AnnotationRegistrar.class, AnnotationImportSeletor.class })
public @interface EnableTams {
// 逗号分隔
String packages();
}

重点就是AnnotationRegistrar这个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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。

坚持原创技术分享,您的支持是我前进的动力!