Java字典注解的简单实现(AOP)

news2024/12/24 3:01:41

需求来源

在开发过程中,必不可少会用到字典,例如,数据库字段性别字段可能是这样的:1:男;2:女,在数据存储的时候用1和2,但是在前端展示的时候需要使用男和女,而我们一般的开发方式有三种:

  1. 在sql查询的时候使用case函数判断,做字段值的转换
  2. 在查询出来结果后,遍历查询结果,根据数据库原值,新加上一个对应的字典值
  3. 返回原值,告诉调用方字典列表,让调用方自行转换

最终的结果大概是这样:

{

    "id": "100",

    "sex": "1",

    "sexText": "男"

}

方式1:需要手动写SQL,很繁琐

方式2:查出来之后需要再次便利一次,也要写代码,容易重复造轮子

方式3:对调用方来说不是很方便,并且后续加入字典含义需要改变,又需要同步给调用方,让他们去更新上线(虽然一般不会出现这样的情况)

鉴于这样的情况是非常常见的,这里我想针对方式2做一个封装,避免重复的去遍历、设置,所有有了这个字典转换组件。

实现思路

可以使用aop的思想,使用环绕通知,获取到方法的结果后,在对结果进行遍历处理

  1. 定义两个注解
    1. DictCovert 用于定义切入点
    2. Dict 真正的字典注解
  2. DictAspect 用来实现具体的注解逻辑
  3. DictUtil 封装字典翻译的逻辑,可提供手动方式

代码实现

定义DictCovert注解:

import java.lang.annotation.*;

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DictCovert {
}

定义Dict注解:

import java.lang.annotation.*;

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Dict {

    /**
     * 是否是本地字典
     * 本地字典:使用dictVal值做解析
     * 非本地字典:集合字典服务查询回来
     **/
    boolean isLocal() default true;

    /**
     * 字典存放后缀
     * 默认 "Text"
     * 例 原始字段名:type  翻译储存的字段名:typeText
     **/
    String suffix() default "Text";

    /**
     * 本地字典键值对,当isLocal=true时有效
     * 例如:1:草稿;2:已提交;3:已删除
     * 为了方便,使用的是中文的:和;
     **/
    String localDictVal() default "";

    /**
     * 在线字典编码,当isLocal=false时有效
     * 例如:onlineDictCode=SEX,则会调用 sysDictService.getDictItemMap("SEX");去获取字典值
     */
    String onlineDictCode() default "";

}

dictUtil工具类

注意:这里面有些类是需要替换成自己的,比如字典服务类,比如分页的对象等

import cn.hutool.core.util.ObjectUtil;
import com.xxx.*;
import com.google.common.collect.Lists;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author: v_luojinj
 * @since: 2023/2/2
 **/
@Component
public class DictUtil {

    /**
     * 使用个缓存,避免列表数据多次进行字典查询
     */
    private static ThreadLocal<Map<String,Map<String,String>>> cache  = ThreadLocal.withInitial(ConcurrentHashMap::new);

    public static Object parseResult(Object result) throws Exception {
        //判断空
        if (Objects.isNull(result)) {
            return null;
        }
        //判断结果类型
        if (result instanceof List) {
            //LIST类型
            List<Object> list = Lists.newArrayList();
            for (Object obj : (List<Object>) result) {
                list.add(parseDict(obj));
            }
            return list;
        } else if (result instanceof PageResult) {
            //自定义的分页返回结果集类型  实际结果在 list字段中。 处理和LIST一致
            PageResult pageResult = (PageResult) result;
            List<Object> list = Lists.newArrayList();
            for (Object obj : pageResult.getList()) {
                list.add(parseDict(obj));
            }
            //分页数据中 重新放入结果
            pageResult.setList(list);
            return pageResult;
        } else {
            //单实例对象类型
            return parseDict(result);
        }
    }

    /**
     * 字典转换
     * @param obj
     */
    private static Object parseDict(Object obj) throws NoSuchFieldException, IllegalAccessException {
        Field[] fields = obj.getClass().getDeclaredFields();
        //非空判断
        if (ObjectUtil.isEmpty(fields)) {
            return null;
        }
        for (Field field : fields) {
            //判断每一个字典是否有Dict注解
            if (Objects.nonNull(field.getAnnotation(Dict.class))) {
                handleDict(obj, field);
            }
        }
        return obj;
    }

