首页 > 编程语言 >【Java】i18n国际化解决方案:通过AOP切面实现多语言的配置

【Java】i18n国际化解决方案:通过AOP切面实现多语言的配置

时间:2024-03-18 14:14:56浏览次数:28  
标签:Object Java String object 遍历 AOP i18n obj name

需求背景

国际化多语言配置。

相较于常规的方法,这次采取了切面的方式,来完成所有字段->不同语言的映射。

大致逻辑:
  1. 按常规的国际化,写一个获取语言的方法:getMsg(String code);

  2. 写一个深层遍历对象的方法traverseObject(Object obj),通过反射,获取所有类型为字符串的字段,并实现对字段的重新赋值;(此处需要额外处理对象内部有List和Map的情况)

    以上,就完成了国际化的基础:对某个对象进行遍历,获取其字符串属性的值,检查配置文件中是否有该值对应的国际化语言,有的话就重新赋值

    eg.
    * 配置文件中: xxModule.name = 名字
    * 业务逻辑层中: obj.setName("xxModule.name")
    * 然后通过遍历对象traverseObject(obj),获取到obj.name,然后检查配置文件中是否有对应的语言,getMsg(obj.name),有的话对该属性重新赋值

  3. AOP切面的配置:
    3.1 定义一个注解
    3.2 定义一个切面,写一个@AfterReturning的方法,并且限定该方法只对上面定义的注解生效
    3.3 @AfterReturning的方法中写入步骤1和步骤2的逻辑实现.

  4. 把该注解加在需要做国际化的controller方法上

    这样就完成了切面+国际化的配置,有时候觉得不那么靠谱,可能是我没想到更好的处理方法,大家谨慎参考。

