一次失败的动态转换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
随机推荐
- 说说css3布局
使用float属性或position属性布局的缺点 <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml&qu ...
- JQuery------Select标签的各种使用方法
optioin属性(value) <option value='>Hello</option> option的点击事件 <select class="s-one ...
- VIM for Python and Django Development
VIM for Python and Django Development VIM-PyDjango created by Programmer for Programmer who work on ...
- 自然语言14_Stemming words with NLTK
https://www.pythonprogramming.net/stemming-nltk-tutorial/?completed=/stop-words-nltk-tutorial/ # -*- ...
- CentOS系统rsync文件同步 安装配置
rsync是类unix系统下的数据镜像备份工具,从软件的命名上就可以看出来了——remote sync 它的特性如下: 可以镜像保存整个目录树和文件系统. 可以很容易做到保持原来文件的权限.时间.软硬 ...
- 使用ASP.NET Web Api构建基于REST风格的服务实战系列教程【九】——API变了,客户端怎么办?
系列导航地址http://www.cnblogs.com/fzrain/p/3490137.html 前言 一旦我们将API发布之后,消费者就会开始使用并和其他的一些数据混在一起.然而,当新的需求出现 ...
- FreeImage使用
http://blog.csdn.net/byxdaz/article/details/6056509 http://blog.chinaunix.net/uid-20660110-id-65639. ...
- Redis学习——SDS字符串源码分析
0. 前言 这里对Redis底层字符串的实现分析,但是看完其实现还没有完整的一个概念,即不太清楚作者为什么要这样子设计,只能窥知一点,需要看完redis如何使用再回头来体会,有不足之处还望告知. 涉及 ...
- RHEL-界面中文乱码问题
一.虚拟机里的rhel更换为中文简体后,中文字符变为小方块 二.解决方法: 安装中文支持的软件包 fonts-chinese-3.02-12.el5.noarch.rpm fonts-ISO8859- ...
- Yii2.0 GridView 新增添加按钮
<?= GridView::widget([ 'dataProvider' => $dataProvider, 'filterModel' => $searchModel, 'col ...