    /**
     * 处理字典注解的字段
     * @param obj
     * @param field
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    private static void handleDict(Object obj, Field field) throws NoSuchFieldException, IllegalAccessException {
        Dict dict = field.getAnnotation(Dict.class);
        boolean local = dict.isLocal();
        if(local) {
            handleLocalDict(obj, field, dict);
        } else {
            handleOnlineDict(obj, field, dict);
        }
    }

    /**
     * 处理本地字典转换的逻辑
     * @param obj
     * @param field
     * @param dict
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    private static void handleLocalDict(Object obj, Field field, Dict dict) throws NoSuchFieldException, IllegalAccessException {
        String suffix = dict.suffix();
        String dictVal = dict.localDictVal();
        field.setAccessible(true);
        Object key = field.get(obj);
        if(StringUtils.isEmpty(dictVal) || Objects.isNull(key)){
            return;
        }
        Map<String, String> dictValMap = getLocalDictValMap(dictVal);
        Field name = obj.getClass().getDeclaredField(field.getName() + suffix);
        name.setAccessible(true);
        name.set(obj, dictValMap.get(key.toString()));
        name.setAccessible(false);
        field.setAccessible(false);
    }

    /**
     * 处理在线字典转换的逻辑
     * @param obj
     * @param field
     * @param dict
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    private static void handleOnlineDict(Object obj, Field field, Dict dict) throws NoSuchFieldException, IllegalAccessException {
        String suffix = dict.suffix();
        String dictCode = dict.onlineDictCode();
        field.setAccessible(true);
        Object key = field.get(obj);
        if(StringUtils.isEmpty(dictCode) || Objects.isNull(key)){
            return;
        }
        Map<String, String> dictValMap = getOnlineDictValMap(dictCode);
        Field name = obj.getClass().getDeclaredField(field.getName() + suffix);
        name.setAccessible(true);
        name.set(obj, dictValMap.get(key.toString()));
        name.setAccessible(false);
        field.setAccessible(false);
    }

    /**
     * 获取本地字典值列表,格式要求:1:草稿,2:已提交,3:已删除
     * @param dictVal 字典键值对
     * @return 字典列表
     */
    private static Map<String, String> getLocalDictValMap(String dictVal) {
        if (StringUtils.isEmpty(dictVal)) {
            return Collections.emptyMap();
        }
        Map<String, Map<String, String>> dictMap = cache.get();
        if(dictMap.containsKey(dictVal)){
            return dictMap.get(dictVal);
        }
        Map<String, String> valMap = new HashMap<>();
        String[] split = dictVal.split(";");
        for (String s : split) {
            String[] val = s.split(":");
            if (val.length != 2) {
                continue;
            }
            valMap.put(val[0], val[1]);
        }
        dictMap.put(dictVal,valMap);
        cache.set(dictMap);
        return valMap;
    }

    /**
     * 获取字典服务那边的字典列表
     * @param dictCode 字典编码
     * @return 字典列表
     */
    private static Map<String, String> getOnlineDictValMap(String dictCode) {
        if (StringUtils.isEmpty(dictCode)) {
            return Collections.emptyMap();
        }
        Map<String, Map<String, String>> dictMap = cache.get();
        if(dictMap.containsKey(dictCode)){
            return dictMap.get(dictCode);
        }
        SysDictService sysDictService = SpringContextHolder.getBean(SysDictService.class);
        Map<String, String> dictItemMap = sysDictService.getDictItemMap(dictCode);
        if(CollectionUtils.isEmpty(dictItemMap)){
            throw new ServiceException(ErrorCode.OBJECT_OR_PROPERTY_NOT_EXISTS,"未找到该字典编码:"+dictCode+",请检查");
        }
        dictMap.put(dictCode,dictItemMap);
        cache.set(dictMap);
        return dictItemMap;
    }

    /**
     * 清除缓存
     */
    public static void clearCache(){
        cache.remove();
    }
}

DictAsect切面:


import com.dg.ys.zsyz.annotation.dict.DictUtil;
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.context.annotation.Configuration;

/**
 * @author: v_luojinj
 * @since: 2023/1/18
 **/
@Aspect
@Configuration
public class DictAspect {

    @Pointcut("@annotation(com.dg.ys.zsyz.annotation.dict.DictCovert)")
    public void dictCovert() {
    }

    @Around("dictCovert()")
    public Object process(ProceedingJoinPoint joinPoint) throws Throwable {
        //1.执行原方法
        Object proceed = joinPoint.proceed();
        //2.拿到原方法的原返回值 调用parseResult进行字典转换
        Object result = DictUtil.parseResult(proceed);
        DictUtil.clearCache();
        return result;
    }
}

使用方式

Dict注解有两种使用方式:

1、isLocal = true,默认的方式,这种方式不用调用字典服务发起rpc请求,它通过解析localDictVal字段来做字典的翻译,格式见注释,一般情况下这种方式应该更常用;

2、isLocal = false,这种方式需要指定onlineDictCode,会调用字典服务发起rpc请求,获取字典服务那边的配置信息;

真正使用转换上也有两种使用方式:

