反射是Java中比较重要的一个知识点,但平时可能用的有点少。最近刚好用到了,就记下来。
需求
首先简要描述一下需求:有一个对象需要投递到消息队列中,但需要根据配置文件中的过滤条件进行过滤。目前只有两个条件,后期可能会增加。过滤条件为消息字段。
实现
例如有一个User类, 目前只根据userId过滤
public class User implement Serializable {
private Integer userId;
private String name;
private String city;
...
// get and set
}
@Service
public class Service {
@value("userId")
private Integer targetId;
@value("name")
private String targetName;
public void sendUser(User user){
Integer userId = user.getUserId();
String name = user.getName();
if((targetId == null || targetId.toString().equals(userId) && (targetName == null || targetName.equals(name))) {
send(queue, user);
}
}
}
配置文件
userId=1
name=hello
但是如果后期增加过滤条件的话, 上面的代码就需要改动. 为了让代码能够兼容更多的过滤条件, 最简单的一种方式就是——直接把所有字段都列出来, 然后一一对比。
@Configuration
@ConfigurationProperties(prefix = "targetUser")
public class TargetUserConfig {
private Integer userId;
private String name;
private String city;
...
// get and set
}
// 判断每个字段
但是User属性少还好,如果属性很很,就是一大堆代码。而且如果User属性增加了,代码还是得改。看起来也不好看,不够优雅。
这个时候就可以采用另一种方式——反射。配置类如下:
@Configuration
@ConfigurationProperties(prefix = "targetUser")
public class TargetUserConfig {
private Map<String, String> map;
...
// get and set
}
@Service
public class Service {
@autowired
private TargetUserConfig targetUserConfig;
public void sendUser(User user){
if(user==null) return;
Map<String, String> map = targetUserConfig.getMap();
boolean flag = true;
for (Map.Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
Class<User> uClass = User.class;
// 根据 key 获取对应的 Field 对象
Field field = msgClass.getDeclaredField(key);
// 设置访问权限, (private修饰的字段不能直接访问)
if (!field.isAccessible()) {
field.setAccessible(true);
}
Object fieldValue = field.get(user);
if (fieldValue == null || !fieldValue.toString().equals(value)){
flag = false;
break;
}
}
}
}
配置文件
targetUser.map.userId=1
targetUser.map.name=hello
这样的话, 后面如果需要增加过滤条件只需要再配置文件中加就可以了, 代码不需要改动, 而且即使是User改动也不要紧. 因为反射直接通过map.key从user对象中获取对应字段的值.
优化
由于反射操作比较耗时, 会影响性能, 所以可以考虑加个缓存, 把field对象存起来, 这样就可以查询对象的时间了. (虽然用异步线程也可以,但是加缓存写起来简单,而且线程切换也是开销)
经过简单测试, 大概能快10倍左右
// 把反射获取到的 field 对象放入map中
private Map<String, Field> fieldCache = new HashMap<>();
public void sendUser(User user){
if(user==null) return;
Map<String, String> map = targetUserConfig.getMap();
boolean flag = true;
for (Map.Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
Field field = fieldCache.computIfAbsent(key, k -> {
Class<User> uClass = User.class;
// 根据 key 获取对应的 Field 对象
Field field = msgClass.getDeclaredField(key);
// 设置访问权限, (private修饰的字段不能直接访问)
if (!field.isAccessible()) {
field.setAccessible(true);
}
return field;
});
Object fieldValue = field.get(user);
if (fieldValue == null || !fieldValue.toString().equals(value)){
flag = false;
break;
}
}
}