基于【切面 + 注解 + 反射】实现字典项自动装配
一、存储装配
需求描述:
数据传输时使用明文字典,入库时存入对应字典项标识
如: 居民身份证 -> 01E0DBDC-E3D3-45DB-B23B-B76590478534
- 定义注解 DictionaryMatching 标记实体属性
package com.xxx.modules.crypt.dictionary;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @program: talent-interface-system
* @description: 字典匹配注解 支持动态标记字典表,字段名
* @author: orange
* @create: 2023-08-28 10:33
* @version: 1.0
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DictionaryMatching {
/**
提示信息
* @return: java.lang.String
* @decription 提示信息
* @date 2023/8/29 10:09
*/
String message();
/**
* @return: java.lang.String
* @decription 条件
* @date 2023/8/28 16:34
*/
String condition() default "";
/**
* @return: java.lang.String
* @decription 列名
* @date 2023/8/28 16:34
*/
String columnName() default "";
/**
* @return: java.lang.String
* @decription 对应的数据表名
* @date 2023/8/28 16:34
*/
String tableName() default "";
}
- 定义注解 QueryDictionaryMatching 标记调用接口
package com.xxx.modules.crypt.dictionary;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @program: talent-interface-system
* @description: 字典查询签注解
* @author: orange
* @create: 2023-08-28 10:59
* @version: 1.0
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface QueryDictionaryMatching {
int index() default 0;
}
- 定义切面 DictionaryAspect
package com.xxx.modules.crypt.dictionary;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xxx.efpx.common.core.util.R;
import com.xxx.modules.talent.service.SysZdxService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Field;
/**
* @program: talent-interface-system
* @description: 字典项切面
* @author: orange
* @create: 2023-08-28 10:48
* @version: 1.0
*/
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class DictionaryAspect {
private final SysZdxService sysZdxService;
@Pointcut(value = "@annotation(com.xxx.modules.crypt.dictionary.QueryDictionaryMatching)")
public void cutPoint() {
}
@Around(value = "cutPoint() && @annotation(param)")
public Object around(ProceedingJoinPoint joinPoint, QueryDictionaryMatching param) throws Throwable {
log.info("进入切面执行----------------------------》");
// @QueryDictionaryMatching 注解定义传参索引
Object arguments = joinPoint.getArgs()[param.index()];
log.info("获取的参数是:{}", arguments);
ObjectMapper mapper = new ObjectMapper();
String json ="{}";
try {
json = mapper.writeValueAsString(arguments);
JSONObject jsonObject = JSONObject.parseObject(json);
StringBuilder builder = new StringBuilder();
for (Field field : arguments.getClass().getDeclaredFields()){
if (field.getAnnotation(DictionaryMatching.class) != null) {
String message = field.getAnnotation(DictionaryMatching.class).message();
String condition = field.getAnnotation(DictionaryMatching.class).condition();
String column = field.getAnnotation(DictionaryMatching.class).columnName();
String table = field.getAnnotation(DictionaryMatching.class).tableName();
String key = String.valueOf(jsonObject.get(field.getName()));
String dictId = translateDictValue(condition, column, table, key);
if (null == dictId) {
builder.append("【").append(message).append("】:").append("存在字典项不匹配错误;");
}
field.setAccessible(true);
field.set(arguments,dictId);
}
}
if (builder.toString().length() > 0){
return R.failed(builder.toString());
}
} catch (JsonProcessingException e){
e.printStackTrace();
}
log.info("继续调用方法本体执行----------------------------》");
return joinPoint.proceed();
}
/**
* @param condition 查询条件
* @param column 显示列
* @param table 查询表
* @param key 需要转义的字段
* @return: java.lang.String
* @decription 查询字典项id
* @date 2023/8/28 17:04
*/
private String translateDictValue(String condition, String column, String table, String key){
return sysZdxService.queryDict(condition, column, table, key);
}
}
- 字典匹配业务代码
package com.xxx.modules.talent.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.xxx.modules.crypt.dictionary.DictionaryEcho;
import com.xxx.modules.talent.entity.SysZdxEntity;
import org.apache.ibatis.reflection.MetaObject;
import java.util.List;
/**
* 字典项业务层
* @author orange
* @date 2023-06-25 13:55:48
*/
public interface SysZdxService extends IService<SysZdxEntity> {
/**
* @param condition 查询条件
* @param column 显示列
* @param table 查询表
* @param key 需要转义的字段
* @return: java.lang.String
* @decription 查询字典项id
* @date 2023/8/28 18:39
*/
String queryDict(String condition, String column, String table, String key);
/**
* @param type 类型
* @decription 查询字典项数据
* @date 2023/8/28 18:39
*/
List<SysZdxEntity> selectDictData(String type);
}
package com.xxx.modules.talent.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xxx.modules.crypt.dictionary.DictionaryEcho;
import com.xxx.modules.system.entity.SysDictItem;
import com.xxx.modules.system.service.ISysDictItemService;
import com.xxx.modules.talent.entity.SysZdxEntity;
import com.xxx.modules.talent.mapper.SysZdxMapper;
import com.xxx.modules.talent.service.SysZdxService;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @program: talent-interface-system
* @description: 描述
* @author: orange
* @create: 2023-08-28 13:46
* @version: 1.0
*/
@Service
public class SysZdxServiceImpl extends ServiceImpl<SysZdxMapper, SysZdxEntity> implements SysZdxService {
@Autowired
private SysZdxMapper sysZdxMapper;
@Override
public String queryDict(String condition, String column, String table, String key) {
return sysZdxMapper.queryDictTable(condition, column, table, key);
}
@Override
@Cacheable(value = "dictCache", key = "'type:'+#type")
public List<SysZdxEntity> selectDictData(String type) {
return sysZdxMapper.selectDictData(type);
}
package com.xxx.modules.talent.mapper;
import com.xxx.core.datascope.EfpBaseMapper;
import com.xxx.modules.talent.entity.AppClient;
import com.xxx.modules.talent.entity.SysZdxEntity;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 字典项
*
* @author orange
* @date 2023-06-25 13:55:48
*/
@Mapper
public interface SysZdxMapper extends EfpBaseMapper<SysZdxEntity> {
String queryDictTable(@Param("condition") String condition,
@Param("column") String column,
@Param("table") String table,
@Param("key") String key);
List<SysZdxEntity> selectDictData(@Param("type")String type);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xxx.modules.talent.mapper.SysZdxMapper">
<!--支持动态指定需要查询的字典表-->
<select id="queryDictTable" resultType="java.lang.String">
select
<choose>
<when test="column != '' and column != null">
${column}
</when>
<otherwise>
id
</otherwise>
</choose>
from
<choose>
<when test="table != '' and table != null">
${table}
</when>
<otherwise>
sys_zdx
</otherwise>
</choose>
<where>
<choose>
<when test="condition != '' and condition != null">
${condition} = #{key}
</when>
<otherwise>
`name` = #{key}
</otherwise>
</choose>
and del_flag = '1'
</where>
limit 1
</select>
<select id="selectDictData" resultType="com.xxx.modules.talent.entity.SysZdxEntity">
select
*
from
sys_zdx
<where>
<if test="type != null and type != ''">
description like CONCAT("%",#{type},"%")
</if>
</where>
</select>
</mapper>
- 实体类标记注解 xxxVO
@DictionaryEcho(target = "idType",type = "证件类型")
@DictionaryMatching(message = "证件类型")
private String idType;
- 接口类使用 xxxController
/**
* 新增 xxxVO
* @param 基本情况
* @return R
*/
@PostMapping
@QueryDictionaryMatching //标记需要实现字典项装配的接口
@ApiOperation(value = "基本信息保存", notes = "基本信息保存")
@Log(title = "基本信息新增", businessType = BusinessType.INSERT)
public R save(@RequestBody xxxVO xxxVO) {
//....略
}
二、 回显装配
需求描述:
字典数据查询回显时,显示对应的明文字典项名称
如: 01E0DBDC-E3D3-45DB-B23B-B76590478534 -> 居民身份证
- 定义注解 DictionaryEcho 标记需要匹配的属性值
package com.xxx.modules.crypt.dictionary;
import java.lang.annotation.*;
/**
* @program: talent-interface-system
* @description: 字典数据回显
* @author: orange
* @create: 2023-08-30 10:59
* @version: 1.0
*/
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
public @interface DictionaryEcho {
/**
* @return: java.lang.String
* @decription 类型
* @date 2023/8/28 16:34
*/
String type() default "";
String target();
}
- 定义拦截器 DictionInterceptor
package com.xxx.modules.crypt.dictionary;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.xxx.modules.mybatis.annotation.FieldDict;
import com.xxx.modules.talent.service.SysZdxService;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
/**
* @program: talent-interface-system
* @description: 字典项数据处理拦截器
* @author: orange
* @create: 2023-08-30 16:56
* @version: 1.0
*/
@Slf4j
@Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})
@Component
public class DictionInterceptor implements Interceptor {
@Autowired
private DataBind dataBind;
private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
private static final ReflectorFactory REFLECTOR_FACTORY = new DefaultReflectorFactory();
@Override
@SuppressWarnings("unchecked")
public Object intercept(Invocation invocation) throws Throwable {
Object returnValue = invocation.proceed();
// 对结果进行处理
if (returnValue instanceof ArrayList<?>) {
List<?> list = (ArrayList<?>) returnValue;
if (CollUtil.isNotEmpty(list)) {
Class<?> clazz = list.get(0).getClass();
//获取所有有该注解的成员变量
Field[] declaredFields = clazz.getDeclaredFields();
list.forEach(obj -> {
MetaObject metaObject = MetaObject.forObject(obj, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, REFLECTOR_FACTORY);
Arrays.stream(declaredFields).forEach(field -> {
DictionaryEcho annotation = field.getAnnotation(DictionaryEcho.class);
if (ObjectUtil.isNotEmpty(annotation)) {
Object fieldValue = metaObject.getValue(field.getName());
dataBind.setMetaObject(annotation, fieldValue, metaObject);
}
});
});
}
}
return returnValue;
}
}
- 业务代码
package com.xxx.modules.crypt.dictionary;
import org.apache.ibatis.reflection.MetaObject;
/**
* @Author orange
* @Date 2023/8/11 14:59
* @Description: 绑定业务
* @Version 1.0
*/
public interface DataBind {
void setMetaObject(DictionaryEcho annotation, Object fieldValue, MetaObject metaObject);
}
package com.xxx.modules.crypt.dictionary;
import com.xxx.modules.mybatis.annotation.FieldDict;
import com.xxx.modules.mybatis.databind.IDictDataBind;
import com.xxx.modules.system.entity.SysDictItem;
import com.xxx.modules.system.service.ISysDictItemService;
import com.xxx.modules.talent.entity.SysZdxEntity;
import com.xxx.modules.talent.service.SysZdxService;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Component
public class DataBindImpl implements DataBind {
@Resource
ApplicationContext applicationContext;
/**
* 设置元数据对象<br>
* 根据源对象映射绑定指定属性(自行处理缓存逻辑)
*
* @param dictionaryEcho 数据绑定注解
* @param fieldValue 属性值
* @param metaObject 元数据对象 {@link MetaObject}
* @return
*/
@Override
public void setMetaObject(DictionaryEcho dictionaryEcho, Object fieldValue, MetaObject metaObject) {
SysZdxService sysZdxService = applicationContext.getBean(SysZdxService.class);
List<SysZdxEntity> sysZdxEntityList = sysZdxService.selectDictData(dictionaryEcho.type());
//找到相应的数据
Map<String, String> dictMap = sysZdxEntityList.stream().collect(Collectors.toMap(it -> it.getId(),it -> it.getName()));
// 赋值
metaObject.setValue(dictionaryEcho.target(), dictMap.get(String.valueOf(fieldValue)));
}
}
- 实体属性标记、DAO层查询如上述代码所示
- 附录sys_zdx表设计
CREATE TABLE
sys_zdx
(
id
char(64) COLLATE utf8_bin NOT NULL COMMENT ‘编号’,
zdid
char(64) COLLATE utf8_bin NOT NULL COMMENT ‘字典’,
name
varchar(1000) COLLATE utf8_bin DEFAULT NULL COMMENT ‘字典名称’,
value
varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT ‘数据值’,
type
varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT ‘类型’,
description
varchar(1000) COLLATE utf8_bin DEFAULT NULL COMMENT ‘描述’,
sort
decimal(10,0) DEFAULT NULL COMMENT ‘排序(升序)’,
parent_id
varchar(64) COLLATE utf8_bin DEFAULT ‘0’ COMMENT ‘父级编号’,
remarks
varchar(255) COLLATE utf8_bin DEFAULT NULL COMMENT ‘备注信息’,
del_flag
char(1) COLLATE utf8_bin DEFAULT ‘0’ COMMENT ‘删除标记’,
PRIMARY KEY (id
) USING BTREE,
KEYzdid
(zdid
) USING BTREE,
KEYparent_id
(parent_id
) USING BTREE,
KEYid
(id
) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT=‘字典表’;
总结
前者基于基础的切面来实现字典项匹配入库,通过注解来标记执行的方法。
后者基于Mybatis中的拦截器Interceptor
两者均可解决相似的业务需求。