前因

公司规范确定不允许使用反射类的进行属性bean的拷贝了,只允许手动的get/set,可以猜到这样定义的原因是制定规范的同事认为反射性能低,虽然写get/set慢点好在性能高。平时开发的时候也是迫不得已才用反射。不过禁用的话就感觉有点钻牛角尖了。

所谓反射性能低是指在使用JDK自带反射工具类反射与非反射性能相比会差7倍(个人测试,未必科学),听起来的确很悬殊的,不过从另一个角度来看貌似反射也没那么恐怖,比如一个非反射对象的转换需要10ns,使用反射就是70ns,这对系统性能提高貌似没有太显著的效果,而且用反射基本都会把能缓存的都存下来,性能也差不哪去。如果一个接口的性能很低一定不是对象拷贝上搞的鬼,基本都和代码逻辑实现、IO、RPC等这些因素相关,这时要做的是优化代码流程、异步线程等方式,优化对象拷贝实在标、本都不治。算上研发人员写get/set的时间与痛苦,感觉得不偿失。对大对象类型的拷贝,的确会耗些性能比如到1ms,目前为止我还没见过超过5ms的对象拷贝,很明显这不是瓶颈,我见过最高要求的接口超时要求是5ms,试想这样的高标准,服务器是不是也很牛逼了,CPU是不是就更那啥了。。。

峰回路转,规范还是要照样遵守:}。不过本人的确很懒,要写那么多的机械代码感觉很low。于是就想能否通过什么办法避过反射完成属性的拷贝。

解决方式

首先,我希望类型转换能像这样优雅的调用:

ConvertUtil.convert(srcBean, TargetBean.class);

于是,我想到两种方式:

第一种 对象克隆,需要科普的可以找找我写的原型设计模式。这个实现起来最简单,但是容错能力很明显不好。因此不考虑。

第二种 动态生成转换方法。根据source object和target class反射生成字符串,动态编译到一个class文件,实例化并放入内存中。预加载一次后再也不用动了。我觉得这个挺好,一劳永逸,“巧妙”的绕过了规范。于是就有了下面的一坨代码:

