Last active
April 24, 2017 02:06
-
-
Save bohrqiu/5046a2a7d983996f0e5a to your computer and use it in GitHub Desktop.
对象属性复制工具类,采用javassit生成源代码实现属性复制.并能实现包装类型和基础类型之间的转换
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package bohr.javassist; | |
import javassist.ClassPool; | |
import javassist.CtClass; | |
import javassist.CtMethod; | |
import javassist.CtNewMethod; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import java.beans.BeanInfo; | |
import java.beans.IntrospectionException; | |
import java.beans.Introspector; | |
import java.beans.PropertyDescriptor; | |
import java.lang.reflect.Method; | |
import java.util.*; | |
import java.util.concurrent.ConcurrentHashMap; | |
import java.util.concurrent.atomic.AtomicInteger; | |
/** | |
* 对象属性复制工具类,采用javassit生成源代码实现属性复制.并能实现包装类型和基础类型之间的转换 | |
* <h3>Usage Examples</h3> | |
* | |
* <pre class="code"> | |
* {@code | |
* TestBean testBean = new TestBean(); | |
* Copier.copy(TestBean1.createTest(), testBean); | |
* Copier.copy(TestBean1.createTest(), testBean,"t1"); | |
* } | |
* </pre> | |
* @author bohr.qiu@gmail.com | |
*/ | |
public class Copier { | |
private static final Logger logger = LoggerFactory.getLogger(Copier.class); | |
private static final String packageName = getPackageName(Copier.class); | |
/** | |
* 生成class dump路径 | |
*/ | |
public static String dumpClass = null; | |
/** | |
* 打印生成源代码 | |
*/ | |
public static boolean logSource = false; | |
/** | |
* Copy实现类对象缓存 | |
*/ | |
private static final Map<Key, Copy> copierMap = new ConcurrentHashMap<Key, Copy>(); | |
private static interface Copy { | |
void copy(Object source, Object target); | |
} | |
/** | |
* 属性复制 | |
* @param from 源对象 | |
* @param to 目标对象 | |
* @param ignorePropeties 忽略属性 | |
*/ | |
public static void copy(Object from, Object to, String... ignorePropeties) { | |
Key key = getKey(notNull(from, "源对象不能为空"), notNull(to, "目标对象不能为空"), ignorePropeties); | |
Copy copy = copierMap.get(key); | |
if (copy == null) { | |
synchronized (Copier.class) { | |
copy = copierMap.get(key); | |
if (copy == null) { | |
Copier.Generator generator = new Copier.Generator(); | |
generator.setSource(from.getClass()); | |
generator.setTarget(to.getClass()); | |
generator.setIgnorePropeties(ignorePropeties); | |
try { | |
copy = generator.generate().newInstance(); | |
copierMap.put(key, copy); | |
} catch (Exception e) { | |
throw new RuntimeException(e); | |
} | |
} | |
} | |
} | |
copy.copy(from, to); | |
} | |
private static Key getKey(Object from, Object to, String[] ignoreProperties) { | |
Class<?> fromClass = from.getClass(); | |
Class<?> toClass = to.getClass(); | |
return new Key(fromClass, toClass, ignoreProperties); | |
} | |
private static String getPackageName(Class<?> clazz) { | |
String className = clazz.getName(); | |
int lastDotIndex = className.lastIndexOf("."); | |
return (lastDotIndex != -1 ? className.substring(0, lastDotIndex) : ""); | |
} | |
private static <T> T notNull(T obj, String message) { | |
if (obj == null) { | |
throw new NullPointerException(message); | |
} | |
return obj; | |
} | |
private static <T> T notNull(T obj) { | |
return notNull(obj, null); | |
} | |
private static class Generator { | |
private static AtomicInteger classNameIndex = new AtomicInteger(1000); | |
private final String SOURCE = "s"; | |
private final String TARGET = "t"; | |
private Class source; | |
private Class target; | |
private String[] ignorePropeties; | |
private String beginSource; | |
private List<String> propSources = new ArrayList<String>(); | |
private String endSources; | |
public void setSource(Class source) { | |
this.source = source; | |
} | |
public void setTarget(Class target) { | |
this.target = target; | |
} | |
public String[] getIgnorePropeties() { | |
return ignorePropeties; | |
} | |
public void setIgnorePropeties(String[] ignorePropeties) { | |
this.ignorePropeties = ignorePropeties; | |
} | |
private void generateBegin() { | |
//生成方法签名public void copy(TestBean s, TestBean1 t) { | |
beginSource = "public void copy(Object " + SOURCE + "1 ,Object " + TARGET + "1){\n"; | |
//强制转换源对象 | |
String convertSource = source.getName() + " " + SOURCE + " =" + "(" + source.getName() | |
+ ")" + SOURCE + "1;\n"; | |
//强制转换目标对象 | |
String convertTarget = target.getName() + " " + TARGET + " =" + "(" + target.getName() | |
+ ")" + TARGET + "1;\n"; | |
beginSource += convertSource + convertTarget; | |
} | |
private void generateEnd() { | |
endSources = "\n}"; | |
} | |
private void generateBody() { | |
PropertyDescriptor[] getters = getPropertyDescriptors(source); | |
PropertyDescriptor[] setters = getPropertyDescriptors(target); | |
Map<String, PropertyDescriptor> getterMap = new HashMap<String, PropertyDescriptor>(); | |
for (PropertyDescriptor getter : getters) { | |
getterMap.put(getter.getName(), getter); | |
} | |
for (PropertyDescriptor setter : setters) { | |
PropertyDescriptor getter = getterMap.get(setter.getName()); | |
if (!checkCanGenSource(setter, getter)) { | |
continue; | |
} | |
Method readMethod = getter.getReadMethod(); | |
Method writeMethod = setter.getWriteMethod(); | |
String readMethodName = readMethod.getName(); | |
String writerMethodName = writeMethod.getName(); | |
if (compatible(getter, setter)) { | |
propSources.add(genPropertySource(writerMethodName, SOURCE + "." | |
+ readMethodName + "()")); | |
} else { | |
//是否是包装类转换 | |
if (compatibleWrapper(getter, setter)) { | |
Convertor convert = new Convertor(setter.getPropertyType(), SOURCE, | |
readMethod.getName()); | |
String f = convert.convert(); | |
if (f != null) { | |
if (isWrapClass(getter.getPropertyType())) { | |
String source = genCheckWrapperIsNotNullSource(readMethod.getName()); | |
source += "\t" + genPropertySource(writerMethodName, f); | |
propSources.add(source); | |
} else { | |
propSources.add(genPropertySource(writerMethodName, f)); | |
} | |
continue; | |
} | |
} | |
warnCantConvert(setter, getter); | |
} | |
} | |
} | |
private String genCheckWrapperIsNotNullSource(String readName) { | |
return "if(" + SOURCE + "." + readName + "()!=null)\n"; | |
} | |
private String genPropertySource(String writerMethodName, String getterSource) { | |
return TARGET + "." + writerMethodName + "(" + getterSource + ");\n"; | |
} | |
private void warnCantConvert(PropertyDescriptor setter, PropertyDescriptor getter) { | |
logger.warn("[对象属性复制]属性类型转换失败{}.{}({})->{}.{}({})", getter.getReadMethod() | |
.getDeclaringClass().getSimpleName(), getter.getName(), getter.getPropertyType(), | |
setter.getWriteMethod().getDeclaringClass().getSimpleName(), setter.getName(), | |
setter.getPropertyType()); | |
} | |
/** | |
* 检查是否可以生成源代码 | |
*/ | |
private boolean checkCanGenSource(PropertyDescriptor setter, PropertyDescriptor getter) { | |
//是否被忽略 | |
if (ignorePropeties != null && isIgnoredProperty(setter)) { | |
return false; | |
} | |
//检查getter是否存在 | |
if (getter == null) { | |
logger.warn("[对象属性复制]原对象[{}.{}]getter方法不存在", source.getCanonicalName(), | |
setter.getName()); | |
return false; | |
} | |
//检查getter的读方法是否存在 | |
if (getter.getReadMethod() == null) { | |
logger.warn("[对象属性复制]原对象[{}.{}]getter方法不存在", source.getCanonicalName(), | |
getter.getName()); | |
return false; | |
} | |
//检查setter的写方法是否存在 | |
if (setter.getWriteMethod() == null) { | |
logger.warn("[对象属性复制]目标对象[{}.{}]setter方法不存在", target.getCanonicalName(), | |
setter.getName()); | |
return false; | |
} | |
return true; | |
} | |
private boolean compatibleWrapper(PropertyDescriptor getter, PropertyDescriptor setter) { | |
return isWrapClass(getter.getPropertyType(), setter.getPropertyType()) | |
|| isWrapClass(setter.getPropertyType(), getter.getPropertyType()); | |
} | |
private boolean isIgnoredProperty(PropertyDescriptor descriptor) { | |
String propertyName = descriptor.getName(); | |
for (String ignorePropety : ignorePropeties) { | |
if (ignorePropety.equals(propertyName)) { | |
return true; | |
} | |
} | |
return false; | |
} | |
public Class<Copy> generate() { | |
generateBegin(); | |
generateBody(); | |
generateEnd(); | |
StringBuilder sb = new StringBuilder(); | |
sb.append(beginSource); | |
for (String propSource : propSources) { | |
sb.append(propSource); | |
} | |
sb.append(endSources); | |
String source = sb.toString(); | |
if (logSource) { | |
logger.info("\n{}", source); | |
} | |
ClassPool pool = ClassPool.getDefault(); | |
CtClass cc = pool.makeClass(packageName + ".CopierImpl" | |
+ classNameIndex.incrementAndGet()); | |
Class<Copy> copyClass = null; | |
try { | |
cc.addInterface(pool.get(Copy.class.getName())); | |
CtMethod m = CtNewMethod.make(source, cc); | |
cc.addMethod(m); | |
if (dumpClass != null) { | |
CtClass.debugDump = dumpClass; | |
} | |
copyClass = cc.toClass(getDefaultClassLoader(), null); | |
} catch (Exception e) { | |
throw new RuntimeException(e); | |
} | |
return copyClass; | |
} | |
private boolean compatible(PropertyDescriptor getter, PropertyDescriptor setter) { | |
return setter.getPropertyType().isAssignableFrom(getter.getPropertyType()); | |
} | |
private ClassLoader getDefaultClassLoader() { | |
ClassLoader cl = null; | |
try { | |
cl = Thread.currentThread().getContextClassLoader(); | |
} catch (Throwable ex) { | |
//ignore | |
} | |
if (cl == null) { | |
cl = this.getClass().getClassLoader(); | |
} | |
return cl; | |
} | |
public PropertyDescriptor[] getPropertyDescriptors(Class<?> clazz) { | |
BeanInfo beanInfo; | |
try { | |
beanInfo = Introspector.getBeanInfo(clazz); | |
return beanInfo.getPropertyDescriptors(); | |
} catch (IntrospectionException e) { | |
throw new RuntimeException(e); | |
} | |
} | |
/** | |
* 是否为包装类 | |
*/ | |
public static boolean isWrapClass(Class clazz) { | |
try { | |
return ((Class) clazz.getField("TYPE").get(null)).isPrimitive(); | |
} catch (Exception e) { | |
return false; | |
} | |
} | |
/** | |
* source对象类型是否是target对象类型的包装类 | |
*/ | |
public static boolean isWrapClass(Class source, Class target) { | |
if (!target.isPrimitive()) { | |
return false; | |
} | |
try { | |
return source.getField("TYPE").get(null) == target; | |
} catch (Exception e) { | |
return false; | |
} | |
} | |
} | |
/** | |
* 把source转换为target需要的包装器类型或者原始类型 | |
*/ | |
private static class Convertor { | |
private String sourceName; | |
private String readMethodName; | |
private Class<?> targetType; | |
private Convertor(Class<?> targetType, String sourceName, String readMethodName) { | |
this.targetType = notNull(targetType); | |
this.sourceName = notNull(sourceName); | |
this.readMethodName = notNull(readMethodName); | |
} | |
public String convert() { | |
if (targetType.isPrimitive()) { | |
String f = getPrimitiveFormat(); | |
return getterSource() + "." + f + "()"; | |
} else if (Generator.isWrapClass(targetType)) { | |
String f = getWrapperFormat(); | |
return f + "(" + getterSource() + ")"; | |
} else { | |
return null; | |
} | |
} | |
private String getterSource() { | |
return sourceName + "." + readMethodName + "()"; | |
} | |
private String getPrimitiveFormat() { | |
return targetType.getName() + "Value"; | |
} | |
private String getWrapperFormat() { | |
return targetType.getSimpleName() + ".valueOf"; | |
} | |
} | |
private static class Key { | |
private Class<?> fromClass; | |
private Class<?> toClass; | |
private String[] ignoreProperties; | |
public Key(Class<?> fromClass, Class<?> toClass) { | |
this.fromClass = fromClass; | |
this.toClass = toClass; | |
} | |
public Key(Class<?> fromClass, Class<?> toClass, String[] ignoreProperties) { | |
super(); | |
this.fromClass = fromClass; | |
this.toClass = toClass; | |
this.ignoreProperties = ignoreProperties; | |
} | |
@Override | |
public boolean equals(Object o) { | |
if (this == o) | |
return true; | |
if (o == null || getClass() != o.getClass()) | |
return false; | |
Key key = (Key) o; | |
if (!fromClass.equals(key.fromClass)) | |
return false; | |
if (!Arrays.equals(ignoreProperties, key.ignoreProperties)) | |
return false; | |
if (!toClass.equals(key.toClass)) | |
return false; | |
return true; | |
} | |
@Override | |
public int hashCode() { | |
int result = fromClass.hashCode(); | |
result = 31 * result + toClass.hashCode(); | |
result = 31 * result | |
+ (ignoreProperties != null ? Arrays.hashCode(ignoreProperties) : 0); | |
return result; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment