1

基于SpringBoot实现自动装配返回属性 - 爱我-中华

 2 years ago
source link: https://www.cnblogs.com/jinliang374003909/p/16051843.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

一:需求背景

	在业务开发中经常会有这个一个场景,A(业务表)表中会记录数据的创建人,通常我们会用userId字段记录该数据的创建者,但数据的使用方会要求展示该数据的创建者姓名,故我们会关联用户表拿该用户的姓名。还有一些枚举值的含义也要展示给前端。导致原本一个单表的sql就要写成多表的关联sql,以及枚举含义的转换很是恶心。

例如:业务对象BusinessEntity.java

public class BusinessEntity {

    /**
     * 创建者id
     */
    private Long createUserId;
    /**
     * 创建者名称 (需要关联用户表)
     */
    private String userName;

    /**
     * 数据状态(0:有效,1失效)
     */
    private String status;

    /**
     * 数据状态含义(需要解析0或1的含义给前端)
     */
    private String statusName;
    /**
     * 数据集合
     */
    private List<BusinessEntity> list;
}

二:设计思路

​ 就像@JsonFormat注解,可以指定返回日期格式。我们是不是可以也自定义一个注解,通过这个注解,我们可以自动的把需要联表的数据userName自动填充,需要解析的数据数据statusName如何通过枚举解析。

​ 故定义枚举@AutowiredAttribute如下

/**
 * 自动装配属性
 */
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
public @interface AutowiredAttribute {

    /**
     * 当为默认值时,表明该属性为javaBean,且该javaBean需要自动注入属性
     * 否则为指向的某一个属性
     *
     * @return
     */
    String param() default "";

    /**
     * 默认为BaseEnum.class,
     * 当为默认时注入数据的来源时redis缓存,
     * 否则为枚举类型
     *
     * @return
     */
    Class<? extends BaseEnum> enumClass() default BaseEnum.class;

    /**
     * 数据源
     *
     * @return
     */
    DataSourceEnum dataSource() default DataSourceEnum.EMPTY;

}

定义公共枚举继承继承接口BaseEnum

public interface BaseEnum {

    String getCode();

    String getMsg();
}

定义数据源枚举如下dataSource

public enum DataSourceEnum implements BaseEnum {

    SYSTEM_DICT("sys:dict:", "系统字典值", "sys_dict_value", "name"),
    USER_NAME("user:name:", "用户的id与姓名的映射", "sys_user", "user_name"),
    USER_ROLE("user:role:", "角色id于角色名称映射", "sys_role", "name"),
    DEPT_NAME("dept:name:", "部门的id与部门名称的映射", "sys_dept", "name"),
    EMPTY("00", "默认", "", "");

    DataSourceEnum(String code, String msg, String tableName, String tableColumn) {
        this.code = code;
        this.msg = msg;
        this.tableName = tableName;
        this.tableColumn = tableColumn;
    }

    private String code;
    private String msg;

    /**
     * 表明
     */
    private String tableName;
    /**
     * 表的列
     */
    private String tableColumn;

    @Override
    public String getCode() {
        return code;
    }

    @Override
    public String getMsg() {
        return msg;
    }

    public String getTableName() {
        return tableName;
    }

    public String getTableColumn() {
        return tableColumn;
    }
}

三:使用方法

对比原对象:通过新增注解,就避免的关联查询和数据解析

public class BusinessEntity {

    /**
     * 创建者id
     */
    private Long createUserId;
    /**
     * 创建者名称 (需要关联用户表)
     */
    @AutowiredAttribute(param = "createUserId", dataSource = DataSourceEnum.USER_NAME)
    private String userName;

    /**
     * 数据状态(0:有效,1失效)
     */
    private String status;

    /**
     * 数据状态含义(需要解析0或1的含义给前端)
     */
    @AutowiredAttribute(param = "status", enumClass = StatusEnum.class)
    private String statusName;
    /**
     * 数据集合
     */
    @AutowiredAttribute
    private List<BusinessEntity> list;
}

四:注解解析器(核心代码)

/**
 * 填充相应体
 */
@Component
@ControllerAdvice()
public class FillResponseBodyAdvice implements ResponseBodyAdvice {

    @Autowired
    RedissonClient redissonClient;

    @Autowired
    JdbcTemplate jdbcTemplate;

