遇到的问题
有一个注解是在DTO的属性上的,作用于rest接口返回对象序列化的时候,但是发现有一个地方的注解不起作用,最后发现是反射造成的问题
返回的对象:AppMessageDTO:
private List<MessageReceiverDTO> receiver;
public List<MessageReceiverDTO> getReceiver() {
return receiver;
}
public void setReceiver(List<MessageReceiverDTO> receiver) {
this.receiver = receiver;
}
MessageReceiverDTO: 就是这里的@Encrypt没作用
private ReceiverTypeEnum receiverType;
@Encrypt
private Long receiverKey;
private String receiverName;
...省略get/set
@Encrypt起作用的点是在AppMessageDTO序列化 rest接口返回的时候,所以问题在于序列化的时候,没有拦截到receiver数组里面对象属性的注解
贴一个AppMessageDTO对象的来源,如下是sql查出来,mybatis构造的
AppMessageDTO appMessageDTO = messageMapper.selectFullMessageById(messageId, tenantId);
问题原因
核心问题在于,序列化之前,也就是查询出来mybatis构造对象的时候,使用的是反射,而set方法的反射的参数只会限定为List,不带泛型
所以虽然最后对象出来是一个AppMessageDTO对象,但是里面的receiver属性不是一个List属性,而是一个List,所以序列化的时候没有探查到注解
分析
这个属性在数据库是文本储存的,使用了自定义的typeHandler来转换成object
<result column="receiver" property="receiver" jdbcType="VARCHAR" javaType="java.util.List" typeHandler="org.hippius.common.util.ReciverTypeHandler"/>
自定义的类型转换里面直接使用的jackson里面的序列化和反序列化
return new ObjectMapper().readValue(rs.getString(columnName), java.util.List.class);
正常都没有问题,大部分情况下就够了,但是这里由于没有指定List的泛型,所以返回的会是一个List
正常情况下我们如果使用appMessageDTO.setReceiver(List)是无法通过编译的,所以我以为应该mybatis构造会报错,但是实际上如果使用反射来invoke的时候,是可以成功执行的,而mybatis构造就是使用的反射,这也就导致了我们最开始的问题
发一张debug下,查询出对象的数据
问题解决
CollectionType listType = MAPPER.getTypeFactory().constructCollectionType(ArrayList.class, MessageReceiverDTO.class);
return MAPPER.readValue(content, listType);
改写自定义typeHandler,手动定义泛型
扩展
Method setReceiver = null;
//手动调用反射,可以运行
AppMessageDTO appMessageDTO = new AppMessageDTO();
try {
setReceiver = AppMessageDTO.class.getMethod("setReceiver", List.class);
List<Long> list = new ArrayList<>();
list.add(1L);
setReceiver.invoke(appMessageDTO,list);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
//使用带泛型的定义来接收对象,也能成功!!!!
List<MessageReceiverDTO> receiver = appMessageDTO.getReceiver();
//这里的getClass()会报错,类型转换错误!!!!
//System.out.println(receiver.get(0).getClass());
//这里输出为false
System.out.println(receiver.get(0) instanceof MessageReceiverDTO);
//编译错误
//System.out.println(receiver.get(0) instanceof Long);
//能正常被add
List<MessageReceiverDTO> receiver2 = new ArrayList<>();
receiver2.add(receiver.get(0));
//编译错误
//LinkedHashMap hash = receiver.get(0);
//receiver2.add(new LinkedHashMap());
总结:
- 转换过来之后如果调用 getClass()方法,就会报错,比较奇幻,猜测getClass()方法里面做了类型判断
- 使用带泛型的定义来接收对象也没有问题,这样看的话,定义的类型能力很弱,既不能表达真实对象的类型,也不能限制真实对象的类型
- 编译无法通过的代码,实际上有些是可以运行的。编译感觉根本上只是限制你coding,有些能通过的代码是错误的,不能通过的反而是错误的
- 如果需要在这种情况下判断类型,需要用List不带泛型来接收对象,然后使用instanceof 来校验,因为如果带了泛型,编译的时候会有问题