公司规模如果比较大的话,那么肯定会遇到跨部门数据交互的场景,一般数据交互无非几种方式(http、mq、oss),如果各个部门的数据模型结构不长改变还好,如果经常变化的话那么简直是灾难性打击,譬如上游系统改变了值得类型或者改变了字段的名字又或者改变了字段值得含义,那么下游系统必须跟着改变,不然下游系统就会出现异常而下游系统的改变往往都是改代码+线上重启,这种方式的风险其实非常大,为了应对这种情况最近自己写了一个模型映射框架。
这个框架通过配置json文件即可完成数据映射,整个过程无需修改代码也无需重启应用,由于开发时间比较紧,所以很多地方没有做的太好,数据结构内嵌太深的话还是会有点问题,不过至少满足了我们的业务需求,下面放出代码和说明,有兴趣的小伙伴可以在这个基础上将代码改造的更加完美,目前测试下来复杂结构的映射运行速度9万次/秒
下面是关键代码解析说明:
springboot配置类:
/**
* <p>Title:mapping配置</p>
* <p>Description:</p>
*
* @author QIQI
* @date
*/
@Data
@Component
@ConfigurationProperties(FinalArgs.ORM_MAPPING_CONFIG)
//public static final String ORM_MAPPING_CONFIG = "com.wms.mapping";
public class OrmMappingProperties {
//是否启用orm-mapping
private String enable = "false";
//是否开启默认字段映射,如果层级+字段名一样,那么不需要配置JSON文件的关系了
private String defaultMapping = "true";
}
下面这部分代码是用来加载JSON配置文件以及创建cglib对象 :
/**
* <p>Title:定义初始化的基础接口</p>
* <p>Description:</p>
*
* @author QIQI
* @date
*/
@FunctionalInterface
public interface Init {
Logger log = LoggerFactory.getLogger( Init.class );
//装载orm-mapping配置map
Map<String, Object> ORM_MAPPINT = new HashMap<>();
//装载链路对应adapter的Map
//示例:{clazz={baseAdapter={name=xname, age=xage, fun=xfun}, nike={name=xname, age=xage, fun=xfun}}}
Map<String, Map<String, Map<String, String>>> LINK_PROPERTY_MAP = new ConcurrentHashMap<>();
default void init(Object adapterBean, Binder binder, String filePathName) throws IOException {
//只读取一次yml文件的配置
if (ORM_MAPPINT.isEmpty()) {
BindResult bindResult = binder.bind( FinalArgs.ORM_MAPPING_CONFIG, Map.class );
if (bindResult.isBound()) {
ORM_MAPPINT.putAll( binder.bind( FinalArgs.ORM_MAPPING_CONFIG, Map.class ).get() );
log.debug( "BeanAdapterImpl init json is {}", ORM_MAPPINT );
} else {
throw new MappingException( new ExceptionMessageImpl( ErrorMessage.START_ERROR.getCode() ) );
}
}
//开始预创建CGLIB对象的MapProperties,全局加载一次
JSONObject jsonObject = JSON.parseObject( FileUtils.getSpringJarFile( filePathName + adapterBean.getClass().getSimpleName() + ".json" ) );
if(null == jsonObject) throw new MappingException( new ExceptionMessageImpl( ErrorMessage.FILE_NOT_FOUND.getCode() ) );
Map<String, Map<String, String>> linkHash2 = new HashMap<>();
//如果多个层级,包含base和定制
jsonObject.forEach( (k1, v1) -> {
List<ConfigAdapterModel> filedList = JSON.parseArray( v1.toString(), ConfigAdapterModel.class );
Map<String, String> linkHash1 = new HashMap<>();
//只装载每个链路的基础base,解决一个SourceFiled对应多个字段,防止MAP-KEY 冲突
filedList.forEach( f -> linkHash1.put( CodeUtils.snowFlake.nextId() + " | " + f.getSourceField(), f.getTargetModelField() ) );
linkHash2.put( k1, linkHash1 );
} );
LINK_PROPERTY_MAP.put( adapterBean.getClass().getName(), linkHash2 );
log.debug( "recursionSetValue 是否有定制需求判断: {}", LINK_PROPERTY_MAP );
log.debug( "CreateOrderAdapterImpl.init 初始化启动成功,BEAN_PROPERTY_MAP is {}", LINK_PROPERTY_MAP );
}
BeanMap beanMapCopy(BeanMap beanBaseMap, BeanMap beanAdapterMap, String k, String v, String baseClassName, String... key);
}
下面这部分代码是核心处理逻辑代码:
/**
* <p>Title:ORM-MAPPING 核心实现类</p>
* <p>Description:</p>
*
* @author QIQI
* @date
*/
@Service
public class BeanAdapterImpl implements EnvironmentAware, Init {
protected static final Logger log = LoggerFactory.getLogger( BeanAdapterImpl.class );
private Binder binder;
private Environment evn; //获取配置文件的配置信息
public static final Map<String, Map<String, String>> BEAN_CLASS_TYPE = new HashMap<>();
/**
* <p>Title:适配器统一转换入口方法</p>
* <p>Description:</p>
*
* @return T
* @throws
* @author QIQI
* @params [adapterBean - 原始model信息, baseObject - 目标model信息, customized - JSON文件定制化的key]
* @date 2019-11-15 10:34
*/
public <T> T conversionBean(Object adapterBean, Object baseObject, String customized, String filePathName) {
try{
//只会初始化一次,cglib动态创建adapter-bean会被缓存下来,如果更改adapter-bean需要应用重启才会生效
if (null == LINK_PROPERTY_MAP.get( adapterBean.getClass().getName() ))
init( adapterBean, binder, filePathName );
//如果开启ORM-MAPPING
if (!ORM_MAPPINT.isEmpty() && "true".equals( ORM_MAPPINT.get( FinalArgs.ORM_MAPPING_ENABLE ) )) {
//缓存下来,只会动态CGLIB一次,嵌套的层级也只会加载一次
Map<String, String> _map = BEAN_CLASS_TYPE.get( baseObject.getClass().getName() );
if (null == _map || _map.isEmpty()) {
//加载子类信息
_map = ReflexBean.getClassFieldTypeAndSon( baseObject.getClass() );
//加载父类信息
_map.putAll( ReflexBean.getSuperClassFieldTypeAndSon( baseObject.getClass() ) );
BEAN_CLASS_TYPE.put( baseObject.getClass().getName(), _map );
}
//拿到链路的动态BEAN
BeanMap beanBaseMap = BeanMap.create( baseObject );
BeanMap beanAdapterMap = BeanMap.create( adapterBean );
//如果YML中开启了默认映射配置,那么进行默认字段映射,如果beanAdapterMap字段属性==beanBaseMap 那么默认copy
if ("true".equals( ORM_MAPPINT.get( FinalArgs.ORM_MAPPING_DEFAULT_MAPPING ) )) {
BeanCopier beanCopier = BeanCopier.create( adapterBean.getClass(), baseObject.getClass(), false );
beanCopier.copy( adapterBean, baseObject, null );
beanBaseMap = BeanMap.create( baseObject );
}
return (T) recursionSetValue( beanBaseMap, customized, beanAdapterMap, baseObject.getClass().getName(), adapterBean.getClass().getName() );
} else {
throw new MappingException( new ExceptionMessageImpl( ErrorMessage.START_ERROR.getCode() ) );
}
}catch(Exception e){
log.warn( "conversionBean error",e );
throw new MappingException( new ExceptionMessageImpl( ExceptionEm.UNSPECIFIED.getCode() ) );
}
}
/**
* <p>Title:递归初始化BeanMap,并且赋值</p>
* <p>Description:</p>
*
* @return T
* @throws
* @author QIQI
* @params [beanBaseMap - 目标model的BeanMap, customized - json配置的特殊映射key, beanAdapterMap - 原始model的BeanMap, clazzSimpleName, adapterSimpleName]
* @date 2019-11-15 10:35
*/
private <T> T recursionSetValue(final BeanMap beanBaseMap, String customized, final BeanMap beanAdapterMap, String clazzSimpleName, String adapterSimpleName) {
//解析JSON-BASE数据
LINK_PROPERTY_MAP.get( adapterSimpleName ).get( FinalArgs.ORM_MAPPING_BASE_ADAPTER ).forEach( (k, v) ->
beanMapCopy( beanBaseMap, beanAdapterMap, k.split( " \\| " )[1], v, beanBaseMap.getBean().getClass().getName() ) );
//判断是否有定制映射需求
if (null != LINK_PROPERTY_MAP.get( adapterSimpleName ).get( customized ) &&
!LINK_PROPERTY_MAP.get( adapterSimpleName ).get( customized ).isEmpty()) {
LINK_PROPERTY_MAP.get( adapterSimpleName ).get( customized ).forEach( (k, v) -> beanMapCopy( beanBaseMap, beanAdapterMap, k.split( " \\| " )[1], v, beanBaseMap.getBean().getClass().getName() ) );
}
return (T) beanBaseMap.getBean();
}
/**
* <p>Title:进行bean-copy核心过程</p>
* <p>Description:基础的2个beanMap已经全部准备好
* 此方法是递归调用,递归逻辑如下:
* 1.v根据"."分割出来的数据长度决定了递归次数
* 2.每次递归传入的beanBaseMap都是原始beanBaseMap的下一层BeanMap
* 3.每次递归传入的v都是去除在上一层根据"."分割掉下标1的数据
* </p>
*
* @return net.sf.cglib.beans.BeanMap
* @throws
* @author QIQI
* @params [beanBaseMap, beanAdapterMap, k, v, baseClassName, baseBeanFieldType]
* @date 2019-11-15 22:37
*/
@Override
public BeanMap beanMapCopy(final BeanMap beanBaseMap, final BeanMap beanAdapterMap, String k, String v, String baseClassName, String... key) {
//如果有内嵌结构:xx.xx
String[] codeValue = v.split( FinalArgs.ORM_MAPPING_SPLIT );
String str = "";
if (null != key && key.length > 0) str = key[0];
for (int index = 0; index < codeValue.length; index++) {
if (!StringUtils.isBlank( str ) && !str.endsWith( FinalArgs.ORM_MAPPING_SPLIT2 )) {
str += FinalArgs.ORM_MAPPING_SPLIT2;
}
if (index < (codeValue.length - 1)) {
try {
//如果已经实例化过了的类,那么不在实例化,防止赋值的值被覆盖
if (null != beanBaseMap.get( codeValue[index] )) {
beanBaseMap.put( codeValue[index], beanMapCopy( BeanMap.create( beanBaseMap.get( codeValue[index] ) ),
beanAdapterMap, k, v.substring( v.indexOf( FinalArgs.ORM_MAPPING_SPLIT2 ) + 1 ), baseClassName, str + codeValue[index] ).getBean() );
}
//没有实例化过的类会初始化一次
else {
beanBaseMap.put( codeValue[index], beanMapCopy( BeanMap.create( Class.forName( BEAN_CLASS_TYPE.get( baseClassName ).get( str + codeValue[index] ) ).newInstance() ),
beanAdapterMap, k, v.substring( v.indexOf( FinalArgs.ORM_MAPPING_SPLIT2 ) + 1 ), baseClassName, str + codeValue[index] ).getBean() );
}
return beanBaseMap;
} catch (Exception e) {
log.warn( "Class.forName beanMapCopy is error ", e );
}
} else {
//拿到原始model内嵌值
log.debug( "beanMapCopy------>field-------->{}", v );
Object oo = Conversion.getConversion( getAdapterBeanMapVal( beanAdapterMap, new ArrayList<>( Arrays.asList( k.split( FinalArgs.ORM_MAPPING_SPLIT ) ) ) ),
BEAN_CLASS_TYPE.get( baseClassName ).get( str + codeValue[index] ) );
beanBaseMap.put( v, oo );
}
}
return beanBaseMap;
}
/**
* <p>Title:递归获取adapterMap的值</p>
* <p>Description:</p>
*
* @return java.lang.String
* @throws
* @author QIQI
* @params [beanMap, k]
* @date 2019-11-13 22:32
*/
private String getAdapterBeanMapVal(BeanMap beanMap, List<?> key) {
if (key.size() > 1) { //如果多层嵌套配置
beanMap = BeanMap.create( beanMap.get( key.get( 0 ) ) );
key.remove( 0 );
return getAdapterBeanMapVal( beanMap, key );
}
if ((beanMap.get( key.get( 0 ) ) instanceof List) || (beanMap.get( key.get( 0 ) ) instanceof Map)) {
return JSON.toJSONString( beanMap.get( key.get( 0 ) ) );
}
return String.valueOf( beanMap.get( key.get( 0 ) ) );
}
@Override
public void setEnvironment(Environment environment) {
this.evn = environment;
// 绑定配置器
binder = Binder.get( evn );
}
}
下面是类型映射工具类:
/**
* <p>Title:转换器工具接口</p>
* <p>Description:所有转换器实现此接口</p>
*
* @author QIQI
* @date
*/
public interface ConversionType {
/**
* <p>Title:类型转换接口</p>
* <p>Description:</p>
* @author QIQI
* @params [str - 原始str, type - 原始数据类型]
* @return java.lang.Object
* @throws
* @date 2019-11-16 12:24
*/
<T> T stringToObject(String str,String type) throws Exception;
}
public class StringToBean implements ConversionType{
@Override
public Object stringToObject(String str, String type) throws ClassNotFoundException {
return JSON.parseObject( str, Class.forName( type ) );
}
}
public class StringToBigDecimal implements ConversionType{
@Override
public Object stringToObject(String str, String type) {
return new BigDecimal(str);
}
}
public class StringToBoolean implements ConversionType{
@Override
public Object stringToObject(String str, String type) {
return Boolean.valueOf( str ).booleanValue();
}
}
public class StringToDate implements ConversionType{
@Override
public Object stringToObject(String str, String type) throws Exception {
return DateUtils.parseDate( str,"yyyy-MM-dd" );
}
}
public class StringToDateTime implements ConversionType {
@Override
public Object stringToObject(String str, String type) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat( "EEE MMM dd HH:mm:ss z yyyy", java.util.Locale.US );
return sdf.parse( str );
}
}
public class StringToDouble implements ConversionType{
@Override
public Object stringToObject(String str, String type) {
return Double.valueOf( str ).doubleValue();
}
}
public class StringToInteger implements ConversionType{
@Override
public Object stringToObject(String str, String type) {
return Integer.valueOf( Math.round( Float.valueOf( str ).floatValue() ) ).intValue();
}
}
public class StringToList implements ConversionType{
@Override
public Object stringToObject(String str, String type) throws Exception {
Matcher mat = Pattern.compile("<(.*?)>").matcher(type);
while(mat.find()){
type = mat.group(1);
}
return JSON.parseArray( str, Class.forName( type ));
}
}
public class StringToLong implements ConversionType{
@Override public Object stringToObject(String str, String type) {
return Long.parseLong( str );
}
}
public class StringToMap implements ConversionType{
@Override
public Object stringToObject(String str, String type) {
JSONObject jsonObject = JSONObject.parseObject( str );
return jsonObject.getInnerMap();
}
}
public class StringToSet implements ConversionType{
@Override public Object stringToObject(String str, String type) {
return new HashSet<>( Arrays.asList( str ) );
}
}
public class StringToShort implements ConversionType{
@Override public Object stringToObject(String str, String type){
return new Short( str );
}
}
public class Conversion {
protected static final Logger log = LoggerFactory.getLogger( Conversion.class );
public static final Map<String,ConversionType> CONVERSION_TYPE_MAP = new HashMap<>();
static {
CONVERSION_TYPE_MAP.put( "java.math.BigDecimal",new StringToBigDecimal() );
CONVERSION_TYPE_MAP.put( "java.util.Date",new StringToDateTime() );
CONVERSION_TYPE_MAP.put( "java.lang.Double",new StringToDouble() );
CONVERSION_TYPE_MAP.put( "java.lang.double",new StringToDouble() );
CONVERSION_TYPE_MAP.put( "java.lang.Boolean",new StringToBoolean() );
CONVERSION_TYPE_MAP.put( "java.lang.boolean",new StringToBoolean() );
CONVERSION_TYPE_MAP.put( "java.lang.Integer",new StringToInteger() );
CONVERSION_TYPE_MAP.put( "java.lang.int",new StringToInteger() );
CONVERSION_TYPE_MAP.put( "java.lang.Long",new StringToLong() );
CONVERSION_TYPE_MAP.put( "java.lang.long",new StringToLong() );
CONVERSION_TYPE_MAP.put( "java.long.Short",new StringToShort() );
CONVERSION_TYPE_MAP.put( "java.long.short",new StringToShort() );
CONVERSION_TYPE_MAP.put( "java.util.List",new StringToList() );
CONVERSION_TYPE_MAP.put( "java.util.ArrayList",new StringToList() );
CONVERSION_TYPE_MAP.put( "java.util.LinkedList",new StringToList() );
CONVERSION_TYPE_MAP.put( "java.util.Map",new StringToMap() );
CONVERSION_TYPE_MAP.put( "java.util.HashMap",new StringToMap() );
CONVERSION_TYPE_MAP.put( "java.util.concurrent.ConcurrentHashMap",new StringToMap() );
CONVERSION_TYPE_MAP.put( "java.util.LinkedHashMap",new StringToMap() );
CONVERSION_TYPE_MAP.put( "java.util.Set",new StringToSet() );
CONVERSION_TYPE_MAP.put( "java.util.HashSet",new StringToSet() );
CONVERSION_TYPE_MAP.put( "java.util.LinkedHashSet",new StringToSet() );
}
/**
* <p>Title:类型转换判断工具类</p>
* <p>Description:</p>
*
* @return java.lang.Object
* @throws
* @author QIQI
* @params [str, type, clazz]
* @date 2019-11-13 01:30
*/
public static Object getConversion(String str, String type) {
try {
if("null".equals( str ) || StringUtils.isBlank( str )){
return null;
}
if(type.contains( "String" )){
return str;
}
ConversionType conversionType = CONVERSION_TYPE_MAP.get( type );
if(null == conversionType){
if(type.contains( "List" )) conversionType = new StringToList();
else if(type.contains( "Map" )) conversionType = new StringToMap();
else if(type.contains( "Set" )) conversionType = new StringToSet();
else throw new MappingException( new ExceptionMessageImpl( ErrorMessage.CONVERSION_ERROR.getCode() ) );
}
Object o = conversionType.stringToObject( str,type );
return o;
} catch (Exception e) {
log.warn( "Conversion.getConversion sourceStr is {} is error ",str,e );
throw new MappingException( new ExceptionMessageImpl( ErrorMessage.ERROR.getCode() ) );
}
}
}
Model映射类:
/**
* <p>Title:对应config-json配置文件的映射model</p>
* <p>Description:</p>
*
* @author QIQI
* @date
*/
@Data
public class ConfigAdapterModel<T> implements Serializable {
private static final long serialVersionUID = 3839138213762089450L;
private String targetModelField;
private String sourceField;
private String remark;
}
/**
* <p>Title:</p>
* <p>Description:</p>
*
* @author QIQI
* @date
*/
public enum ErrorMessage {
ERROR("1001", "类型映射错误,请确认目标model的字段类型是否在WIKI文档中被认可,请查询WIKI:http://wiki.baozun.com/pages/viewpage.action?pageId=36857312"),
START_ERROR("1002", "未启用ORM-MAPPING功能,请在YML中启用:com.wms.mapping.enable = true"),
FILE_NOT_FOUND("1003","需要解析的JSON文件未找到,请按照不同链路特定Model的名字命名JSON文件"),
CONVERSION_ERROR("1004","未找到相关的Conversion字段适配类,请确认是否按照WIKI标准定制Conversion,WIKI地址:http://wiki.baozun.com/pages/viewpage.action?pageId=36857312");
ErrorMessage(String code, String message) {
this.code = code;
this.message = message;
}
/** 错误码 */
private final String code;
/** 描述 */
private final String message;
public String getCode() {
return code;
}
public String getMessage() {
return message;
}
}
异常处理类:
/**
* <p>Title:自定义异常信息</p>
* <p>Description:</p>
*
* @author QIQI
* @date
*/
public class ExceptionMessageImpl implements ExceptionMessage {
private String code;
public static final Logger logger = LoggerFactory.getLogger( ExceptionMessageImpl.class );
public ExceptionMessageImpl(String code){
this.code = code;
}
@Override
public String code() {
return code;
}
@Override
public String message() {
for(ErrorMessage val : ErrorMessage.values()){
if(code().equals( val.getCode() )){
logger.error( "ExceptionMessageImpl.message error is {}", val.getMessage());
return val.getMessage();
}
}
return ExceptionEm.UNSPECIFIED.getMessage();
}
}
public class MappingException extends BaseException {
public MappingException(ExceptionMessage co) {
super( co );
}
}