一次失败的动态转换bean的尝试与思考
前因
公司规范确定不允许使用反射类的进行属性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的接口,其中的工作量~~
结果
写到后面没有动力了,可以预见后面有很多的关于集合、自定义对象、不同数据类型转换的逻辑判断,根据其中的巨大工作量,还可能有很多的坑留下,反射异常的不可控制等因素。发现对自己和对调用者而言,很明显弊大于利。
我也终于明白为什么目前没有使用生成内部类的方式写对象转换实现的开源包了,目光短浅了。
性能的确没有问题了,这是废话。。简单类型转换还是太过鸡肋。
最后竟然是相当于只写了个简易的代码生成器。。
收获
- 验证了意图的不可实现;
- 自动生成转换方法的函数;
- 走出一步既有收获,不过要看的远点。。
一次失败的动态转换bean的尝试与思考的更多相关文章
- PDF创建及动态转换控件程序包ActivePDF Portfolio
ActivePDF Portfolio是将4个activePDF最优秀的服务器产品捆绑成一个价格适中的控件程序包.它提供了开发一个完整的服务器端的PDF解决方案所需的一切. 具体功能: activeP ...
- mysql 行列动态转换(列联表,交叉表)
mysql 行列动态转换(列联表,交叉表) (1)动态,适用于列不确定情况 create table table_name( id int primary key, col1 char(2), col ...
- Spring BPP中优雅的创建动态代理Bean
一.前言 本文章所讲并没有基于Aspectj,而是直接通过Cglib以及ProxyFactoryBean去创建代理Bean.通过下面的例子,可以看出Cglib方式创建的代理Bean和ProxyFact ...
- 180804-Spring之动态注册bean
Spring之动态注册bean 什么场景下,需要主动向Spring容器注册bean呢? 如我之前做个的一个支持扫表的基础平台,使用者只需要添加基础配置 + Groovy任务,就可以丢到这个平台上面来运 ...
- Spring动态注册bean实现动态多数据源
Spring动态注册bean实现动态多数据源 http://blog.csdn.net/littlechang/article/details/8071882
- SpringBoot27 JDK动态代理详解、获取指定的类类型、动态注册Bean、接口调用框架
1 JDK动态代理详解 静态代理.JDK动态代理.Cglib动态代理的简单实现方式和区别请参见我的另外一篇博文. 1.1 JDK代理的基本步骤 >通过实现InvocationHandler接口来 ...
- 生产环境屏蔽swagger(动态组装bean)
spring动态组装bean 背景介绍: 整合swagger时需要在生产环境中屏蔽掉swagger的地址,不能在生产环境使用 解决方案 使用动态profile在生产环境中不注入swagger的bean ...
- Spring Boot通过ImportBeanDefinitionRegistrar动态注入Bean
在阅读Spring Boot源码时,看到Spring Boot中大量使用ImportBeanDefinitionRegistrar来实现Bean的动态注入.它是Spring中一个强大的扩展接口.本篇文 ...
- springBoot 动态注入bean(bean的注入时机)
springBoot 动态注入bean(bean的注入时机) 参考博客:https://blog.csdn.net/xcy1193068639/article/details/81517456
随机推荐
- segmentfault.com mongo出识以及对数组的操作
https://segmentfault.com/a/1190000003951602 首先推荐个工具,no-sql-manager-for-mongodb-professional,虽然收费,但是每 ...
- omnet++5.0安装使用
1.下载Windows安装包,5.0的omnetpp-5.0-src-windows.zip 2.解压到d盘 3.D:\omnetpp-5.0\doc找到这个目录,下面有个InstallGuide.p ...
- PHP 数组(遍历)
数组定义$attr = array(); //定义一个空的数组$attr = array(1,2,3,4); //定义一个有值的数组$attr[0]="aa";$attr[1]=& ...
- cocos2d 3.6 win7下的配置
我搭建cocos2.6的开发环境需要安装工具包括: 1.Visual Studio 2012(由于不兼容win7,需要安装Update 4)和虚拟光驱daemon tool,虚拟光驱的下载地址:htt ...
- nodejs fs module
fs.watchFile(filename[, options], listener)# Added in: v0.1.31 filename <String> | <Buffer& ...
- 自然语言14_Stemming words with NLTK
https://www.pythonprogramming.net/stemming-nltk-tutorial/?completed=/stop-words-nltk-tutorial/ # -*- ...
- JSP EL表达式
1 EL表达式简介 EL 全名为Expression Language.EL主要作用: 1.获取数据 EL表达式主要用于替换JSP页面中的脚本表达式,以从各种类型的web域 中检索java对象.获取数 ...
- 入门:JavaWeb Cookie
总结: JavaWeb 利用Cookie 存储在本地用户名和密码,设置Cookie的生存时间. 两个页面,一个登陆页面,一个登陆后的页面,在登陆页面选择是否保存Cookie(保存Cookie,下次自动 ...
- os模块之popen
想查看当前目录下有哪些东西,可以使用os.popen()方法,代码如下: t = (os.popen("dir")) print(t.read()) #运行结果 C:\python ...
- Behavior Trees
https://en.wikipedia.org/wiki/Behavior_Trees_(artificial_intelligence,_robotics_and_control) http:// ...