!!!支持的场景有限:支持list,map,object(直接的object对象或者自定义class的实例,不支持单纯的string类型。

支持的几种返回值类型示例:
List: ["aaa","bbb","ccc"]
List: ["aaa","bbb",{name:"111",gender:"男性"}]
Map<String,Object>: {name:"aaa",sports:{type:1,name:"football"}},
Object,或者自定义类的实例:{type:1,name:"football"}

不支持的类型示例:
String: "name"
(这是因为递归遍历的方法是void,没有return的内容,而String类型的属性不能修改其属性值,大家可以根据业务情况重新写)

代码实现

1. 国际化基础配置
1.1 resources目录下新建目录i18n,然后在i18n中新建三个properties文件:

-- messages.properties
-- messages_en_US.properties
-- messages_zh_CN.properties

(框出来的是自动生成的,不需要手动建立)

请在.properties文件右下角检查文件的编码是否是utf-8

1.2 在application.yaml文件中添加spring配置:

spring:
messages:
encoding: utf-8
basename: i18n.messages

2. 国际化工具方法编写

创建文件i18nUtils.java

@Slf4j
public class I18nUtils {

    private static final MessageSource MESSAGE_SOURCE = SpringUtil.getBean(MessageSource.class);

    /**
     * 通过code获取对应语言
     *
     * @param code 配置文件中的key
     * @return 对应的语言 如果不存在则直接返回code
     */
    public static String get(String code) {
        try {
            return MESSAGE_SOURCE.getMessage(code, null, LocaleContextHolder.getLocale());
        } catch (Exception e) {
            return code;
        }
    }

    /**
     * 这个方法看起来没什么用......
     * 实际上用处也不是很大......
     * 主要用来log国际化操作耗费的时间和做一些其他的处理
     */
    public static void transformMsg(Object obj) {
        traverseObjectForLanguage(obj);
    }

    /**
     * 深层遍历对象
     */
    public static void traverseObjectForLanguage(Object object) {

        // 空对象处理:直接返回
        if (object == null) {
            return;
        }

        Class<?> clazz = object.getClass();

        String name = clazz.getName();

        // 如果第一次传来的object是个String
        if ("java.lang.String".equals(name)) {
            // String类型不能重新赋值 跳过
            System.out.printf("String 类型不能重新赋值,跳过");
            return;
        }

        // 基本类型的处理:如果对象是lang包下,又不直接属于Object类型,直接返回,主要为了绕过基本类型
        if (name.contains("java.lang") && !"java.lang.Object".equals(name)) {
            return;
        }

        // Map的处理:继续遍历map的value对象
        if (object instanceof Map) {
            for (String key : ((Map<String, Object>) object).keySet()) {
                Object mapValue = ((Map<String, Object>) object).get(key);
                // Map中value为String的处理
                if (mapValue instanceof String) {
                    ((Map<String, Object>) object).put(key, get(mapValue.toString()));
                } else {
                    // 否则继续进入下一轮遍历
                    traverseObjectForLanguage(mapValue);
                }
            }
        }

        // List的处理:继续遍历list内的值
        if (object instanceof List) {
            for (Object obj : (List) object) {
                if (obj instanceof String) {
                    // list里面有string类型的值 进行替换
                    int index = ((List<Object>) object).indexOf(obj);
                    ((List<Object>) object).set(index, get(obj.toString()));
                } else {
                    // 继续下一轮遍历
                    traverseObjectForLanguage(obj);
                }
            }
        }

        // Object类型或自定义类的实例的处理
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {

            // 不操作static 或者 final修饰的属性
            Boolean isStatic = Modifier.isStatic(field.getModifiers());
            Boolean isFinal = Modifier.isFinal(field.getModifiers());

            if (isStatic || isFinal) {
                continue;
            }

            // 开放属性的访问权限
            Boolean isAccess = field.isAccessible();
            field.setAccessible(true);

            try {
                Object value = field.get(object);
                if (value instanceof String) {
                    // check if string value exists in i18n config
                    String result = get((String) value);
                    field.set(object, result);
                } else {
                    // 字段值为内部对象
                    traverseObjectForLanguage(value);
                }

                // 还原属性的访问权限
                field.setAccessible(isAccess);
            } catch (Exception e) {
                throw new BusinessException("traverse object error:pls check the i18n annotation methods", ResultCode.UNKNOWN_ERROR);
            }
        }
    }
}

创建文件I18nAspectUtils.java,用来实现切面

@Aspect
@Component
public class I18nAspectUtils {
    @AfterReturning(returning = "object", value = "@annotation(I18nAspectConfig)")
    public void doAfterReturning(Object object) {
        I18nUtils.transformMsg(object);
    }
}

创建文件I18nAspectConfig,用来实现注解

public @interface I18nAspectConfig {

}

到这里,注解+AOP就完成了。

运用:在controller方法上添加注解@I18nAspectConfig就可以。

标签:Object,Java,String,object,遍历,AOP,i18n,obj,name
From: https://www.cnblogs.com/northwest332/p/18079981

相关文章

  • Java面试问题集合,Java面试题合集
    前言:说到算法,相信每一个程序员和接触过程序员的朋友都不会陌生,直到现在算法一直占着面试必问的地位,而算法面试也仍是当前最适合公司筛选程序员的方法之一,在阿里,字节跳动、华为等公司带动下,无论是求职者还是面试官,都逐渐认识到算法面试其实是相对高效、准确且公平的筛选机制......
  • 后端程序员学JavaWeb必备小知识
    小知识1.判断前端或后端的关键在于内容由哪个解析对象来解析。如果是由浏览器解析,那就是前端;如果是由后台的JDK/Tomcat/Nginx等来解析和执行,那就是后端。2.后端成员掌握前端程度:(1)基本使用,能看懂,能维护即:具备基本的前端知识,包括HTML、CSS和JavaScript的基础概念、语法......
  • 我的腾讯Java面试经历分享,Java常见笔试题目
    珍藏版(1)——Mybatis入门1.什么是MyBatis2.为什么我们要用Mybatis?3.Mybatis快速入门3.1导入开发包3.2准备测试工作3.3创建mybatis配置文件3.4编写工具类测试是否获取到连接3.5创建实体与映射关系文件3.6编写DAO4.Mybatis工作流程5.完成CRUD操作5.1......
  • [Java、Android面试]_08_强软弱虚四种引用及应用场景
    本人今年参加了很多面试,也有幸拿到了一些大厂的offer,整理了众多面试资料,后续还会分享众多面试资料。整理成了面试系列,由于时间有限,每天整理一点,后续会陆续分享出来,感兴趣的朋友可关注+收藏文章目录1.强引用(默认的引用形式)2.软引用3.弱引用4.虚引用引用在java中......
  • Java_Idea打jar包
    1.在使用Maven的时候,如果我们要依赖一个本地的jar包的时候,01.resources目录下创建一个lib文件夹(也可以创建到根目录下),将外部的jar包复制拷贝到lib下面02.pom文件 通常都会使用<scope>system</scope>和<systemPath></systemPath>来处理需要在maven插接中配置一个includeSyst......
  • java八股——mysql数据库
    上一篇传送门:点我JVM是java面试八股中的一个重难点,本文仅是部分问题,SQL语句、主从复制以及数据库锁等知识点还未涉及,后续会进行修改补充。数据库三大范式是什么?第一范式:每个字段都不可以再被拆分;第二范式:在第一范式的基础上,有主键,并且主键之外的其他字段完全依赖于主键,......
  • 还在用Calendar操作Date?Java8都弃用了,还不知道它的这款强大的工具吗?
    引言在过去的Java版本中,日期和时间的处理主要依赖于java.util.Date和java.util.Calendar类,然而随着业务系统的复杂以及技术层面的提升,这些传统的日期时间类暴露出了若干显著的不足之处。随着Java8的发布,其引入了一套全新的日期时间API,彻底改变了我们处理日期和时间的方式。传统......
  • 华为OD机试Java - 机器人搬砖
    机器人搬砖前言:本专栏将持续更新互联网大厂机试真题,并进行详细的分析与解答,包含完整的代码实现,希望可以帮助到正在努力的你。关于大厂机试流程、面经、面试指导等,如有任何疑问,欢迎联系我,wechat:steven_moda;email:[email protected];备注:CSDN。题目描述机器人搬砖,一共有N......
  • 华为OD机试Java - 转盘寿司
    转盘寿司前言:本专栏将持续更新互联网大厂机试真题,并进行详细的分析与解答,包含完整的代码实现,希望可以帮助到正在努力的你。关于大厂机试流程、面经、面试指导等,如有任何疑问,欢迎联系我,wechat:steven_moda;email:[email protected];备注:CSDN。题目描述寿司店周年庆,正在举办......
  • 华为OD机试Java - 分月饼
    分月饼前言:本专栏将持续更新互联网大厂机试真题,并进行详细的分析与解答,包含完整的代码实现,希望可以帮助到正在努力的你。关于大厂机试流程、面经、面试指导等,如有任何疑问,欢迎联系我,wechat:steven_moda;email:[email protected];备注:CSDN。题目描述中秋节,公司分月饼,m个员......