让你的代码借助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坐标即可:

<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。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

偏锋书生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值