公用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>
<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;
@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.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
多说一句,上面示例“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。