1、使用DictCovert注解,在需要对结果对象做字典转换的接口或方法上添加该注解

2、使用DictUtil#parseResult 方法手动进行字典转换

详细步骤

通过注解作为aop切入点的方式:

1、在VO里需要做字典转换的字段添加@Dict,并新增用来存储的字段,如下面source和abc字段,source使用的是本地字典,abc使用的是在线字典

@Data

@ApiModel("xxx对象")

public class XxxVO {

    private String id;

    private String xxxName;

    @Dict(localDictVal = "1:男;2:女")

    private Integer sex;

    private String sexText;

    @Dict(isLocal = false,onlineDictCode = "SEX")

    private Integer oSex=1;

    private String oSexText;

}

2、在control接口处添加@DictCovert

   @PostMapping("/page")

   @DictCovert

   public PageResult<XxxVO> page(@RequestBody @Valid XxQO qo) {

       return XxxManager.listByPage(qo);

   }

使用工具方法的方式:

直接调用DictUtil#parseResult 该方法即可

注意

目前只对返回结果是java.util.List、PageResult、普通的VO类这些类型才可以做字典转换,并且不会做深度的字段转换,例如一个对象A里面的属性又是一个对象B,B里面使用Dict注解是识别不到的,这种可以在获取对象B的时候去做这个转换动作,而不是在外层的对象A上去做。

如需新增其他类型的转换可以在DictUtil#parseResult 里面添加

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/194821.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

有没有人像我一样每次面对sql都很痛苦

今天又操作了一遍sql&#xff0c;这世界上有没有人像我一样&#xff0c;每一次面对mysql都是一次痛苦的经历。 不知道别人怎么想&#xff0c;反正我是这样的。使用mysql从来没有让我快乐过。在数据库里面&#xff0c;最喜欢mongo&#xff0c;当然我也只会mongo。但是每一次使用…

NL-meals、BM3D

常用的高斯滤波或者均值滤波相对都比较简单&#xff0c;即每个窗口的滤波核都是一样的。稍微复杂一些保边滤波如&#xff0c;双边滤波和导向图滤波等。这里介绍几种ffmpeg里面包含的相对比较复杂的滤波算法。计算量不可谓不小。可以通过ffmpeg查看源码实现过程&#xff0c;这里…

RHCE(防火墙)

文章目录一、什么是防火墙二、iptables三、firewalld四、作业一、什么是防火墙 防火墙&#xff1a;防火墙是位于内部网络和外部网络之间的屏障&#xff0c;它按照系统管理员预先定义的规则来控制数据包的进出 防火墙可以分为硬件防火墙和软件防火墙。硬件防火墙是由厂商设计好的…

LongAdder/LongAccumulator类分析

一、LongAdder简介 1.下图是JDK-API文档的的截图信息 我们可以得知这两个类都是1.8开始提供的&#xff0c;并且都具有顶级的并发性。这两类的区别点主要在于LongAdder初始值为0&#xff0c;只能做累加操作&#xff0c;而LongAccumulator可以完成一些复杂的计算&#xff0c;本…

软件设计师考试整理-0-前言

1. 整理初衷 2022年下半年的软考成绩出来了&#xff0c;查了一下&#xff0c;上午58分&#xff0c;下午61分&#xff0c;虽然不高&#xff0c;但是也过了&#xff0c;还是值得开心的&#xff0c;毕竟在最初报考的时候&#xff0c;会的知识点寥寥无几。 先解释下为什么我会报考…

富勒烯C60,131159-39-2,水溶性富勒烯,CARBON C60

产品描述&#xff1a;富勒烯C60与金刚石、石墨是碳的三种同素异形体&#xff0c;富勒烯分子是一种由60个碳原子结合形成的稳定分子&#xff0c;它具有60个顶点和32个面&#xff0c;其中12个为正五边形&#xff0c;20个为正六边形&#xff0c;它形似足球&#xff0c;所以又称为富…

OpenMMLab AI实战营Day2 图像分类

目录 一、图像分类 二、卷积神经网络 三、超越ResNet的图像分类模型 1、神经结构搜索 2、Transformer 3、ConvNext 四、轻量化神经网络 五、Vision Transformer 六、模型学习 七、学习率与优化器调整策略 八、数据增强 一、图像分类 图像分类&#xff1a;识别图像中…

网络骗局丨典型案例分析,大家一起来避雷!

作者&#xff1a;黑蛋因为疫情的困扰&#xff0c;总体经济都不是很好&#xff0c;春节前后&#xff0c;网络诈骗高发期&#xff0c;以下是几种典型案例&#xff0c;一起来看看。比较常见的有以下几种&#xff1a;1、网络购物骗局小红在某平台网购一件产品&#xff0c;几天后&am…

