新手原创,不喜轻喷,欢迎交流。
最近项目交付了,想着项目中遇到的一个问题,在传递返回的包装类给app端的时候有的字段位是null,如果这个字段app端需要其中的值,如果获取的值是null的时候会导致app的闪退,为了解决这个问题,我不得不把所有需要传递给app端且有可能为空的字段一个一个赋“”或者0等,为了简便,我把返回的包装类的数据类型只保留String和Integer.写出了以下方法:
@SuppressWarnings("unused")
public class ParameterInit {
public static <E> void inChange2(Object o) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException, NoSuchMethodException, SecurityException {
Class<? extends Object> c = o.getClass(); //获取要返回的类的class
Method[] methods = c.getMethods(); //获取其中的方法(自定义的方法都是public)
for (Method m : methods) {
String methodName = m.getName(); //获取方法名
if(methodName.startsWith("set")) { //判断方法前缀(整个操作只有set/get有关)
String getMethod = methodName.substring(3); //截取除set外的其余部分
Method method = c.getMethod("get"+getMethod ); //拼成get就变成了这个属性的get方法
Object invoke = method.invoke(o); //因为get方法是没有参数的,所以直接传obj进去运行方法
if( invoke == null ) { //判断当前方法的返回值
Type[] genericParameterTypes = m.getGenericParameterTypes(); //获取set方法的参数类型
if( genericParameterTypes[0] == String.class) { //匹配参数类型m.invoke(o, ""); //如果是String类型就赋值“”
}else if( genericParameterTypes[0] == Integer.class) {
m.invoke(o, 0); //如果是Integer类型就赋值0
}
}
}
}
System.out.println(o.toString()); //测试用,打印看是否所有字段都不为null
}
}
注:以上操作的参数Object是经过springframework的BeanUtils.copyProperties赋值过后的类
做完这些之后高兴的和领导说了一下我做了一个工具类,然后领导说就这个问题把copyProperties研究一下,做一个类似的吧
下面就说说对copyProperties的研究心得,做为一个新手,写的不好大家不要喷,谢谢
先说一下spring源在下载,用git下的方式是
git clone https://github.com/spring-projects/spring-framework.git
spring源码是用gradle写的,所以需要用gradle编译才能放到eclipse中,gradle安装这个大家百度吧
然后用gradle编译的命令是 gradle cleanidea eclipse
以下是copyProperties的源码
public static void copyProperties(Object source, Object target) throws BeansException {
copyProperties(source, target, null, (String[]) null);
}
可以看到,这里面只是一个方法的调用
private static void copyProperties(Object source, Object target, Class<?> editable, String... ignoreProperties)
throws BeansException {
Assert.notNull(source, "Source must not be null");
Assert.notNull(target, "Target must not be null");
Class<?> actualEditable = target.getClass();if (editable != null) {
if (!editable.isInstance(target)) {
throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
"] not assignable to Editable class [" + editable.getName() + "]");
}
actualEditable = editable;
}
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
for (PropertyDescriptor targetPd : targetPds) {
Method writeMethod = targetPd.getWriteMethod();
if (writeMethod != null && (ignoreList == null || (!ignoreList.contains(targetPd.getName())))) {
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
if (sourcePd != null) {
Method readMethod = sourcePd.getReadMethod();
if (readMethod != null &&
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
try {
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
readMethod.setAccessible(true);
}
Object value = readMethod.invoke(source);
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
writeMethod.invoke(target, value);
}
catch (Throwable ex) {
throw new FatalBeanException(
"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
}
}
}
}
}
}
首先Assert.notNull这是spring-core包中的一个类,用于判断对象是否为null,为null抛出异常
Class<?> actualEditable = target.getClass();
if (editable != null) {
if (!editable.isInstance(target)) {
throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
"] not assignable to Editable class [" + editable.getName() + "]");
}
actualEditable = editable;
}
这一段是获取目标bean的class和对第三个参数的判断,第三个参数字面意思是editable是target的实例,不是就抛异常
平时没用过,这次没有深入研究、
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
getPropertyDescriptors是BeanUtils中的另一个静态方法
public static PropertyDescriptor[] getPropertyDescriptors(Class<?> clazz) throws BeansException {
CachedIntrospectionResults cr = CachedIntrospectionResults.forClass(clazz);
return cr.getPropertyDescriptors();
}
CachedIntrospectionResults.forClass调用的是CachedIntrospectionResults类的方法
/**
* Create CachedIntrospectionResults for the given bean class.
* @param beanClass the bean class to analyze
* @return the corresponding CachedIntrospectionResults
* @throws BeansException in case of introspection failure
*/
@SuppressWarnings("unchecked")
static CachedIntrospectionResults forClass(Class<?> beanClass) throws BeansException {
CachedIntrospectionResults results = strongClassCache.get(beanClass);
if (results != null) {
return results;
}
results = softClassCache.get(beanClass);
if (results != null) {
return results;
}
results = new CachedIntrospectionResults(beanClass);
ConcurrentMap<Class<?>, CachedIntrospectionResults> classCacheToUse;
if (ClassUtils.isCacheSafe(beanClass, CachedIntrospectionResults.class.getClassLoader()) ||
isClassLoaderAccepted(beanClass.getClassLoader())) {
classCacheToUse = strongClassCache;
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Not strongly caching class [" + beanClass.getName() + "] because it is not cache-safe");
}
classCacheToUse = softClassCache;
}
CachedIntrospectionResults existing = classCacheToUse.putIfAbsent(beanClass, results);
return (existing != null ? existing : results);
}
CachedIntrospectionResults.forClass这个方法我找了很多资料都没有很明白,综合这些资料和这个类上面的注解,初步了解到这个类是一个”自省缓存结果集“
补充一些资料说明就是,这里copyProperties用到的是javaBean中的自省,自省和反射有什么区别呢?
====================================================================================
摘自 https://blog.csdn.net/zhanjixun/article/details/68256724
- 反射
- 内省
而自省技术会用到classLoader类加载器,当使用JavaBean的内省时,使用Introspector,jdk会自动缓存内省的信息(BeanInfo),这一点可以理解,因为内省通过反射的代价是高昂的。当ClassLoader关闭的时候,Introspector的缓存持有BeanInfo的信息,而BeanInfo持有Class的强引用,这将导致ClassLoader和它引用的Class等对象不能被垃圾收集器回收,因此在关闭前,需要手工清除Introspector中的缓存,调用Introspector.flushFromCaches。
虽然在copyProperties中并没有调用Introspector.flushFromCaches,但是spring框架有一个listen是用来监听这些关闭的classLoader的。
再回到getPropertyDescriptors方法上,这个方法的作用就是内省获取javaBean中的字段,通过获取强引用或者软引用或者新建立一个安全的缓存内省,最后返回的就是javaBean中的有get/set方法的字段。类似于class.getFieldsList<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
这里分析的是忽略的字段,就是那些你不想操作的字段!
for (PropertyDescriptor targetPd : targetPds) {
//获取字段的写方法,--set
Method writeMethod = targetPd.getWriteMethod();
//如果方法不为空,且没有忽略
if (writeMethod != null && (ignoreList == null || (!ignoreList.contains(targetPd.getName())))) {
//获取原对象的字段
if (sourcePd != null) {
//如果原对象的字段是存在的,获取原对象的这个字段的get方法
if (readMethod != null &&
//判断源对象的get的返回值类型和目标对象的set参数是否一致
try {
//如果类型一致再判断方法是不是public修饰的
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
//如果不是就强制反射
}
//然后获取值
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
//赋值
}
catch (Throwable ex) {
throw new FatalBeanException(
"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
这中间还穿插了好多个方法,没有详细贴上来,在文章最后我会贴一张大图,有兴趣的可以看看
一段代码跑下来感觉脑细胞死了不少,似乎头发掉了几根。。。好复杂的样子,所以我决定使用普通反射,代码如下:
基于目标类中的字段全是String类型的来做的
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import com.financial.core.SuperStringUtils;
import com.financial.core.date.DateUtil;
import com.financial.core.exception.ServiceException;
public class BeanCopyUtils {
public static void copyProperties(Object source,Object target) {
try {
copyProperties(source, target,(String[]) null);
} catch (ServiceException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
private static void copyProperties(Object source,Object target,String... ignoreProperties) throws ServiceException, Exception {
if(SuperStringUtils.isBlank(source) || SuperStringUtils.isBlank(target)) {
throw new ServiceException("500","源类或目标类不能为空");
}
Class<?> targetClass = target.getClass();
Class<?> sourceClass = source.getClass();
Field[] targetFields = targetClass.getDeclaredFields();
Field[] sourceFields = sourceClass.getDeclaredFields();
ArrayList<String> fieldList = change(sourceFields);
for (Field field : targetFields) {
String targetMethod = obtainSetMethod(field);
Method method = targetClass.getMethod(targetMethod, String.class);
//如果源类中有这个字段,赋值源类中的值给目标类
if(checkField(field,fieldList)){
Method fieldMethod = sourceClass.getMethod(obtainGetMethod(field));
//获取源对象中get方法的返回值
Object sourceResult = fieldMethod.invoke(source);if(sourceResult != null ) {
if(sourceResult.getClass() == java.util.Date.class){
method.invoke(target, DateUtil.formatDate((java.util.Date)sourceResult));
}else{
method.invoke(target, sourceResult+"");
}
}else {
method.invoke(target, "");
}
}else {
//如果源类中没有这个字段,则把目标类中的值赋为""或者0
method.invoke(target, "");
}
}
}
//将字段数组转换为StringList
private static ArrayList<String> change(Field[] sourceFields) {
ArrayList<String> fieldList = new ArrayList<>();
for(int i = 0;i<sourceFields.length;i++) {
fieldList.add(sourceFields[i].getName());
}
return fieldList;
}
//比较源类中是否含有这个字段
private static Boolean checkField(Field targetField,ArrayList<String> fieldList) {
if(fieldList.contains(targetField.getName())) { //很遗憾不能拿Field[]直接用的比较
return true;
}
return false;
}
//生成这个字段的get方法
private static String obtainGetMethod(Field field) {
String fieldName = field.getName();
String upperCase = "get" + fieldName.substring(0, 1).toUpperCase();
return upperCase+fieldName.substring(1);
}
//生成这个字段的set方法
private static String obtainSetMethod(Field field) {
String fieldName = field.getName();
String upperCase = "set" + fieldName.substring(0, 1).toUpperCase();
return upperCase+fieldName.substring(1);
}
}