不知你是否写过,或者曾经写过大段的Getter/Setter语句?是否觉得这样的代码繁琐、低效且费时?是否分散了你对核心业务的注意力?至少于我,是的。昨天(3月27日),是一个姑娘的生日,作为一个刚刚入行的菜鸟java程序员,今年我想以程序员的方式遥相祝贺,虽然这段代码她不会收到,也不会用到,但我想,如果能以此为契机,为一些同行带去便利,于我是心安的。当然,由于个人水平着实有限(从代码中你也许会感受到),时间很仓促,功能测试也不全面,这个工具类还很稚嫩,后续我还会在实际工作中加以完善,也希望有兴趣拿去使用的朋友留下宝贵意见,拜谢!
package com.i2f.i2pay.common;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 参数自动化封装工具类: 适用于大量规律性同名参数的自动化封装</br>
* @author TangJS</br>
* @version 瑶光版(1.0)</br>
* 2017/3/27</br>
*</br>
* @version 瑶光版(1.1)</br>
* 对 SpringMVC —— Model接口的 addAttribute方法提供支持</br>
* 2017/3/31</br>
*</br>
* @version 月华版(2.0)</br>
* 1.建立map参数封装方法族,包括 object2Map(原setMapProperties) 、map2Object 、 map2Map 三个方法 。</br>
* 2.统一方法命名规则,使见名更知意</br>
* 3.进行类中方法级别结构化重构——抽取工具类内部流程控制方法,调整工具类各方法流程结构</br>
* 2017/4/6</br>
* </br>
* @version 月华版(2.0.1)</br>
* 程序细节订正</br>
* 思考:迄今程序主体逻辑采用字符串匹配方式确定方法调用,是否有其他更高效的方式</br>
* 2017/4/7</br>
* </br>
* @version 月华版(2.1.0)</br>
* 对上午的思考进行初步实现 :</br>
* 对标准参数封装法 进行类中方法级别逻辑化重构 </br>
* 建立标准参数封装法(Object-Object)方法族</br>
* 2017/4/7 下午</br>
*
* @version 璃莹版(3.0.0)</br>
* 修正传入的键值因含空格导致设置函数匹配失败的问题</br>
* 修正map2Object方法逻辑, 以正确完成非String类型Value的设值 </br>
* 2018/8/27 </br>
*/
public class SetPropertiesTool {
//目标对象的字节码对象
private static Class targetClazz = null;
//参数的字节码对象
private static Class paramsClazz = null;
//作为字段首字母大小写标识位
public static final String LOWER_FIRST = "lower";
public static final String UPPER_FIRST = "UPPER";
//方法前缀
public static final String GETTER = "get";
public static final String SETTER = "set";
/** 标准参数封装法(Object-Object) 方法族
* 1.标准 对象-对象 参数封装法(加强版)
* 2.自定义设值函数参数封装法(params2Target)
* 3.自定义取值函数参数封装法(target2Params)
*/
/**
* 对象-对象 参数封装法(加强版):</br>
* 利用反射, 将参数封装类中的参数值封装进目标对象同名对应参数中, 且可以做到无视参数首字母大小写.</br>
* 适用于目标对象和参数封装类对象都有同名参数,且有相应的get/set方法,如果参数名称不同或值需要特别指定,则需要单独设置.</br>
* @param params 数据源对象
* @param target 目标对象
*/
public static void object2Object(Object params,Object target) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException,SecurityException {
targetClazz = target.getClass();
paramsClazz = params.getClass();
ArrayList<Method> paramsGetterMethods = getAllGetterMethods(paramsClazz);
for (Method paramsGetterMethod : paramsGetterMethods) {
String paramsGetterMethodName = paramsGetterMethod.getName();
String tempTargetSetterMethodName = SETTER.concat(paramsGetterMethodName.substring(3));
Method targetSetterMethod = null;
/* 此处强行调用可能存在的设值方法,
* 如果该设值方法真实存在那么正常执行;
* 如果不存在,则抛出NoSuchMethodException异常捕获后跳过该方法继续执行循环
* 考虑到某些设值函数可能不存在形参,特先获取其参数类型,走if(无参)或else(带参)两种方法调用渠道
* */
Class targetSetterMethodParamType = getMethodParamType(targetClazz, tempTargetSetterMethodName, 1);
try {
if(targetSetterMethodParamType==null) {
targetSetterMethod = targetClazz.getMethod(tempTargetSetterMethodName);
}else {
targetSetterMethod = targetClazz.getMethod(tempTargetSetterMethodName, targetSetterMethodParamType);
}
} catch (NoSuchMethodException e) {
continue;
}
targetSetterMethod.invoke(target, paramsGetterMethod.invoke(params));
}
}
/**原始版本 对象-对象封装法,繁琐,弃用*/
public static Object object2ObjectAbandoned(Object target, Object params) throws Exception {
targetClazz = target.getClass();
paramsClazz = params.getClass();
/*将目标对象的全部属性名称(字符串)封装进targetList, 作为判定params对象中是否含有同名属性的判据*/
Field[] targetFields = targetClazz.getDeclaredFields();
List<String> targetFieldsNameList = new ArrayList<String>();
for (Field field : targetFields) {
String fieldName = field.getName();
if (!targetFieldsNameList.contains(fieldName)) {
targetFieldsNameList.add(fieldName);
}
}
Field[] paramsFields = paramsClazz.getDeclaredFields();
ArrayList<Method> paramsGetterMethods = getAllGetterMethods(paramsClazz);
ArrayList<Method> targetSetterMethods = getAllSetterMethods(targetClazz);
/*遍历参数field对象,为target对象中包含的同名属性设值,所用判据是targetList是否包含字段名(字符串)*/
for (Field paramField : paramsFields) {
// 获取字符串形式字段名
String paramFieldName = paramField.getName();
// 通过判断targetList中是否含有该字段名, 来决定是否需要进行同名属性的封装
if (targetFieldsNameList.contains(paramFieldName)) {
/* 反射调用getter函数得值 */
Object paramValue = null;
for (Method method : paramsGetterMethods) {
String methodName = method.getName();
// 获得字段名(截掉get/set,并将字段首字母小写,由于规则固定,此处写死)
String fieldName = methodName.substring(3, 4).toLowerCase().concat(methodName.substring(4));
// 从param对象中获取相应的getter方法
if (fieldName.equals(paramFieldName)) {
// 反射调用该get方法获得参数值
paramValue = method.invoke(params);
paramsGetterMethods.remove(method);//删除该元素,提升下次大循环效率
break;
}
}
/* 反射调用setter函数设值 */
for (Method method : targetSetterMethods) {
String methodName = method.getName();
// 获得字段名(截掉get/set,并将字段首字母小写,由于规则固定,此处写死)
String fieldName = methodName.substring(3, 4).toLowerCase().concat(methodName.substring(4));
// 从target对象中获取相应的setter方法
if (fieldName.equals(paramFieldName)) {
// 反射调用该set方法设置值
method.invoke(target, paramValue);
targetSetterMethods.remove(method);//删除该元素,提升下次大循环效率
break;
}
}
}
}
return target;
}
/**
* 自定义设值函数参数封装法(params2Target):</br>
* 适用于底层为Map形式储存数据,但设值函数名称不确定</br>
* 如:struts2 , setAttribute()方法 . </br>
* 需要传递目标对象(即调用该设值方法的对象,如applicationContext), 参数封装类及该设值函数名称,如"setAttribute"</br>
* @param target 目标对象
* @param params 参数封装类对象(数据源对象)
*
* 已对springMVC—— Model对象的addAttribute方法提供支持
*/
public static void params2TargetByTargetSetMethod(Object params, Object target, String targetSetMethodName) throws Exception{
targetClazz = target.getClass();
paramsClazz = params.getClass();
/*
* 注:对于springMVC—— Model对象的addAttribute方法,底层是调用BindingAwareModelMap对象的put方法完成参数封装的。
* 由于顶层接口(Model)和底层实现类(BindingAwareModelMap)的设值方法名称不一致,
* 需将用户传递的设值函数名称(addAttribute)替换为底层所用方法(put)
*/
if(targetClazz.toString().endsWith("BindingAwareModelMap")){
targetSetMethodName = "put";
}
Object paramValue = null;
/*从param对象中获取全部getter方法*/
ArrayList<Method> paramsGetterMethods = getAllGetterMethods(paramsClazz);
for (Method method : paramsGetterMethods) {
String methodName = method.getName();
//反射调用该get方法获得参数值
paramValue= method.invoke(params);
//如果paramValue是字符串类型,且为空字符串,设值为null
if(paramValue==""){
paramValue=null;
}
//获得字段名(截掉get,并将字段首字母小写,由于规则固定,此处写死)
String fieldName = methodName.substring(3,4).toLowerCase().concat(methodName.substring(4));
//设值进对象
Method setMethod = targetClazz.getMethod(targetSetMethodName, getMethodParamType(targetClazz, targetSetMethodName, 1), getMethodParamType(targetClazz, targetSetMethodName, 2));
setMethod.invoke(target,fieldName, paramValue);
}
}
/**
* 自定义取值函数参数封装法(params2TargetByParamsGetMethod):</br>
* 适用于从参数封装类对象中使用任意取值方法获得目标对象所需的同名参数并完成封装</br>
* 如:struts2 , getAttribute()方法 . </br>
* 需要传递参数类对象(即调用特殊取值方法的对象,如applicationContext),该取值函数名称(如传递字符串 "getAttribute") 及目标对象</br>
* @param params 调用特殊取值方法的数据源对象
* @param paramsGetMethodName 取值函数名称( 如传递字符串 "getAttribute" )
* @param target 目标对象
*/
public static void params2TargetByParamsGetMethod( Object params, String paramsGetMethodName, Object target) throws Exception{
paramsClazz = params.getClass();
targetClazz = target.getClass();
//获得传入的取值方法
Method getMethod = paramsClazz.getMethod(paramsGetMethodName, getMethodParamType(paramsClazz,paramsGetMethodName,1));
Field[] targetFields = targetClazz.getDeclaredFields();
for (Field field : targetFields) {
String targetFieldName = field.getName();
Object paramValue = getMethod.invoke(params, targetFieldName);
/*反射调用参数封装类对象params的setter函数设值*/
ArrayList<Method> targetSetterMethods = getAllSetterMethods(targetClazz);
for (Method method : targetSetterMethods) {
String methodName = method.getName();
//获得字段名(截掉get/set,并将字段首字母小写,由于规则固定,此处写死)
String tempFieldName = methodName.substring(3,4).toLowerCase().concat(methodName.substring(4));
//从target对象中获取相应的setter方法
if(tempFieldName.equals(targetFieldName)){
//反射调用该set方法设置值
method.invoke(target, paramValue);
targetSetterMethods.remove(method);
break;
}
}
}
}
/**map<String,Object>参数封装法 方法族</br>
* 1.对象-Map 参数封装法</br>
* 2.Map-对象 参数封装法</br>
* 3.Map-Map 参数封装法</br>
*/
/**
* 对象-Map 参数封装法:</br>
* 将参数封装类对象(params)以键值对形式封装进目标对象(target),</br>
* 封装规则为:以参数封装类对象中全部属性名为key,属性值为value ,需要特别指定的字段需手动添加</br>
* 由于所在公司接口字段首字母使用大写,采用传入参数的方式以控制字段首字母大小写情况</br>
* @param targetMap 目标对象(map类型<String,Object>)
* @param params 参数封装类对象(源对象)
* @param key1stFlag map对象中key首字母大小写标识位, 使用工具类内置的常量值 LOWER_FIRST/UPPER_FIRST
*
*/
public static void object2Map(Object params, Map<String,Object> targetMap, String key1stFlag) throws Exception{
paramsClazz = params.getClass();
Object paramValue = null;
/*从param对象中获取全部getter方法*/
ArrayList<Method> paramsGetterMethods = getAllGetterMethods(paramsClazz);
for (Method method : paramsGetterMethods) {
String methodName = method.getName();
//反射调用该get方法获得参数值
paramValue= method.invoke(params);
//如果paramValue是字符串类型,且为空字符串,设值为null
if(paramValue==""){
paramValue=null;
}
//从方法名中截取字段名
String fieldName = getFieldNameFromMethodName(key1stFlag, methodName);
//设值进map
targetMap.put(fieldName, paramValue);
}
}
/**
* Map-对象 参数封装法:</br>
* 将map对象(paramsMap)按键取值,调用目标对象(target)相应设值函数完成参数封装 ,</br>
* 封装规则为:遍历获取paramsMap对象中全部key及相应值,封装进目标对象同名字段中 ,需要特别指定的字段需手动添加</br>
* 由于所在公司接口字段首字母使用大写,采用传入参数的方式以控制字段首字母大小写情况</br>
* @param target 目标对象(map类型<String,Object>)
* @param paramsMap 参数封装类对象(源对象)
* @param key1stFlag map对象中key首字母大小写标识位, 使用工具类内置的常量值LOWER_FIRST/UPPER_FIRST
*
*/
public static void map2Object( Map<Object,Object> paramsMap, String key1stFlag, Object target) throws Exception{
targetClazz = target.getClass();
ArrayList<Method> targetSetterMethods = getAllSetterMethods(targetClazz);
Set<Object> keys = paramsMap.keySet();
for (Object key : keys) {
//去除可能的空格
key = key.toString().trim();
//拼接目标对象设值函数名
String methodName = getMethodNameFromFieldName(SETTER,key1stFlag, (String)key);
//获取该设值方法并调用完成设值
for (Method method : targetSetterMethods) {
if(method.getName().equals(methodName)){
Object paramValue = paramsMap.get(key);
if(paramValue != null) {
//将map中键值对的值强转为方法形参的实际类型, 防止设值函数抛出参数类型不符的异常
paramValue = convertMapValueToRealType(method, paramValue);
//类型转换方法中过滤掉的无意义值, 不再调用invoke方法
if(paramValue != null) {
method.invoke(target, paramValue);
}
}
targetSetterMethods.remove(method);//删除该元素,提升下次大循环效率
break;
}
}
}
}
/**
* 将map中键值对的值强转为方法形参的实际类型, 防止设值函数抛出参数类型不符的异常
* @param method
* @param paramValue
* @return
*/
private static Object convertMapValueToRealType(Method method,Object paramValue) {
String typeName = method.getParameterTypes()[0].getName();
//将非空字符串转换成字符串类型
if(String.class.getName().equals(typeName)) {
return paramValue.toString();
}
//如果该参数类型不是字符串 , 且不是 "null" , "" 等无意义的值, 则将该值转换为其实际类型, 否则视为该值无意义, 置为 null (控制 方法外invoke的执行)
if(!paramValue.equals("null") && !paramValue.equals("")) {
if(Double.class.getName().equals(typeName)) {
paramValue = Double.parseDouble(paramValue.toString());
}
if(Float.class.getName().equals(typeName)) {
paramValue = Float.parseFloat(paramValue.toString());
}
if(Long.class.getName().equals(typeName)) {
paramValue = Long.parseLong(paramValue.toString());
}
if(Integer.class.getName().equals(typeName)) {
paramValue = Integer.parseInt(paramValue.toString());
}
if(Short.class.getName().equals(typeName)) {
paramValue = Short.parseShort(paramValue.toString());
}
if(Byte.class.getName().equals(typeName)) {
paramValue = Byte.parseByte(paramValue.toString());
}
if(Character.class.getName().equals(typeName)) {
paramValue = paramValue.toString().toCharArray()[0];
}
if(Boolean.class.getName().equals(typeName)) {
paramValue = Boolean.valueOf(paramValue.toString());
}
}else {
paramValue = null;
}
return paramValue;
}
/**
* Map-Map 参数封装法:</br>
* 将map对象(paramsMap)按键取值,封装进 另一map对象的同名字段内</br>
* 由于所在公司接口字段首字母使用大写,采用传入参数的方式以控制字段首字母大小写情况</br>
* @param targetMap 目标对象(map类型<String,Object>)
* @param paramsMap 参数封装类对象(源对象)
* @param paramMapKey1stFlag 参数map对象中key首字母大小写标识位, 使用工具类内置的常量值 LOWER_FIRST/UPPER_FIRST
* @param targetMapKey1stFlag 目标map对象中key首字母大小写标识位, 使用工具类内置的常量值 LOWER_FIRST/UPPER_FIRST
*
*/
public static void map2Map(Map<String,Object> paramsMap, String paramMapKey1stFlag, Map<String,Object> targetMap, String targetMapKey1stFlag) throws Exception{
Set<String> keys = paramsMap.keySet();
for (String key : keys) {
//去除可能的空格
key = key.trim();
Object paramValue = paramsMap.get(key);
/*大写转大写,大写转小写,小写转大写,小写转小写四种情况*/
if("UPPER".equals(paramMapKey1stFlag)){//参数键首字母大写
if("UPPER".equals(targetMapKey1stFlag)){//目标键首字母大写
targetMap.put(key, paramValue);
}else{//目标键首字母小写
targetMap.put(key.substring(0, 1).toLowerCase().concat(key.substring(1)), paramValue);
}
}else{//参数建小写
if("UPPER".equals(targetMapKey1stFlag)){//目标键首字母大写
targetMap.put(key.substring(0, 1).toUpperCase().concat(key.substring(1)), paramValue);
}else{//目标键首字母小写
targetMap.put(key, paramValue);
}
}
}
}
/**设值工具类内部 流程控制方法封装*/
/**从方法名中截取字段名:根据传入标识,确定map对象key首字母大小写*/
private static String getFieldNameFromMethodName(String key1stLetterFlag, String methodName) {
String fieldName = null;
//如果需要字段首字母大写,则直接截取去掉“get”的字段名
if("UPPER".equals(key1stLetterFlag)){
fieldName = methodName.substring(3);
}else if ("lower".equals(key1stLetterFlag)) {
//获得字段名(截掉get,并将字段首字母小写,由于规则固定,此处写死)
fieldName = methodName.substring(3,4).toLowerCase().concat(methodName.substring(4));
}
return fieldName;
}
/**将字段名拼接为方法名:传入方法前缀(set/get),根据传入标识,确定是否需要转化字段首字母为大写*/
private static String getMethodNameFromFieldName(String prefix , String key1stLetterFlag, String key) {
String methodName = null;
//如果key首字母是大写,直接拼接相应getter方法
if("UPPER".equals(key1stLetterFlag)){
methodName = prefix.concat(key);
}else if ("lower".equals(key1stLetterFlag)) {//如果小写,首字母转成大写再拼接
methodName = prefix.concat(key.substring(0, 1).toUpperCase().concat(key.substring(1)));
}
return methodName;
}
/**获取传入字节码对象除getClass外的全部getter方法*/
private static ArrayList<Method> getAllGetterMethods(Class clazz){
ArrayList<Method> getterMethods = new ArrayList<Method>();
Method[] methods = clazz.getMethods();
for (Method method : methods) {
//获取除getClass外全部getter方法
if(method.getName().contains("get")&&!method.getName().equals("getClass")){
getterMethods.add(method);
}
}
return getterMethods;
}
/**获取传入字节码对象的全部setter方法*/
private static ArrayList<Method> getAllSetterMethods(Class clazz){
ArrayList<Method> setterMethods = new ArrayList<Method>();
Method[] methods = clazz.getMethods();
for (Method method : methods) {
//获取除getClass外全部getter方法
if(method.getName().contains("set")){
setterMethods.add(method);
}
}
return setterMethods;
}
/**获取符合传入方法名方法的参数类型
* 注:如果传入方法确实存在参数,则按照参数序号,返回相应参数;如不存在参数,则返回 null
* @param targetClazz 目标对象字节码对象
* @param targetGetMethodName 目标对象取值函数
* @param paramIndex 参数位置
* @return 指定位置的参数
*/
private static Class getMethodParamType(Class targetClazz, String targetGetMethodName, int paramIndex) {
Class[] methodParameterTypes = null;
Class methodParamType;
Method[] targetMethods = targetClazz.getMethods();
for (Method method : targetMethods) {
String methodName = method.getName();
if (methodName.equals(targetGetMethodName)) {
methodParameterTypes = method.getParameterTypes();
break;
}
}
if(methodParameterTypes!=null) {
return methodParameterTypes[paramIndex - 1];
}else {
return null;
}
}
}