转换逻辑:

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager.Location;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider; public final class ConvertUtil { private ConvertUtil(){} public final static String PREFIX = "X$";
private final static String PKG = "com.array7.util.dynamic_proxy";
private final static Map<String, IConvert<?, ?>> CLASS_MAPPING = new ConcurrentHashMap<String, IConvert<?, ?>>(); public static <S, T> StringBuffer genConvertUtilStr(Class<S> srcClazz, Class<T> targetClazz, String pkg) {
StringBuffer sb = new StringBuffer();
sb.append("package ").append(pkg).append(";\n\n");
sb.append("public class ").append(getJavaFileName(srcClazz, targetClazz)).append(" implements ")
.append(IConvert.class.getName()).append("<").append(srcClazz.getName()).append(",")
.append(targetClazz.getName()).append(">").append(" {\n");
sb.append("\t@Override\n");
sb.append("\tpublic ").append(targetClazz.getName()).append(" convert(").append(srcClazz.getName())
.append(" s) throws IllegalAccessException, InstantiationException {\n");
sb.append(targetClazz.getName()).append(" t = new ").append(targetClazz.getName()).append("(); \n"); genSetFiledsStr(srcClazz, targetClazz, sb); sb.append("\t\treturn t;\n");
sb.append("\t}\n");
sb.append("}\n");
return sb;
} /**
* create convert method string
* @param srcClazz .
* @param targetClazz .
* @param sb .
*/
private static <S, T> void genSetFiledsStr(Class<S> srcClazz, Class<T> targetClazz, final StringBuffer sb) {
// class field map
Map<String, Class<?>> filedMap = getFieldMap(srcClazz, targetClazz); for (String name : filedMap.keySet()) {
sb.append("\t\tt.").append(getSetMethodName(name)).append("(s.").append(getGetMethodName(name)).append("()); \n");
}
} /**
*
* @param srcClazz .
* @param targetClazz .
* @return
*/
private static Map<String, Class<?>> getFieldMap (Class<?> srcClazz, Class<?> targetClazz) {
return getFieldMap(srcClazz, targetClazz, null);
} /**
* getFieldMap
* @param srcClazz
* @param targetClazz
* @param map
* @return
*/
private static Map<String, Class<?>> getFieldMap (Class<?> srcClazz, Class<?> targetClazz, Map<String, Class<?>> map) {
if (map == null) {
map = new HashMap<String, Class<?>>();
}
Field[] fields = srcClazz.getDeclaredFields();
for (Field field : fields) {
String name = field.getName();
try {
srcClazz.getMethod(getSetMethodName(name), field.getType());
targetClazz.getMethod(getGetMethodName(name)); map.put(field.getName(), field.getType());
} catch (NoSuchMethodException e) {
System.err.println(getSetMethodName(name));
System.err.println(getGetMethodName(name));
continue;
} }
Class<?> srcSuperClazz = srcClazz.getSuperclass();
Class<?> targetSuperClazz = targetClazz.getSuperclass();
if (!Object.class.getName().equals(srcSuperClazz.getName())
&& !Object.class.getName().equals(srcSuperClazz.getName())) {
return getFieldMap(srcSuperClazz, targetSuperClazz, map);
}
return map;
} private static String getSetMethodName(String fieldName) {
return "set" + upper1stChar(fieldName);
} private static String getGetMethodName(String fieldName) {
return "get" + upper1stChar(fieldName);
} private static String upper1stChar(String name) {
byte[] bytes = name.getBytes();
bytes[0] = (byte) ((char) bytes[0] - 'a' + 'A');
return new String(bytes);
} private static <S, T, Z> Class<Z> genClazz(Class<S> sClazz, Class<T> tClazz, String pkg) {
StringBuffer code = ConvertUtil.genConvertUtilStr(sClazz, tClazz, pkg);
// debug
System.out.println(code); String output_path = ConvertUtil.class.getResource("/").getPath(); JavaCompiler jc = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = jc.getStandardFileManager(null, null, null);
Location location = StandardLocation.CLASS_OUTPUT; File[] outputs = new File[] { new File(output_path) };
try {
fileManager.setLocation(location, Arrays.asList(outputs));
} catch (IOException e) {
e.printStackTrace();
} JavaFileObject jfo = new LoadSourceFromString(pkg + "." + getJavaFileName(sClazz, tClazz),
code.toString());
JavaFileObject[] jfos = new JavaFileObject[] { jfo }; Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(jfos);
boolean success = jc.getTask(null, fileManager, null, null, null, compilationUnits).call();
if (success) {
try {
return (Class<Z>) Class.forName(pkg + "." + getJavaFileName(sClazz, tClazz));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
return null;
} private static <S, T, Z> Z instanceClazz(Class<S> sClazz, Class<T> tClazz, String pkg) throws InstantiationException,
IllegalAccessException {
Class<Z> clazzZ = genClazz(sClazz, tClazz, pkg);
if (clazzZ == null) {
throw new NullPointerException();
}
return clazzZ.newInstance();
} public static <S, T> T convert(S s, Class<T> clazz) {
try {
if (s == null || clazz == null) {
throw new NullPointerException("Source value or target class is null.");
}
IConvert<?, ?> convert = CLASS_MAPPING.get(getJavaFileName(s.getClass(), clazz));
if (convert == null) {
convert = instanceClazz(s.getClass(), clazz, PKG);
if (convert == null) {
throw new InstantiationException();
}
CLASS_MAPPING.put(getJavaFileName(s.getClass(), clazz), convert);
}
return ((IConvert<S, T>) convert).convert(s);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
} private static String getJavaFileName(Class<?> srcClazz, Class<?> targetClazz) {
String src = srcClazz.getSimpleName();
String target = targetClazz.getSimpleName();
return PREFIX + src + "2" + target;
} }

通用接口:

public interface IConvert<S, T> {
public T convert(S s) throws InstantiationException, IllegalAccessException;
}

动态编译:

import java.net.URI;

import javax.tools.SimpleJavaFileObject;

public class LoadSourceFromString extends SimpleJavaFileObject {
final String code;
LoadSourceFromString(String name, String code) {
super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
this.code = code;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return code;
}
}

自定义映射关系,以后打算缓存配置文件接口来着,用不上了:

public class MetaFiledData {
private String srcFieldName;
private String srcFieldType;
private String targetFieldName;
private String targetFieldType;
...
getter/setter
}

以上代码完成了基本类型的转换,同时也支持了父类的属性的递归调用。

但是再往下继续写的时候发现一个很尴尬的问题。如果两个对象的类型不一致的情况就很难继续下去了。

举个栗子:

source bean有一个属性是List a,而target bean对应的属性是ArrayList a。直接看我们知道他是可以转的,但是如果在代码里实现,要if else的逻辑会让人崩溃,加之集合内部元素XyzBean很明显也有一些属性,如此递归下去,可能性太多了,甚至有极端情况属性bean里如果有循环依赖,那就。。。

再举个栗子:

如果有人写了List接口的自定义实现,我甚至根本不知道这是个玩意。通过反射可以实现找到List的接口,其中的工作量~~

结果

写到后面没有动力了,可以预见后面有很多的关于集合、自定义对象、不同数据类型转换的逻辑判断,根据其中的巨大工作量,还可能有很多的坑留下,反射异常的不可控制等因素。发现对自己和对调用者而言,很明显弊大于利。

我也终于明白为什么目前没有使用生成内部类的方式写对象转换实现的开源包了,目光短浅了。

性能的确没有问题了,这是废话。。简单类型转换还是太过鸡肋。

最后竟然是相当于只写了个简易的代码生成器。。

收获

  1. 验证了意图的不可实现;
  2. 自动生成转换方法的函数;
  3. 走出一步既有收获,不过要看的远点。。

一次失败的动态转换bean的尝试与思考的更多相关文章

  1. PDF创建及动态转换控件程序包ActivePDF Portfolio

    ActivePDF Portfolio是将4个activePDF最优秀的服务器产品捆绑成一个价格适中的控件程序包.它提供了开发一个完整的服务器端的PDF解决方案所需的一切. 具体功能: activeP ...

  2. mysql 行列动态转换(列联表,交叉表)

    mysql 行列动态转换(列联表,交叉表) (1)动态,适用于列不确定情况 create table table_name( id int primary key, col1 char(2), col ...

  3. Spring BPP中优雅的创建动态代理Bean

    一.前言 本文章所讲并没有基于Aspectj,而是直接通过Cglib以及ProxyFactoryBean去创建代理Bean.通过下面的例子,可以看出Cglib方式创建的代理Bean和ProxyFact ...

  4. 180804-Spring之动态注册bean

    Spring之动态注册bean 什么场景下,需要主动向Spring容器注册bean呢? 如我之前做个的一个支持扫表的基础平台,使用者只需要添加基础配置 + Groovy任务,就可以丢到这个平台上面来运 ...

  5. Spring动态注册bean实现动态多数据源

    Spring动态注册bean实现动态多数据源 http://blog.csdn.net/littlechang/article/details/8071882

  6. SpringBoot27 JDK动态代理详解、获取指定的类类型、动态注册Bean、接口调用框架

    1 JDK动态代理详解 静态代理.JDK动态代理.Cglib动态代理的简单实现方式和区别请参见我的另外一篇博文. 1.1 JDK代理的基本步骤 >通过实现InvocationHandler接口来 ...

  7. 生产环境屏蔽swagger(动态组装bean)

    spring动态组装bean 背景介绍: 整合swagger时需要在生产环境中屏蔽掉swagger的地址,不能在生产环境使用 解决方案 使用动态profile在生产环境中不注入swagger的bean ...

  8. Spring Boot通过ImportBeanDefinitionRegistrar动态注入Bean

    在阅读Spring Boot源码时,看到Spring Boot中大量使用ImportBeanDefinitionRegistrar来实现Bean的动态注入.它是Spring中一个强大的扩展接口.本篇文 ...

  9. springBoot 动态注入bean(bean的注入时机)

    springBoot 动态注入bean(bean的注入时机) 参考博客:https://blog.csdn.net/xcy1193068639/article/details/81517456

随机推荐

  1. 深入理解css中的margin属性

    深入理解css中的margin属性 之前我一直认为margin属性是一个非常简单的属性,但是最近做项目时遇到了一些问题,才发现margin属性还是有一些“坑”的,下面我会介绍margin的基本知识以及 ...

  2. 10 months then free? 10个月,然后自由

    Parole board to recommend Oscar Pistorius be released in August 假释委员会将建议奥斯卡·皮斯托瑞斯在8月份被释放 By Don Melv ...

  3. Web Api系列教程第2季(OData篇)(一)——OData简介和一个小应用

    第一季的链接以及系列导航:http://www.cnblogs.com/fzrain/p/3490137.html 在这里,首先要感谢Taiseer Joudeh不断的为我们带来最新的技术分享,楼主对 ...

  4. 英文论文中i.e.,e.g.,etc.,viz.的简要小结

    英文论文中i.e.,e.g.,etc.,viz.的简要小结 看了一堆用法,全白扯,自己总结的最好记,最实用 i.e. =即.换句话说.也就是说."'In essence' or 'in ot ...

  5. perspective属性

    1. 目前只有safari和chrome浏览器支持 -webkit-perspective. 2. 单位为像素 { -webkit-perspective:500 } 该属性只影响子元素的的透视效果.

  6. #if __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_8_0

    头文件处理 #import <UIKit/UIKit.h> #if __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_8_0 #else #imp ...

  7. Redis学习——环境搭建以及基础命令使用

    0. 前言: 这篇文章旨在对redis环境的搭建以及对redis有个大概的认识. 一.redis搭建: 环境:ubuntu 14 软件包:redis-3.0.3.tar.gz 安装步骤: 1. 首先解 ...

  8. Effective Objective-C 2.0 — 第七条:在对象内部尽量直接访问实例变量

    直接访问实例变量,不经过”方法派发“(method dispatch) 速度快. 直接访问实例变量,不会调用其“设置方法”,这就绕过了为相关属性所定义的“内存管理语义”. 直接访问实例变量,不会触发“ ...

  9. Guid.NewGuid()

    System.Guid.NewGuid().ToString()全球唯一标识符 (GUID) 是一个字母数字标识符,用于指示产品的唯一性安装.在许多流行软件应用程序(例如 Web 浏览器和媒体播放器) ...

  10. JMX整理

    阅读目录 Standard MBean与MXBean的区别 实现 Notification 认证与授权 JConsole Custom Client What and Why JMX JMX的全称为J ...