在使用 SpringBoot 的默认序列化方式 Jackson 时,如果在执行 RPC,并且参数中含有 Map 类型的字段时,如果不做特别的处理,会抛出异常,提示参数不能是 LinkedHashMap,这遍文章将探寻报错的原因与解决方法。
当调用一个参数为Map的方法时(在 RPC 调用中常常出现),例如如下的方法:
public void setRequestParam(Map<String, Object> requestParam) {
this.requestParam = requestParam;
}
为什么使用反射调用会报类型转化异常?
反射调用分为两部,第一步时找到反射方法,第二步时执行反射调用,在获取反射方法时,执行的具体方法是:
Method method = clazz.getMethod(setter, value.getClass());
value.getClass() 返回的是 LinkedHashMap 实例,但是在类中并没有 参数类型为 LinkedHashMap 的方法,比如:
public void setRequestParam(LinkedHashMap<String, Object> requestParam) {
this.requestParam = requestParam;
}
所以会报错。
所以在获取方法的方式不能是指定具体的参数类型,而是应该遍历类的具体方法,例如:
// 查找匹配的方法
Method targetMethod = null;
for (Method method : clazz.getMethods()) {
if (method.getName().equals(methodName) && method.getParameterCount() == 1) {
Class<?> parameterType = method.getParameterTypes()[0];
if (parameterType.isAssignableFrom(LinkedHashMap.class)) {
targetMethod = method;
break;
}
}
}
这样才能无视 Map 的接口或者是 Map 的子类,执行反射调用,但是这也会有一个问题,执行某个同名方法的重载时可能会找到到方法导致报错。
完整的工具类示例下:
public class BeanUtils {
/**
* 将字符串的首字母大写。
*
* @param str 需要处理的字符串。
* @return 首字母大写的字符串。
*/
private static String capitalize(String str) {
if (str == null || str.isEmpty()) {
return str;
}
return str.substring(0, 1).toUpperCase() + str.substring(1);
}
/**
* 将Java对象的字段和属性转换为Map。
*
* @param object Java对象
* @return Map类型的键值对
*/
public static Map<String, Object> objectToMap(Object object) {
Map<String, Object> map = new HashMap<>();
Class<?> clazz = object.getClass();
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
try {
map.put(field.getName(), field.get(object));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return map;
}
/**
* 将JSON字符串转换为Map。
*
* @param jsonString JSON字符串
* @return Map类型的键值对
*/
public static Map jsonToMap(String jsonString) {
ObjectMapper objectMapper = new ObjectMapper();
try {
return objectMapper.readValue(jsonString, Map.class);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将Map转换为特定类型的对象。
*
* @param map 包含属性值的Map。
* @param clazz 目标对象的Class。
* @param <T> 目标对象的类型。
* @return 转换后的对象。
* @throws Exception 可能抛出的异常。
*/
public static <T> T mapToObject(Map<String, Object> map, Class<T> clazz) throws Exception {
T instance = clazz.newInstance();
if (null == map && map.isEmpty()) {
return null;
}
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
// 尝试获取属性名对应的setter方法
String methodName = "set" + capitalize(key);
// 查找匹配的方法
Method targetMethod = null;
for (Method method : clazz.getMethods()) {
if (method.getName().equals(methodName) && method.getParameterCount() == 1) {
Class<?> parameterType = method.getParameterTypes()[0];
if (parameterType.isAssignableFrom(LinkedHashMap.class)) {
targetMethod = method;
break;
}
}
}
if (targetMethod != null) {
targetMethod.invoke(instance, value);
}
}
return instance;
}
}