0. 已做全新升级版
链接:【SpringBoot】自定义注解终极升级版<i18n国际化>方案源码Copy
链接:【SpringBoot】自定义注解终极升级版<i18n国际化>方案源码Copy
链接:【SpringBoot】自定义注解终极升级版<i18n国际化>方案源码Copy
重要的事情三遍
注解接口 I18n
package annotation; import java.lang.annotation.*; @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface I18n { /** * 在方法的参数列表中检索是否包含此参数名才进行转换,如自定义需要进行指定 * 在参数值返回的是国际化前缀字段 映射 到主字段,如 englishName --> name * @return 参数名 */ String language() default "language"; } 切面实现类 I18nAspect
package aspect; import cn.iocoder.yudao.module.nmkj.annotation.I18n; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import java.lang.reflect.Field; import java.util.*; @Aspect @Component /* * 请确保被国际化实体或被包含国际化实体的类型修饰为 Object 类型 或内置 集合类型 的顶层类型 修饰的,而不是具体自定义类型 * 搜索并国际化实例与子实例以此类推所有的国际化前缀映射到主字段 */ public class I18nAspect { // 方法级别的切点 @Pointcut("@annotation(i18n)") public void annotatedWithTestAnnotation(I18n i18n) {} // 类级别的切点 @Pointcut("@within(i18n)") public void withinTestAnnotation(I18n i18n) {} // 组合切点,处理两个条件的逻辑 (有 BUG ,null 指针注解实例问题) // @Pointcut("(execution(* *(..)) && @annotation(testAnnotation)) || @within(testAnnotation)") // @Pointcut(value = "annotatedWithTestAnnotation(testAnnotation) || withinTestAnnotation(testAnnotation)", argNames = "testAnnotation") // public void testAnnotationPointcut(TestAnnotation testAnnotation) {} @Around(value = "withinTestAnnotation(i18n)", argNames = "proceedingJoinPoint, i18n") public Object aroundAdviceClass(ProceedingJoinPoint proceedingJoinPoint, I18n i18n) throws Throwable { return aroundAdvice(proceedingJoinPoint, i18n); } @Around(value = "annotatedWithTestAnnotation(i18n)", argNames = "proceedingJoinPoint, i18n") public Object aroundAdviceMethod(ProceedingJoinPoint proceedingJoinPoint, I18n i18n) throws Throwable { return aroundAdvice(proceedingJoinPoint, i18n); } private Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint, I18n i18n) throws Throwable { // Before // System.out.println("========== AroundAdvice Before Advice =========="); // System.out.println("当前执行类全限定名: "+ proceedingJoinPoint.getTarget().getClass().getName()); // System.out.println("当前执行类: "+ proceedingJoinPoint.getTarget().getClass().getSimpleName()); // System.out.println("方法名: "+ proceedingJoinPoint.getSignature().getName()); //获取方法传入参数列表 MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature(); String[] parameterNames = methodSignature.getParameterNames(); Object[] args = proceedingJoinPoint.getArgs(); // System.out.println("方法传入参数列表: " + Arrays.toString(args) + " length: " + args.length); //拿到指定名字前缀语言字段参数 String lang = null; for (int i = 0; i < parameterNames.length; i++) { if (parameterNames[i].equals(i18n.language()) && args[i] instanceof String) { // 找到指定的语言前缀参数名 // System.out.println("获得语言参数: " + testAnnotation.lang() + " ---> " + args[i]); lang = (String) args[i]; break; } } // Method Running // System.out.println("========== Around Advice Method Running ==========="); Object proceed = proceedingJoinPoint.proceed(args); //对返回结果进行转换 if (lang != null) { //当参数列表有 lang 字段名时才执行转换 BFSTargetField(proceed, lang); } // After // System.out.println("========== Around Advice After End ==========="); return proceed; } private void BFSTargetField(Object object, String startsWithFieldName) throws IllegalAccessException { if (object == null) { return; } Queue 0. 假设实体类与接口地址
实体类:
import lombok.Data; import org.springframework.stereotype.Component; @Data @Component public class TestVO { private Long id; private String name; private String englishName; private Object testVO; } 接口地址:
import cn.CommonResult; import annotation.I18n; import aspect.TestVO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @Tag(name = "测试接口 - Admin - Test") @RestController @RequestMapping("/nmkj/test") @Validated @I18n(language = "lang") public class AdminTestController { @GetMapping("/get-simple") @Operation(summary = "获取 test 信息 simple") public CommonResult get(String lang){ TestVO testVO = new TestVO(); testVO.setName("很好"); testVO.setEnglishName(lang); TestVO testVO2 = new TestVO(); testVO2.setName("实体内"); testVO2.setEnglishName(lang); testVO.setTestVO(testVO2); return success(testVO); } } 1. 约定规则描述
2. 使用效果接口测试
不发送前缀字段:

发送前缀字段:

3. 效率问题
PS:虽然说我已经优化了递归变成 队列的迭代方式,但是它依然会深层次的进行搜索,不过层次也因加特判效果效率明显提升,但如果返回的结果深层嵌套,那效率绝对会降低,不过也可能只是第一次,可以用 Redis 进行优化查询即可。
优势:相对于自定义映射,可以简化超多代码。
如果有代码问题,欢迎指正,谢谢各位大佬!
方案二:使用接口约束自定义实体的映射
PS:用于实体类自定义实现国际化接口。
public interface I18nInterface { void english(); } PS:用于约定管理类实现的方法,也可以省略掉,直接写类。
import java.util.List; import java.util.Map; public interface I18nManagerInterface { void i18n(List extends I18nInterface> list, String i18n); void i18n(I18nInterface obj, String i18n); void iteration(List extends I18nInterface> list, String i18n); void map(Map PS:用于直接操作实体类进行国际化。
import org.springframework.stereotype.Component; import java.util.List; import java.util.Map; @Component public class I18nManager implements I18nManagerInterface { public static final String I18N_ENGLISH = "english"; @Override public void i18n(List extends I18nInterface> list, String i18n) { if (i18n == null) return; switch (i18n) { case I18N_ENGLISH: iteration(list, i18n); break; } } @Override public void i18n(I18nInterface obj, String i18n) { if (i18n == null) return; switch (i18n) { case I18N_ENGLISH: obj.english(); break; } } @Override public void iteration(List extends I18nInterface> list, String i18n) { if (i18n == null) return; for (I18nInterface obj : list) { i18n(obj, i18n); } } @Override public void map(Map 0. 效果展示--假设接口 & 实体实现类 I18nInterface 接口
实体类:
@Data public class CarouselDO extends BaseDO implements I18nInterface { /** * 轮播ID */ private Long id; /** * 轮播地址 */ private String img; /** * 排序权值 */ private Long sort; /** * 轮播权重 */ private Integer status; /** * 内容1 */ private String content1; /** * 内容2 */ private String content2; /** * 内容3 */ private String content3; /** * 国际化内容1 */ private String englishContent1; /** * 国际化内容2 */ private String englishContent2; /** * 国际化内容3 */ private String englishContent3; @Override public void english() { //实现直接赋值 this.content1 = this.englishContent1; this.content2 = this.englishContent2; this.content3 = this.englishContent3; } } 接口:
@Tag(name = "用户APP - 首页轮播") @RestController @RequestMapping("/nmkj/carousel") @Validated public class AppCarouselController { @Resource private CarouselService carouselService; @Resource private I18nManager i18nManager; // 国际化管理类 @GetMapping("/list") @Operation(summary = "获得首页轮播列表") // @I18n public CommonResult> getCarouselList(@Valid CarouselPageReqVO reqVO, String language) { reqVO.setStatus(1); //用户端请求的数据必须为启用状态 List lsitResult = carouselService.getCarouselList(reqVO); i18nManager.i18n(lsitResult, language); // 直接传递应用的语言参数与集合的 VO国际化实现类 return success(BeanUtils.toBean(lsitResult, AppCarouseIRespVO.class)); } }
1. 效果展示--接口测试
数据库部分字段数据:

不发送国际化对应字段:

发送国际化对应字段:

如果有代码问题,欢迎指正,谢谢各位大佬!