Nacos+Springcloud+mybatis-plus+oracle的整合

NacosSpringcloudmybatis-plusoracle的整合 1、项目结构 2、父类的依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance&q…

shell脚本基本使用

目录 1.是什么&#xff1f; 2.写法 3.shell脚本语法 3.1第一个shell脚本 3.2交互式shell脚本 3.3shell脚本的数值计算 3.4test命令 3.5中括号【】判断符 3.6默认变量 4.shell脚本条件判断 4.1 if fi 4.2 if then else 4.3 if elif else 4.4 case 5.shell脚本函数…

Linux 学习笔记 正则、管道、进程与任务定时

一、筛选与搜索 1.1 grep 命令 筛选数据 grep 是 Globally search a regular expression and print 的缩写。意思是全局搜索一个正则表达式&#xff0c;并且打印。 考虑这样的一个名为 a.txt 的文件&#xff1a; 执行 grep apple ./a.txt 即可匹配所有含有 apple 的字符 默认…

IDEA搭建Finchley.SR2版本的SpringCloud父子基础项目-------zuul和SpringConfig

1.Zuul是什么 Zuul包含了对请求的路由和过滤两个最主要的功能&#xff1a; 其中路由功能负责将外部请求转发到具体的微服务实例上&#xff0c;是实现外部访问统一入口的基础而过滤器功能则负责对请求的处理过程进行干预&#xff0c;是实现请求校验、服务聚合等功能的基础. Zu…

4款实用的办公软件,每一款都让你效率翻倍,相见不易

称心的电脑使用体验&#xff0c;总离不开实用的工具&#xff0c;一款高效的工具&#xff0c;能让你的办公体验翻倍。 1、Mem Reduct 这是一款强大到离谱的电脑清理工具&#xff0c;比某安全卫士实用多&#xff0c;没有任何弹屏广告&#xff0c;完全免费使用&#xff0c;内存清理…

搜索算法基础

一 DFS 深度优先搜索算法&#xff08;Depth First Search&#xff0c;简称DFS&#xff09;&#xff1a;一种用于遍历或搜索树或图的算法。 沿着树的深度遍历树的节点&#xff0c;尽可能深的搜索树的分支。当节点v的所在边都己被探寻过或者在搜寻时结点不满足条件&#xff0c;搜…

uboot的烧写及使用

目录 一、uboot概述 Bootloader Bootloader基本功能 常见的Bootloader 二、SD卡启动盘制作 三、uboot的使用 3.1uboot模式 3.2uboot帮助命令 3.3uboot环境变量命令 3.4常用环境变量 3.5网络传输命令 3.6u-boot访问存储器命令 3.7 u-boot自启动环境变量&#xff08…

人工智能趋势——2023 年综述

随着DALLE 2 于 2022 年 4 月的宣布&#xff0c;关于2022 年初第三个 AI 冬天——或 AI 撞墙——的预言过时得很快而且效果不佳&#xff0c;随后出现了更多主要由扩散模型驱动的文本到图像应用程序&#xff0c;这是一个非常多产的领域用于计算机视觉研究及其他领域。AI 的 2022…

SpringCloud五大核心组件

Consul 等&#xff0c;提供了搭建分布式系统及微服务常用的工具&#xff0c;如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性token、全局锁、选主、分布式会话和集群状态等&#xff0c;满足了构建微服务所需的所有解决方案。 服务发现——Netflix Eureka …

六百亿流量、25万出场费,袁树雄身价是《汉川》杨语莲125倍

在文章开始之前&#xff0c;咱们不妨先看两组数据&#xff0c;第一组数据是六百个亿&#xff0c;第二组数据则是二十五万。乍一看并没有什么特别&#xff0c;但是要把这两组数据&#xff0c;和一名音乐人联系起来的话&#xff0c;就会产生非常神奇的效果。 《早安隆回》的创作者…

[oeasy]python0070_ 字体样式_下划线_中划线_闪动效果_反相_取消效果

字体样式 回忆上次内容 m 可以改变字体样式 0-10 之间设置的都是字体效果 0 复原1 变亮2 变暗 从3到10 又是什么效果 呢&#xff1f;&#xff1f;真的可以blink闪烁吗&#xff1f;&#x1f441; 3m 3m 实现斜体字的效果 4m 4m 对应着下划线 控制范围 通过控制字符串的位置…

DNS服务解析与原理笔记

引言DNS介绍DNS原理与解析DNS查询DNS服务搭建DNS劫持和污染计算机面试 or 真题DNS应用扩展参考与推荐引言 我想很多人遇到过这样一种情况&#xff0c;电脑突然上不了网了&#xff0c;或者说可以登陆QQ&#xff0c;但是进不了网页&#xff0c;任何网页都会出现如下的类似截图&a…