利用注解和反射,在Java后端开发里偷一个不该偷的懒
不要学我不要学我,这只是一个注解和反射的使用案例教程
需求
我是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的插件,这样性能就嘎嘎好了,使用也很方便,什么时候写呢?下次一定
转载自:https://juejin.cn/post/7130896693189672974