反射在项目中的使用:根据字段名动态过滤,以及一点点优化

反射是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;
        }
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值