简介
AOP也是我们常说的面向切面编程,AOP在我们开发过程中应用也比较多,在这里我们就基于AOP来实现一个数据字典转换的案例。
案例介绍
相信各位在写代码的时候肯定有过这样的经历,我们设计数据库时对于字典类的数据一般都会采用字典码进行存储,而不是直接使用字典值。首先是因为这是一种开发规范,其次使用编码也会利于数据存储,数据整体也会比较干净整洁。
数据字典编码的定义一般也会做一些分类,比如说U01开头代表用户类型,U02开头代表用户性别等等,这样也有助于我们进行数据分析。
下面我们就简单以一个用户表来做数据字典转换。
案例实现
创建表:
CREATE TABLE `t_user` ( `id` BIGINT(12) NOT NULL AUTO_INCREMENT, `user_code` VARCHAR(20) NOT NULL, `user_name` VARCHAR(50) NOT NULL, `user_type` CHAR(5) NOT NULL COMMENT '用户类型 -> U0101:普通用户,U0102:VIP用户', `gender` CHAR(5) NOT NULL COMMENT '用户性别 -> U0299:未知,U0201:男,U0202:女', PRIMARY KEY (`id`) ) CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
代码结构:
相关代码可由逆向工程去生成,我们就简单的编写了从控制层请求到服务层的业务处理再到dao层的数据处理,在这里我就一一将代码展示出来了各位还需要自己多动手。下面就直接上代码结构图
测试接口:
接下来我们在UserController类中写一个测试接口,根据userCode查询用户信息如下:
@GetMapping("/{userCode}") public UserVo queryUser(@PathVariable("userCode") String userCode) { UserDto userDto = userService.queryUserByCode(userCode); return userMapStruct.userDtoToUserVo(userDto); }
初始化测试数据:
切面定义
定义注解类:用于标记哪个地方需要进行数据字典转换切面。
@Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DictParam { /** * 映射目标属性,为空默认直接映射到field,替换掉原来的field * @return */ String targetField() default ""; /** * 映射来源属性 * @return */ String field(); /** * 数据字典类型 * @return */ String dictType(); }
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DictHelper { /** * 字典转换参数配置 */ DictParam[] value(); }
定义注解切面类:主要实现字典转换的核心内容。
@Slf4j @Aspect public class DictHelperAspect { public DictHelperAspect() { } @Around("@annotation(dictHelper)") public Object doAround(ProceedingJoinPoint joinPoint, DictHelper dictHelper) { try { // 执行方法得到结果 Object result = joinPoint.proceed(); DictParam[] values = dictHelper.values(); if (values == null || values.length == 0) { return result; } // 字典转换开始(使用反射) for (DictParam value : values) { Class<?> clazz = result.getClass(); // 反射调用get方法获取字段值 Method sourceMethod = clazz.getMethod("get" firstToUppercase(value.field())); Object fieldValue = sourceMethod.invoke(result); // 获取字典值 String dictValue = DictConfig.DICT_MAPPER.get(value.dictType()).get(fieldValue.toString()); // 获取目标方法进行设值 String targetField = StringUtils.isBlank(value.targetField()) ? value.field() : value.targetField(); Method targetMethod = clazz.getMethod("set" firstToUppercase(targetField), dictValue.getClass()); targetMethod.invoke(result, dictValue); } return result; } catch (Throwable throwable) { log.error("error:", throwable); return null; } } private String firstToUppercase(String str) { return str.substring(0, 1).toUpperCase() str.substring(1); } }
Service层添加字典转换的切面扫描注解:
@DictHelper(values = { @DictParam(field = "userType", targetField = "userTypeShow", dictType = "USER_TYPE"), @DictParam(field = "gender", targetField = "genderShow", dictType = "GENDER") }) public UserDto queryUserByCode(String userCode) { UserEntity userEntity = userMapper.selectUser(userCode); return userMapStruct.userEntityToUserDto(userEntity); }
如上代码主要分三个步骤:
- 根据
@DictParam
注解配置的数据来源字段通过返回调用数据返回对象获取数据来源字典编码。 - 根据字典编码通过字典编码表(这里直接使用静态
DictConfig
直接调用)找到对应字典值。 - 根据
@DictParam
注解配置的目标数据字典,将匹配到的数据字典值通过反射将数据回填到对象中。
注意:各位开发者朋友们,看到这里是不是以为很简单呢,但是在实际开发过程中我们更注重的是程序的安全、稳定、可靠,所以这也不难看出上面的代码当中省去了许多校验
静态字典:实际开发过程中,不建议这么配置,因为这样是完全不灵活的,这里只是为了方便演示而已。实际业务当中可以自定义一种数据字典加载策略(服务启动成功后加载或者定期刷新加载),将字典加载到内存,或者使用数据库结合redis做内存也可以,数据字典还是要避免频繁直接的去查数据库。
public class DictConfig { public static final Map<String, Map<String, String>> DICT_MAPPER = new HashMap<>(); static { Map<String, String> USER_TYPE = new HashMap<>(); USER_TYPE.put("U0101", "普通用户"); USER_TYPE.put("U0102", "VIP用户"); DICT_MAPPER.put("USER_TYPE", USER_TYPE); Map<String, String> GENDER = new HashMap<>(); GENDER.put("U0201", "男"); GENDER.put("U0202", "女"); GENDER.put("U0299", "未知"); DICT_MAPPER.put("GENDER", GENDER); } }
运行结果:
总结
利用切面编程还可以做很多事,本文所展示的数据字典转换也仅仅只是冰山一角,像用的比较多的分页处理我们也一样可以用这种方式去做。
数据字典在我们开发设计当中是必不可少的,合理的使用好数据字典还是很有必要的。
到此这篇关于如何利用Java使用AOP实现数据字典转换的文章就介绍到这了,更多相关Java AOP字典转换内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!