国际化多语言配置。
相较于常规的方法,这次采取了切面的方式,来完成所有字段->不同语言的映射。
按常规的国际化,写一个获取语言的方法:getMsg(String code);
写一个深层遍历对象的方法traverseObject(Object obj),通过反射,获取所有类型为字符串的字段,并实现对字段的重新赋值;(此处需要额外处理对象内部有List和Map的情况)
以上,就完成了国际化的基础:对某个对象进行遍历,获取其字符串属性的值,检查配置文件中是否有该值对应的国际化语言,有的话就重新赋值
eg. * 配置文件中: xxModule.name = 名字 * 业务逻辑层中: obj.setName("xxModule.name") * 然后通过遍历对象traverseObject(obj),获取到obj.name,然后检查配置文件中是否有对应的语言,getMsg(obj.name),有的话对该属性重新赋值
AOP切面的配置: 3.1 定义一个注解 3.2 定义一个切面,写一个@AfterReturning的方法,并且限定该方法只对上面定义的注解生效 3.3 @AfterReturning的方法中写入步骤1和步骤2的逻辑实现.
把该注解加在需要做国际化的controller方法上
这样就完成了切面+国际化的配置,有时候觉得不那么靠谱,可能是我没想到更好的处理方法,大家谨慎参考。
支持的几种返回值类型示例: 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就可以。
不支持的类型示例: String: "name" (这是因为递归遍历的方法是void,没有return的内容,而String类型的属性不能修改其属性值,大家可以根据业务情况重新写)
-- messages.properties -- messages_en_US.properties -- messages_zh_CN.properties
(框出来的是自动生成的,不需要手动建立)
请在.properties文件右下角检查文件的编码是否是utf-8
spring: messages: encoding: utf-8 basename: i18n.messages
创建文件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就完成了。
今日报告-66
设置Windows10暂停更新3000天
AQS公平锁的流程
AMD锐龙7 7800X3D网游专项测试:竟比i9-13900KS强了15%
常用总线技术基本参数对比
探索图像数据中的隐藏信息:语义实体识别和关系抽取的奇妙之旅
设置Chrome浏览器自动升级
JavaScript – 小技巧 Tips
Winform无边框窗体实现拖动
STM32基础(一)
技术人的修炼---九五小庞
vue自定义事件用法及$emit
ODOO 科目配置4
sqlite 触发器 c#
postgresql在插入数据后怎么获取自增id
EF Core 的基本使用
error: failed to push some refs to 'https://github.com/*******/********.github.io.git'
欧拉降幂
编程语言能力对比
基于机器视觉的小车轨迹控制软件界面展示
Fiddler抓包Android7以内版本
SD NAND 与 SPI NAND
C语言中的编译过程详解
step7 V5.x上的SCL
yolo --- 核心思想
【游记】CCPC 济南 2024 游记
AJAX & AXIOS-2024/11/1
验证码处理在自动化测试中的应用
一些学科笑话
NOIP2024模拟赛20 & 11.1 小记
20241101 数据结构与算法期中机试收获
Java,启动!
什么是IT技术
即将到来!
2024/11/1日 日志 关于JavaScript简介&引入方式 以及基础语法的学习
舍得-时间-工作是人的一生最重要的事情-自己要有私房钱-人的一生最重要的事情是书写自己的人生
2.TiUP 部署 DM 集群
原型模式的C++实现
python bytecode解析
09-XSS键盘监听、cookie窃取&文件上传绕过
Ethernaut Level 11: Elevator Attack and Blockchain Interaction
快速部署开源spug运维平台的Docker安装指南
驱动调试之printk的原理与使用
计算机思维模型及其应用
华为云发布代码大模型PanGu-Coder2,实现高效代码生成
Linux多硬盘数据存储和分区操作
构建高可用架构: 分层冗余与自动故障转移
LoRA:高效调参的大语言模型适应方法
《分布式系统的基本原理及互联网分层架构的本质》
Hadoop写流程解析
Java架构师的系统架构设计方法论中的规范要点
使用observeDOM解决BetterScroll插件在移动端无法滑动的问题
互联网一致性架构设计实践
高并发系统架构与水平扩展
混合应用的崛起:跨平台开发取代原生应用