    private static String GET_CODE_METHOD_NAME = "getCode";
    private static String GET_MSG_METHOD_NAME = "getMsg";

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        if (ResponseResult.class.getName().equals(returnType.getMethod().getReturnType().getName())) {
            return true;
        }
        return false;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if (((ResponseResult<?>) body).getCode() == 200) {//仅仅对相应为200结果处理
            Object data = ((ResponseResult<?>) body).getData();
            Class<?> aClass = data.getClass();
            if (data instanceof List) {
                //集合对象设置属性
                setForListBeanArr((List) data);
            } else {
                //判断是否为自定义java对象
                if (aClass.getSuperclass() instanceof Object) {
                    setForJavaBeanArr(data, aClass);
                }
            }
        }
        return body;
    }

    /**
     * 为集合对象设置属性
     *
     * @param list
     */
    void setForListBeanArr(List<Object> list) {
        for (Object object : list) {
            Class<?> aClass = object.getClass();
            setForJavaBeanArr(object, aClass);
        }
    }


    /**
     * 为自定义javaBean对象设置值
     */
    private void setForJavaBeanArr(Object data, Class<?> aClass) {
        Field[] declaredFields = aClass.getDeclaredFields();
        for (Field field : declaredFields) {
            AutowiredAttribute annotation = field.getAnnotation(AutowiredAttribute.class);
            if (annotation == null) {
                continue;
            }
            //通过枚举注入
            String param = annotation.param();
            try {
                field.setAccessible(true);
                if (param.equals("")) {//注解表明该对象时javaBean对象
                    //获取该javaBean对象
                    Object data2 = field.get(data);
                    if (data2 == null) {
                        continue;
                    }
                    //属性是list对象
                    if (data2 instanceof List) {
                        setForListBeanArr((List) data2);
                    } else if (data2.getClass().getSuperclass() instanceof Object) {
                        setForJavaBeanArr(data2, data2.getClass());
                    }

                } else {
                    //反射获取值
                    Field field1 = aClass.getDeclaredField(param);
                    field1.setAccessible(true);
                    Object o = field1.get(data);
                    if (annotation.enumClass().getName().equals(BaseEnum.class.getName())) {
                        //通过redis注入
                        injectByEnum(o, field, data);
                    } else {
                        //通过枚举注入
                        injectByRedis(o, field, data);
                    }
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
        }
    }

    private void injectByEnum(Object param, Field field, Object data) throws IllegalAccessException {
        AutowiredAttribute annotationAutowiredAttribute = field.getAnnotation(AutowiredAttribute.class);
        DataSourceEnum dataSourceEnum = annotationAutowiredAttribute.dataSource();
        if (dataSourceEnum.equals(DataSourceEnum.EMPTY)) {
            //不规范的
        } else if (dataSourceEnum.equals(DataSourceEnum.SYSTEM_DICT)) {
            Object o = redissonClient.getMap(DataSourceEnum.SYSTEM_DICT.getCode()).get(param);
            if (o == null) {
                o = getDictAndSetRedis(DataSourceEnum.SYSTEM_DICT, param);
            }
            field.set(data, o);
        }
    }

    private void injectByRedis(Object param, Field field, Object data) throws IllegalAccessException {
        AutowiredAttribute annotation = field.getAnnotation(AutowiredAttribute.class);
        Class<? extends BaseEnum> aClass = annotation.enumClass();
        try {
            // 获取所有常量
            Object[] objects = aClass.getEnumConstants();
            //获取指定方法
            Method getMsg = aClass.getMethod(GET_MSG_METHOD_NAME);
            //获取指定方法
            Method getCode = aClass.getMethod(GET_CODE_METHOD_NAME);
            for (Object obj : objects) {
                if (getCode.invoke(obj).equals(param.toString())) {
                    field.set(data, getMsg.invoke(obj));
                    System.out.println(getMsg.invoke(obj));
                }
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

    //
    Object getDictAndSetRedis(DataSourceEnum dataSourceEnum, Object value) {
        String sql = "select name from " + dataSourceEnum.getTableName() + " where id = " + value;
        String s = jdbcTemplate.queryForObject(sql, String.class);
        RMap<Object, Object> map = redissonClient.getMap(dataSourceEnum.getCode());
        map.put(value, s);
        return s;
    }
}

实现了从数据库(mysql)自动查询,并把结果缓冲到数据库。

五:需要思考的技术点

1.为什么注解要用到枚举和泛型class

2.注解解析器,为什么用ResponseBodyAdvice这里解析?不在Filter,Interceptors?

3.对于对象里面嵌套对象,或对象里面嵌套集合,怎么解决注入?递归

欢迎大家使用,如果遇到问题可一起探讨,如果帮助了你可点赞子支持一下。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK