利用注解和反射,在Java后端开发里偷一个不该偷的懒

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第9天,点击查看活动详情

不要学我不要学我,这只是一个注解和反射的使用案例教程

需求

我是Mybatis-plus重度依赖患者,本着能不写sql语句就不写的原则,损失性能获得只属于我自己的便捷,所以诞生了以下需求:

返回给前端的VO需要消减部分隐私数据

正常情况下,写一个VO,写句sql语句,这不就来了吗,但我是谁?非著名懒狗,一个VO我就要写1+条sql,那以后VO越来越多了怎么办?我直接返回PO不好吗,大不了自己写逻辑消减数据,反正不用写sql语句了

注解实现

所以我就想,我自定义一个注解,名字叫@AccessPO,在返回PO的时候,消减有注解的数据不就行了吗,于是诞生了以下代码

@Target(ElementType.FIELD)  //作用在属性上
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessPO {
    /**
     * 0全不可见 1用户可见 2商户可见
     */

    int type() default 2;

    public static final int ALL_NOT = 0;
    public static final int USER = 1;
    public static final int MERCHANT = 2;

}
复制代码

type用于权限控制,不同的接口返回不同的数据,每种角色需要消减的数据也不同

注解有了,怎么在返回的时候消减呢?那就在修改一下我的Result

基于Springboot返回的类都会被转为json,本质是一个Map,不停的put数据,返回给前端即可

public class R extends HashMap<String, Object> {
    private static final long serialVersionUID = 1L;

    public R() {
        put("code", 0);
        put("msg", "success");
    }


    public static R error(String msg) {
        return error(400, msg);
    }

    public static R error(int code, String msg) {
        R r = new R();
        r.put("code", code);
        r.put("msg", msg);
        return r;
    }

    public static R ok(String msg) {
        R r = new R();
        r.put("code", 200);
        r.put("msg", msg);
        return r;
    }
    ```
    public R put(String key, Object value) {
        super.put(key, value);
        return this;
    }
    ...
复制代码

消减字段对应方法

怎么消减对应字段呢?用反射获取到对象,遍历一下字段,消减有注解的字段吧

private Map<String, Object> getAccessFields(Object value) throws IllegalAccessException {
    final Class<?> clazz = value.getClass();
    final Field[] fields = clazz.getDeclaredFields();
    Map<String, Object> map = new HashMap<>();
    for (int i = 1; i < fields.length; i++) {
        fields[i].setAccessible(true);
        if (!fields[i].isAnnotationPresent(AccessPO.class)) {
            map.put(fields[i].getName(), fields[i].get(value));
        }
    }
    return map;
}
复制代码

获取到类的Class对象,获取所有字段(不包括父类),循环遍历是否存在注解,若不存在则加入待返回的Map中

好像可以了,那我type里面权限控制不是白写了吗?再修改一下
添加一些判断身份函数,调用的时候先调用他们,就知道要删减哪些字段咯


private int access = 2;

public int isAccess() {
    return access;
}

public void setAccess(int access) {
    this.access = access;
}

public R user() {
    this.setAccess(1);
    return this;
}

public R merchant() {
    this.setAccess(2);
    return this;
}

复制代码

身份控制,在Result对象中新建一个access的字段,用来判断当前的权限是如何如何,消减对应的字段

再修改getAccessFields()

private Map<String, Object> getAccessFields(Object value) throws IllegalAccessException {
    final Class<?> clazz = value.getClass();
    final Field[] fields = clazz.getDeclaredFields();
    Map<String, Object> map = new HashMap<>();
    for (int i = 1; i < fields.length; i++) {
        fields[i].setAccessible(true);
        if (fields[i].isAnnotationPresent(AccessPO.class)) {
            final AccessPO accessVO = fields[i].getDeclaredAnnotation(AccessPO.class);
            final int type = accessVO.type();
            if (this.access == type) {
                map.put(fields[i].getName(), fields[i].get(value));
            }
        } else {
            map.put(fields[i].getName(), fields[i].get(value));
        }
    }
    return map;
}
复制代码

如果获取到的注解的type值等于当前Result的权限,则说明身份一致,若为0,1/2都无法匹配,所以全部不见

这样就能完成PO的字段消减了,但还要还有一点问题,如果我要返回一个List<PO>,这该怎么办,那就再加两个函数区分一下单个对象和集合

public R data(String key, Object value) throws IllegalAccessException {
    final Map<String, Object> map = getAccessFields(value);
    super.put(key, map);
    return this;
}

public R list(String key, List value) throws IllegalAccessException {
    List<Map<String, Object>> list = new ArrayList<>();
    for (Object v : value) {
        list.add(getAccessFields(v));
    }
    super.put(key, list);
    return this;
}
复制代码

遍历List,对单个元素进行消减然后获取Map即可,相信SpringBoot转Json的强大

这样就大功告成了,现在返回给前端数据,只需要查询出PO,然后直接R.ok().user().data(data).list(list).put("xxx","xxx")就行了

例外

当然在系统中,也不可能一个VO没有嘛,毕竟VO也是很方便的,那就有了一个新需求,我现在有一个VO是由几个PO组成的,可能需要返回VO或者VOLIST,那就继续加函数

public R voList(String key, List list) throws IllegalAccessException {
    List<Map<String, Map<String, Object>>> r = new ArrayList<>();
    for (Object vo : list) {
        Map<String, Map<String, Object>> map = new HashMap<>();
        final Class<?> clazz = vo.getClass();
        final Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            Object o = field.get(vo);
            map.put(o.getClass().getSimpleName().toLowerCase(Locale.ROOT), getAccessFields(o));
        }
        r.add(map);
    }
    super.put(key, r);
    return this;
}

public R vo(String key, Object vo) throws IllegalAccessException {
    Map<String, Map<String, Object>> r = new HashMap<>();
    final Class<?> clazz = vo.getClass();
    final Field[] fields = clazz.getDeclaredFields();
    for (Field field : fields) {
        field.setAccessible(true);
        Object o = field.get(vo);
        r.put(o.getClass().getSimpleName().toLowerCase(Locale.ROOT), getAccessFields(o));
    }
    super.put(key, r);
    return this;
}
复制代码

VOList的实现思路和list一样,区别是通过反射获取所有的field,再执行解析vo的方法

VO,一个PO是一个Map<String,Object>,那一个VO就是Map<String, Map<String, Object>>,使用getSimpleName().toLowerCase(Locale.ROOT)来获取小写类名当key 然后反射解析出所有的PO,循环求得Map插入即可

总结

在需求不多的情况下,还是挺方便的,只需要加注解即可完成字段隐藏,没有完美的使用数据库,直接获取整个PO的话会比手写sql多一点点压力,还是更建议手写sql
所以这篇文章就当作学习注解和反射的使用了,其实也能拓展一下,通用化一下作为mybatisplus的插件,这样性能就嘎嘎好了,使用也很方便,什么时候写呢?下次一定

猜你喜欢

转载自juejin.im/post/7130896693189672974