【开源项目】Easy-Trans数据翻译服务的快速入门及原理解析

news2025/1/15 23:04:09

项目介绍

easy-trans是一款用于做数据翻译的代码辅助插件,利用mybatis plus/jpa/beetsql 等ORM框架的能力自动查表,让开发者可以快速的把id/字典码 翻译为前端需要展示的数据。

快速入门

maven依赖

    <properties> 
        <fhs.release.version>2.2.1-M1</fhs.release.version>
    </properties>        
		<dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fhs-opensource</groupId>
            <artifactId>easy-trans-spring-boot-starter</artifactId>
            <version>${fhs.release.version}</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>com.fhs-opensource</groupId>
            <artifactId>easy-trans-mybatis-plus-extend</artifactId>
            <version>${fhs.release.version}</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

application.yml

spring:
  #数据库配置
  datasource:
      url: jdbc:mysql://127.0.0.1:3306/charles?useUnicode=true&useSSL=false&characterEncoding=utf-8
      username: root
      password: root
      driver-class-name: com.mysql.jdbc.Driver

easy-trans:
  #启用redis缓存 如果不用redis请设置为false
  is-enable-redis: true
  #启用全局翻译(拦截所有responseBody进行自动翻译),如果关闭需要手动调用翻译方法或者方法加注解,具体看文档
  is-enable-global: true
  #启用平铺模式
  is-enable-tile: false
  #字典缓存放到redis 微服务模式请开启
  dict-use-redis: true
  # ruoyi相关的请开启
  is-enable-map-result: true

定义实体类

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class StudentVo implements TransPojo {

    private Long id;

    @Trans(type = TransType.DICTIONARY, key = "sex")
    private Integer sex;

    @Trans(type = TransType.DICTIONARY, key = "disable", ref = "disableName")
    private boolean disable;


    private String disableName;
}

测试效果

@RestController
public class TestController implements InitializingBean {

    @Autowired
    private DictionaryTransService dictionaryTransService;

    @RequestMapping("/t1")
    public StudentVo t1() {
        return StudentVo.builder().sex(1).disable(false).build();
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        //字典国际化支持
        Map<String, String> transMap = new HashMap<>();
        // 如果不要国际化则是  ransMap.put("0","男");  transMap.put("1","女");
        transMap.put("0", "男");
        transMap.put("1", "女");
        transMap.put("2", "TS");
        dictionaryTransService.makeUseRedis();
        dictionaryTransService.refreshCache("sex", transMap);
        transMap = new HashMap<>();
        transMap.put("true", "禁用");
        transMap.put("false", "启用");
        dictionaryTransService.refreshCache("disable", transMap);
        transMap = new HashMap<>();
        // 如果不要国际化则是  ransMap.put("0","男");  transMap.put("1","女");
        transMap.put("610100", "西安");
        dictionaryTransService.refreshCache("address", transMap);
    }
}

不选用平铺(is-enable-tile=false):{"id":null,"sex":1,"disable":false,"disableName":"启用","transMap":{"sexName":"女"}}

选用平铺(is-enable-tile=true):{"id":null,"sex":1,"disable":false,"disableName":"启用","sexName":"女"}

原理解析

切面拦截

TransMethodResultAop拦截处理带有TransMethodResult注解的方法。

@Slf4j
@Aspect
public class TransMethodResultAop implements InitializingBean {

    /**
     * 开启平铺模式
     */
    @Value("${easy-trans.is-enable-tile:false}")
    private Boolean isEnableTile;


    /**
     * 支持vo包装类是map
     */
    @Value("${easy-trans.is-enable-map-result:false}")
    private Boolean isEnableMapResult;

    @Autowired
    private TransService transService;

    @Around("@annotation(com.fhs.core.trans.anno.TransMethodResult)")
    public Object transResult(ProceedingJoinPoint joinPoint) throws Throwable {
        Object proceed = null;
        Set<String> includeFields = null;
        Set<String> excludeFields = null;
        try {
            proceed = joinPoint.proceed();
            //1.获取用户行为日志(ip,username,operation,method,params,time,createdTime)
            //获取类的字节码对象,通过字节码对象获取方法信息
            Class<?> targetCls = joinPoint.getTarget().getClass();
            //获取方法签名(通过此签名获取目标方法信息)
            MethodSignature ms = (MethodSignature) joinPoint.getSignature();
            //获取目标方法上的注解指定的操作名称
            Method targetMethod =
                    targetCls.getDeclaredMethod(
                            ms.getName(),
                            ms.getParameterTypes());
            if (targetMethod.isAnnotationPresent(TransSett.class)) {
                TransSett transSett = targetMethod.getAnnotation(TransSett.class);
                if (transSett.include().length != 0) {
                    includeFields = new HashSet<>(Arrays.asList(transSett.include()));
                }else{
                    excludeFields = new HashSet<>(Arrays.asList(transSett.exclude()));
                }
            }
        } catch (Throwable e) {
            throw e;
        }
        try {
            return TransUtil.transOne(proceed, transService, isEnableTile, new ArrayList<>(),includeFields,excludeFields);
        } catch (Exception e) {
            log.error("翻译错误", e);
        }
        return proceed;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        if (isEnableMapResult) {
            TransUtil.transResultMap = true;
        }
    }
}

TransService#trans,获取ClassInfo,遍历transTypeServiceMap的键,获取对应的ITransTypeService,进行翻译。

    private void trans(List<? extends VO> objList, VO obj, Set<String> includeFields, Set<String> excludeFields) {
        ClassInfo info = ClassManager.getClassInfoByName(obj != null ? obj.getClass() : ((VO)objList.get(0)).getClass());
        if (info.getTransTypes() != null) {
            Set<String> transTypes = new HashSet(Arrays.asList(info.getTransTypes()));
            List<Field> tempTransFieldList = null;
            List<Field> transFieldList = null;
            Iterator var9 = transTypeServiceMap.keySet().iterator();

            while(var9.hasNext()) {
                String type = (String)var9.next();
                if (transTypes.contains(type)) {
                    tempTransFieldList = info.getTransField(type);
                    if (includeFields != null) {
                        transFieldList = (List)tempTransFieldList.stream().filter((field) -> {
                            return includeFields.contains(field.getName());
                        }).collect(Collectors.toList());
                    } else if (excludeFields != null) {
                        transFieldList = (List)tempTransFieldList.stream().filter((field) -> {
                            return !excludeFields.contains(field.getName());
                        }).collect(Collectors.toList());
                    } else {
                        transFieldList = tempTransFieldList;
                    }

                    if (transFieldList != null && transFieldList.size() != 0) {
                        transFieldList.sort(new Comparator<Field>() {
                            public int compare(Field o1, Field o2) {
                                return ((Trans)o1.getAnnotation(Trans.class)).sort() - ((Trans)o2.getAnnotation(Trans.class)).sort();
                            }
                        });
                        ITransTypeService transTypeService = (ITransTypeService)transTypeServiceMap.get(type);
                        if (ObjectUtils.isEmpty(transTypeService)) {
                            logger.warn("没有匹配的转换器:" + type);
                        } else if (objList != null) {
                            transTypeService.transMore(objList, transFieldList);
                        } else {
                            transTypeService.transOne(obj, transFieldList);
                        }
                    }
                }
            }

        }
    }

AutoTransService#afterPropertiesSet,实现了ITransTypeService的服务在afterPropertiesSet方法中会进行注入

    public void afterPropertiesSet() throws Exception {
        TransService.registerTransType("auto", this);
        TransMessageListener.regTransRefresher("auto", this::refreshCache);
    }

TransService#registerTransType,注入翻译服务。

public class TransService {
    private static Logger logger = LoggerFactory.getLogger(TransService.class);
    private static Map<String, ITransTypeService> transTypeServiceMap = new LinkedHashMap();

    public TransService() {
    }

    public static void registerTransType(String type, ITransTypeService transTypeService) {
        transTypeServiceMap.put(type, transTypeService);
    }
}

加载类信息

ClassManager#getClassInfoByName,根据类信息加载ClassInfo

    public static ClassInfo getClassInfoByName(Class<?> clazz) {
        ClassInfo temp = (ClassInfo)CACHE.get(clazz.getName());
        ClassInfo info = null;
        if (null == temp) {
            try {
                temp = new ClassInfo(clazz);
            } catch (IllegalAccessException | InstantiationException var5) {
                LOGGER.error(clazz.getName() + "生成classinfo错误", var5);
                throw new ParamException(clazz.getName() + "生成classinfo错误");
            }

            setClassInfoByName(clazz.getName(), temp);
        }

        try {
            info = new ClassInfo();
            BeanUtils.copyProperties(info, temp);
        } catch (Exception var4) {
            var4.printStackTrace();
            LOGGER.error(var4.getMessage());
        }

        return info;
    }

ClassInfo,获取到该类的所有属性,加载TransUnTrans属性的翻译类型到transTypeSetunTransTypeSet,以及该类型对应的字段加载到transFieldMapunTransFieldMap

    public <T> ClassInfo(Class<?> clazz) throws InstantiationException, IllegalAccessException {
        this.clazz = clazz;
        this.getClazzFieldMap();
    }

    private void getClazzFieldMap() throws InstantiationException, IllegalAccessException {
        List<Field> declaredFields = ReflectUtils.getAllField(this.clazz.newInstance());
        Set<String> transTypeSet = new HashSet();
        Set<String> unTransTypeSet = new HashSet();
        int mod = false;
        Iterator var5 = declaredFields.iterator();

        while(var5.hasNext()) {
            Field field = (Field)var5.next();
            int mod = field.getModifiers();
            if (!Modifier.isStatic(mod) && !Modifier.isFinal(mod) && !Modifier.isVolatile(mod)) {
                Trans trans = (Trans)field.getAnnotation(Trans.class);
                if (trans != null) {
                    if (trans.type() == null) {
                        LOGGER.warn("类 {} 属性 [{}] type为空。", this.clazz.getName(), field.getName());
                    } else {
                        transTypeSet.add(trans.type());
                        List<Field> fieldList = (List)this.transFieldMap.get(trans.type());
                        List<Field> fieldList = fieldList != null ? fieldList : new ArrayList();
                        ((List)fieldList).add(field);
                        this.transFieldMap.put(trans.type(), fieldList);
                    }
                }

                UnTrans untrans = (UnTrans)field.getAnnotation(UnTrans.class);
                if (untrans != null) {
                    if (untrans.type() == null) {
                        LOGGER.warn("类 {} 属性 [{}] type为空。", this.clazz.getName(), field.getName());
                    } else {
                        unTransTypeSet.add(untrans.type());
                        List<Field> fieldList = (List)this.unTransFieldMap.get(untrans.type());
                        List<Field> fieldList = fieldList != null ? fieldList : new ArrayList();
                        ((List)fieldList).add(field);
                        this.unTransFieldMap.put(untrans.type(), fieldList);
                    }
                }
            }
        }

        this.transTypes = new String[transTypeSet.size()];
        this.unTransTypes = new String[unTransTypeSet.size()];
        transTypeSet.toArray(this.transTypes);
        unTransTypeSet.toArray(this.unTransTypes);
    }

字典翻译

DictionaryTransService#transOne,根据实体数据和需要解析的字段。调用bothCacheService来获取数据。属性key是字典组的名称,dicCode是字典对应的值。

    @Override
    public void transOne(VO obj, List<Field> toTransList) {
        Trans tempTrans = null;

        for (Field tempField : toTransList) {
            tempField.setAccessible(true);
            tempTrans = tempField.getAnnotation(Trans.class);
            String dicCodes = StringUtil.toString(ReflectUtils.getValue(obj, tempField.getName()));
            if(dicCodes.contains("[")){
                dicCodes = dicCodes.replace("[", "").replace("]", "");
            }
            if (dicCodes.contains(",")) {
                dicCodes = dicCodes.replace(" ", "");
            }
            String[] dicCodeArray = dicCodes.split(",");
            String key = tempTrans.key().contains("KEY_") ? StringUtil.toString(ReflectUtils.getValue(obj, tempTrans.key().replace("KEY_", ""))) : tempTrans.key();
            //sex_0/1  男 女
            List<String> dicCodeList = new ArrayList<>(1);

            for (String dicCode : dicCodeArray) {
                if (!StringUtil.isEmpty(dicCode)) {
                    dicCodeList.add(bothCacheService.get(getMapKey(key.trim(), dicCode)));
                }
            }
            String transResult = dicCodeList.size() > Constant.ZERO ? StringUtil.getStrForIn(dicCodeList, false) : "";
            if (obj.getTransMap() != null && !setRef(tempTrans, obj, transResult)) {
                obj.getTransMap().put(tempField.getName() + "Name", transResult);
            }
        }
    }

DictionaryTransService#getMapKey,如果开启了国际化,需要添加this.localeGetter.getLanguageTag(),作为构建获取缓存数据key的一部分。

    public String getMapKey(String dictGroupCode, String dictCode) {
        return this.isOpenI18n ? dictGroupCode + "_" + dictCode + "_" + this.localeGetter.getLanguageTag() : dictGroupCode + "_" + dictCode;
    }

ITransTypeService#setRef(),如果该属性上的注解Trans,配置了ref或者refs的属性,修改实体数据该字段的属性值。

    default boolean setRef(Trans trans, VO vo, String val) {
        boolean isSetRef = false;
        if (CheckUtils.isNotEmpty(trans.ref())) {
            this.setValue(vo, trans.ref(), val);
            isSetRef = true;
        }

        if (CheckUtils.isNotEmpty(trans.refs())) {
            Stream.of(trans.refs()).forEach((ref) -> {
                this.setValue(vo, ref, val);
            });
            isSetRef = true;
        }

        return isSetRef;
    }

二级缓存

BothCacheService处理数据,是优先存放到ConcurrentHashMap,其次调用redisCacheService

@Data
public class BothCacheService<T> {
    /**
     * redis key前缀
     */
    private static final String TRANS_PRE = "trans:";

    @Autowired(required = false)
    private RedisCacheService<T> redisCacheService;


    @Value("${easy-trans.dict-use-redis:false}")
    private boolean useRedis;

    /**
     * 用来放字典缓存的map
     */
    private Map<String, T> localCacheMap = new ConcurrentHashMap<>();

    /**
     * 添加缓存
     *
     * @param key       key
     * @param value     value
     * @param onlyLocal 是否只添加本地缓存
     */
    public void put(String key, T value, boolean onlyLocal) {
        if (!onlyLocal && redisCacheService != null && useRedis) {
            redisCacheService.put(TRANS_PRE + key, value);
        }
        localCacheMap.put(key, value);
    }

    /**
     * 获取本地缓存
     *
     * @param key key
     * @return value
     */
    public T get(String key) {
        if (!StringUtil.isEmpty(key)) {
            if (localCacheMap.containsKey(key)) {
                T result = localCacheMap.get(key);
                return result;
            }
            if (redisCacheService != null && useRedis) {
                T result = redisCacheService.get(TRANS_PRE + key);
                if (Objects.nonNull(result)) {
                    localCacheMap.put(key, result);
                }
                return result;
            }
            return null;
        }
        return null;
    }
}

数据库翻译

AutoTransService#getTempTransCacheMap,根据namespace和主键值,优先从缓存中获取,缓存数据获取不到,调用baseServiceMap里面的AutoTransable实现。

    private Map<String, Object> getTempTransCacheMap(String namespace, Object pkey) {
        AutoTrans autoTrans = (AutoTrans)this.transSettMap.get(namespace);
        if (this.localTransCacheMap.containsKey(namespace + "_" + pkey)) {
            return (Map)this.localTransCacheMap.get(namespace + "_" + pkey);
        } else if (((AutoTrans)this.transSettMap.get(namespace)).globalCache() && this.getFromGlobalCache(pkey, namespace, "auto") != null) {
            return this.getFromGlobalCache(pkey, namespace, "auto");
        } else if (autoTrans != null && !autoTrans.useRedis()) {
            if (autoTrans.useCache()) {
                return new HashMap();
            } else if (this.threadLocalCache.get() == null) {
                if (CheckUtils.isNullOrEmpty(pkey)) {
                    return new HashMap();
                } else {
                    VO vo = this.findById(() -> {
                        return ((AutoTransable)this.baseServiceMap.get(namespace)).selectById(pkey);
                    }, (String)null);
                    return this.createTempTransCacheMap(vo, autoTrans);
                }
            } else {
                return (Map)((Map)this.threadLocalCache.get()).get(namespace + "_" + pkey);
            }
        } else {
            Map<String, Object> redisCacheResult = (Map)this.getRedisTransCache().get(namespace + "_" + pkey);
            return (Map)(redisCacheResult != null ? redisCacheResult : new HashMap());
        }
    }

ITransTypeService#findById

    default VO findById(Callable<VO> callable, String dataSourceName) {
        if (!TransConfig.MULTIPLE_DATA_SOURCES) {
            try {
                return (VO)callable.call();
            } catch (Exception var5) {
                Logger.error("", var5);
                return null;
            }
        } else {
            CompletableFuture cf = CompletableFuture.supplyAsync(() -> {
                try {
                    if (!StringUtil.isEmpty(dataSourceName)) {
                        TransConfig.dataSourceSetter.setDataSource(dataSourceName);
                    }

                    return (VO)callable.call();
                } catch (Exception var3) {
                    Logger.error("", var3);
                    return null;
                }
            });

            try {
                return (VO)cf.get();
            } catch (InterruptedException var6) {
                Logger.error("", var6);
            } catch (ExecutionException var7) {
                Logger.error("", var7);
            }

            return null;
        }
    }

AutoTransService#init,获取容器中带有AutoTrans注解中的类,存放AutoTrans注解上的namespace和该实例bean。

    public void init(ApplicationReadyEvent event) {
        ConfigurableApplicationContext context = event.getApplicationContext();
        Map<String, Object> beans = context.getBeansWithAnnotation(AutoTrans.class);
        Iterator var4 = beans.values().iterator();

        while(var4.hasNext()) {
            Object baseService = var4.next();
            if (baseService instanceof AutoTransable) {
                AutoTrans autoTransSett = (AutoTrans)baseService.getClass().getAnnotation(AutoTrans.class);
                this.baseServiceMap.put(autoTransSett.namespace(), (AutoTransable)baseService);
                this.transSettMap.put(autoTransSett.namespace(), autoTransSett);
            }
        }

        (new Thread(() -> {
            Thread.currentThread().setName("refresh auto trans cache");
            this.refreshCache(new HashMap());
        })).start();
    }

MybatisPlus适配

EasyTransMybatisPlusConfig,Mybaits适配配置。

@Configuration
public class EasyTransMybatisPlusConfig {

    /**
     * service的包路径
     */
    @Value("${easy-trans.autotrans.package:com.*.*.service.impl}")
    private String packageNames;

    @Bean
    @ConditionalOnProperty(name = "easy-trans.is-enable-auto", havingValue = "true")
    public MybatisPlusTransableRegister mybatisPlusTransableRegister() {
        MybatisPlusTransableRegister result = new MybatisPlusTransableRegister();
        result.setPackageNames(packageNames);
        return result;
    }
}

MybatisPlusTransableRegister启动的时候,获取到包名下AutoTrans的所有类信息,注册到baseServiceMap

@Data
public class MybatisPlusTransableRegister implements ApplicationListener<ApplicationReadyEvent> {

    /**
     * service的包路径
     */
    private String packageNames;

    @Autowired
    private AutoTransService autoTransService;

    @Override
    public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
        //spring容器初始化完成之后,就会自行此方法。
        Set<Class<?>> entitySet = AutoTransService.scan(AutoTrans.class, packageNames.split(";"));
        // 遍历所有class,获取所有用@autowareYLM注释的字段
        if (entitySet != null) {
            final List<String> namespaceList = new ArrayList<>();
            for (Class<?> entity : entitySet) {
                AutoTrans autoTransSett = entity.getAnnotation(AutoTrans.class);
                if (autoTransSett.ref() == VO.class || (!autoTransSett.ref().isAnnotationPresent(TableName.class))) {
                    continue;
                }
                // 获取该类
                Object baseService = SpringContextUtil.getBeanByClass(entity);
                if ((baseService instanceof AutoTransable)) {
                    continue;
                }
                namespaceList.add(autoTransSett.namespace());
                autoTransService.regTransable(new MybatisPlusTransableAdapter(autoTransSett.ref()), autoTransSett);
            }
            new Thread(() -> {
                Thread.currentThread().setName("refresh auto trans cache");
                for (String namespace : namespaceList) {
                    autoTransService.refreshOneNamespace(namespace);
                }
            }).start();

        }
    }

}

MybatisPlusTransableAdapter根据类信息获取到对应的SqlSession。

public class MybatisPlusTransableAdapter implements AutoTransable {

    private Class<? extends VO> voClass;

    public MybatisPlusTransableAdapter(Class<? extends VO> voClass) {
        this.voClass = voClass;
    }

    @Override
    public VO selectById(Object primaryValue) {
        SqlSession sqlSession = this.sqlSession();
        VO result;
        try {
            result = (VO) sqlSession.selectOne(this.sqlStatement(SqlMethod.SELECT_BY_ID), primaryValue);
        } finally {
            this.closeSqlSession(sqlSession);
        }
        return result;
    }
    
    protected SqlSession sqlSession() {
        return SqlHelper.sqlSession(this.voClass);
    }

    protected String sqlStatement(SqlMethod sqlMethod) {
        return this.sqlStatement(sqlMethod.getMethod());
    }
}

RPC访问

RpcTransService#findById,访问请求。

http://easyTrans/easyTrans/proxy/com.fhs.test.pojo.School/findById/2?uniqueField=&targetFields=schoolName

    public VO findById(Object id, Trans tempTrans) {
        if (!this.isEnableCloud) {
            try {
                Class clazz = Class.forName(tempTrans.targetClassName());
                return this.findById(() -> {
                    return this.transDiver.findById((Serializable)id, clazz, tempTrans.uniqueField(), new HashSet(Arrays.asList(tempTrans.fields())));
                }, tempTrans.dataSource());
            } catch (ClassNotFoundException var4) {
                throw new IllegalArgumentException("类找不到:" + tempTrans.targetClassName());
            }
        } else {
            try {
                return (VO)this.restTemplate.getForObject("http://" + tempTrans.serviceName() + this.getContextPath(tempTrans) + "/easyTrans/proxy/" + tempTrans.targetClassName() + "/findById/" + id + "?uniqueField=" + tempTrans.uniqueField() + "&targetFields=" + (String)Arrays.stream(tempTrans.fields()).collect(Collectors.joining(",")), BasicVO.class, new Object[0]);
            } catch (Exception var5) {
                log.error("trans service执行RPC Trans 远程调用错误:" + tempTrans.serviceName(), var5);
                return null;
            }
        }
    }

TransProxyController#findById,RPC服务的具体处理。

    @GetMapping("/{targetClass}/findById/{id}")
    public Object findById(@PathVariable("targetClass") String targetClass, @PathVariable("id") String id, @RequestParam("uniqueField")String uniqueField, @RequestParam("targetFields")String targetFields) throws ClassNotFoundException, IllegalAccessException {
        Assert.notNull(targetClass, "targetClass 不可为空");
        Assert.notNull(targetClass, "id 不可为空");
        Serializable sid = id;
        Class fieldType = getPkeyFieldType(targetClass);
        // 如果字段类型不是String,则转换
        if (fieldType == int.class || fieldType == Integer.class) {
            sid = Integer.valueOf(id);
        } else if (fieldType == long.class || fieldType == Long.class) {
            sid = Long.valueOf(id);
        }
        Set<String> targetFieldSet = null;
        if(!StringUtil.isEmpty(targetFields) && !"null".equals(targetFields)){
            targetFieldSet = new HashSet<>(Arrays.asList(targetFields.split(",")));
        }
        VO vo = simpleTransDiver.findById(sid, (Class<? extends VO>) Class.forName(targetClass),uniqueField,targetFieldSet);
        if (vo == null) {
            return null;
        }
        return vo2BasicVO(vo);
    }

页面展示

EasyTransResponseBodyAdvice,对数据进行翻译

@Slf4j
@ControllerAdvice
@ConditionalOnProperty(name = "easy-trans.is-enable-global", havingValue = "true")
public class EasyTransResponseBodyAdvice implements ResponseBodyAdvice {

    /**
     * 开启平铺模式
     */
    @Value("${easy-trans.is-enable-tile:false}")
    private Boolean isEnableTile;

    @Autowired
    private TransService transService;

    @Override
    public boolean supports(MethodParameter methodParameter, Class aClass) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        // 如果主动指定了忽略某个方法,则不执行翻译
        if (methodParameter.getExecutable().isAnnotationPresent(IgnoreTrans.class)) {
            return o;
        }
        Set<String> includeFields = null;
        Set<String> excludeFields = null;
        if (methodParameter.getExecutable().isAnnotationPresent(TransSett.class)) {
            TransSett transSett = methodParameter.getExecutable().getAnnotation(TransSett.class);
            if (transSett.include().length != 0) {
                includeFields = new HashSet<>(Arrays.asList(transSett.include()));
            } else {
                excludeFields = new HashSet<>(Arrays.asList(transSett.exclude()));
            }
        }
        Object result = null;
        try {
            result = TransUtil.transOne(o, transService, isEnableTile, new ArrayList<>(), includeFields, excludeFields);
        } catch (Exception e) {
            log.error("翻译错误", e);
        }
        return result == null ? o : result;
    }
}

TransUtil#transOne,如果选用平铺模式,会执行TransUtil#createProxyVo方法。

    public static Object transOne(Object object, TransService transService, boolean isProxy, ArrayList<Object> hasTransObjs, Set<String> includeFields, Set<String> excludeFields) throws IllegalAccessException, InstantiationException {
        if (object == null) {
            return null;
        } else if (contains(hasTransObjs, object)) {
            return object;
        } else {
            hasTransObjs.add(object);
            boolean isVo = false;
            if (transResultMap && object instanceof Map) {
                Map tempMap = (Map)object;
                Iterator var8 = tempMap.keySet().iterator();

                while(var8.hasNext()) {
                    Object key = var8.next();
                    Object mapValue = tempMap.get(key);

                    try {
                        tempMap.put(key, transOne(mapValue, transService, isProxy, hasTransObjs, includeFields, excludeFields));
                    } catch (Exception var12) {
                    }
                }

                return object;
            } else {
                if (object instanceof VO) {
                    transService.transOne((VO)object, includeFields, excludeFields);
                    transFields(object, transService, isProxy, hasTransObjs, includeFields, excludeFields);
                    isVo = true;
                } else {
                    if (object instanceof Collection) {
                        return transBatch(object, transService, isProxy, hasTransObjs, includeFields, excludeFields);
                    }

                    if (object.getClass().getName().startsWith("java.")) {
                        return object;
                    }

                    transFields(object, transService, isProxy, hasTransObjs, includeFields, excludeFields);
                }

                return isProxy && isVo ? createProxyVo((VO)object) : object;
            }
        }
    }

TransUtil#createProxyVo,创建代理类,通过属性赋值赋值到代理类中。

    public static Object createProxyVo(VO vo) {
        if (vo != null && vo.getTransMap() != null) {
            try {
                Class clazz = genNewClass(vo);
                Object newObject = clazz.newInstance();
                if (newObject == null) {
                    return vo;
                } else {
                    BeanUtils.copyProperties(vo, newObject);
                    Iterator var3 = vo.getTransMap().keySet().iterator();

                    while(var3.hasNext()) {
                        String property = (String)var3.next();
                        ReflectUtils.setValue(newObject, property, ConverterUtils.toString(vo.getTransMap().get(property)));
                    }

                    return newObject;
                }
            } catch (Exception var5) {
                log.error("easy trans 赋值错误", var5);
                return vo;
            }
        } else {
            return vo;
        }
    }

TransUtil#genNewClass,核心就是根据ASM来生成新的代理类

    public static Class genNewClass(VO vo) {
        try {
            Class targetClass = (Class)proxyClassMap.get(vo.getClass());
            boolean isGenNewClass = true;
            if (targetClass != null && validProxyClass(vo.getTransMap().keySet(), targetClass)) {
                isGenNewClass = false;
            }

            if (isGenNewClass) {
                AnnotationDescription jacksonIgnore = Builder.ofType(JsonIgnore.class).build();
                AnnotationDescription fastJsonIgnore = Builder.ofType(JSONField.class).define("serialize", false).build();
                net.bytebuddy.dynamic.DynamicType.Builder<? extends Object> builder = (new ByteBuddy()).subclass(vo.getClass()).name(vo.getClass().getSimpleName() + "DynamicTypeBuilder" + StringUtil.getUUID()).defineMethod("getTransMap", Map.class, 1).intercept(FixedValue.nullValue()).annotateMethod(new AnnotationDescription[]{jacksonIgnore, fastJsonIgnore});

                String property;
                for(Iterator var6 = vo.getTransMap().keySet().iterator(); var6.hasNext(); builder = ((net.bytebuddy.dynamic.DynamicType.Builder)builder).defineField(property, String.class, 1)) {
                    property = (String)var6.next();
                }

                targetClass = ((net.bytebuddy.dynamic.DynamicType.Builder)builder).make().load(ClassUtils.getDefaultClassLoader(), Default.INJECTION).getLoaded();
                proxyClassMap.put(vo.getClass(), targetClass);
                proxyClassFieldMap.put(targetClass, vo.getTransMap().keySet());
            }

            return targetClass;
        } catch (Exception var8) {
            if (var8 instanceof ClassNotFoundException) {
                log.error("生成新class错误,目前不支持JDK17,请关闭平铺模式: easy-trans.is-enable-tile 设置为false");
            } else {
                log.error("生成新class错误", var8);
            }

            return null;
        }
    }

在这里插入图片描述

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

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

相关文章

痞子衡嵌入式:MCUBootUtility v5.0发布,初步支持i.MXRT1180

--   痞子衡维护的NXP-MCUBootUtility工具距离上一个大版本(v4.0.0)发布过去4个多月了&#xff0c;期间痞子衡也做过两个小版本更新&#xff0c;但不足以单独介绍。这一次痞子衡为大家带来了全新大版本v5.0.0&#xff0c;这次更新主要是想和大家特别聊聊恩智浦新一代 i.MXRT …

一个传统剧团的自救

今天我和大家分享一个 最近我在网上看到的案例。 是这样说的 在岭南地区 有一个较为偏远的地方 当地有一个传统的戏团 他们依托当地传统习俗 把戏曲一代一代流传下来 但是到了现在。 戏团面临传承中断 戏团解散的困境。 当地文化宣传员小林 知道这个情况后。 立马展开调查 并且…

Linux——gcc/g++编译器

gcc是用来编译C语言代码的编译器&#xff0c;而g是用来编译C代码的编译器的。 而gcc和g都是软件&#xff0c;需要使用yum进行下载 注&#xff1a;需要使用root权限才能下载 在C语言编译的过程中&#xff0c;会有四个过程: 1预处理&#xff0c;2编译&#xff0c;3汇编&#xf…

CB06551 PRD-B040SSIB-63

​ CB06551 PRD-B040SSIB-63 步进电机驱动器有什么参数   步进电机驱动器基本参数如下&#xff1a; a、供电电源&#xff0c;可据所驱动步进电机的电源规格进行选择。交流电源供电的&#xff0c;如AC80V&#xff0c;可用220V市电经降压变压器&#xff0c;提供给驱动器。选用变…

YOLO-NAS 如何将 YOLO-v8 甩在身后?

在使用目标检测模型方面&#xff0c;我已经积累了一些经验。所以当我听说这个火热的新玩意儿叫做 YOLO-NAS 时&#xff0c;我知道我必须去尝试一下。让我告诉你&#xff0c;这个家伙真是让人惊叹。它就像是目标检测模型中的埃隆马斯克——大胆、创新&#xff0c;稍微有点让人害…

关于我被敲诈勒索骗了 1w 多这件事

大家好&#xff0c;我是程序员贺同学。 昨晚遭遇了人生中第一次诈骗&#xff0c;损失金额 1w多&#xff0c;趁这两天情绪缓了缓&#xff0c;把过程记录了下来&#xff0c;希望对看到的人有所帮助。 昨晚报完警回来快 23 点&#xff0c;把手机上的重要图片&#xff0c;视频&…

嵌入式 QT多界面切换

目录 1. 添加界面类实现 1.1 添加第二个界面的类 1.2 添加第2个界面的头文件和槽函数 2. 工程管理实现 在Qt中&#xff0c;多界面切换是指在一个应用程序中使用多个不同的界面&#xff0c;并在它们之间进行切换。这种切换可以是用户触发的&#xff0c;例如点击按钮或选择菜…

建议收藏,最全ChatGPT 中文调教指南:提供各个领域的角色提示词(prompts)及使用技巧,当然也有不正经指南

ChatGPT是一种基于GPT&#xff08;Generative Pre-trained Transformer&#xff09;模型的聊天机器人&#xff0c;能够回答用户提出的问题和进行对话。它是由OpenAI开发的人工智能产品&#xff0c;具有自然语言处理和深度学习技术。 ChatGPT在日常的对话中&#xff0c;表现的非…

深度剖析JVM调优法则,神器Arthas从CPU/内存出发轻松掌握调优实战技巧

场景一、CPU过高 CPU占用过高排查思路&#xff1a; step1&#xff1a;进行arthas java -jar arthas-boot.jar step2&#xff1a;输入deashboard 如何不记得命令可以在控制台输入help step3&#xff1a;查看线程栈信息 thread ID 从线程栈信息中定位到具体的java代码。 场…

Ubuntu关机、重启和注销命令

学习linux系统&#xff0c;最重要的是使用各种命令对系统进行操作&#xff0c;打开各种软件&#xff0c;本次主要分析ubuntu中的关机&#xff0c;重启&#xff0c;注销命令的使用详解。 关机命令 shutdown 1)使用shutdown --help可以查看shutdown命令如何使用&#xff0c;当然也…

mssql计划

介绍 MSSQL计划是一个用于Microsoft SQL Server数据库管理的工具。它包含了一系列的功能&#xff0c;可以帮助管理员进行数据库的备份、恢复、优化、监控等操作&#xff0c;提高数据库的性能和可靠性。 MSSQL计划的主要功能包括&#xff1a; 备份和恢复数据库&#xff1a;可以…

【运维知识进阶篇】集群架构-HTTPS证书详解

HTTPS证书在企业中非常重要&#xff0c;因为HTTP不安全&#xff0c;采用HTTP协议容易受到劫持和篡改&#xff0c;如果是采用HTTPS&#xff0c;数据在传输过程中加密&#xff0c;可以避免报文信息被窃取篡改&#xff0c;避免网站传输时信息泄露。实现https&#xff0c;要了解SSL…

记录--九个超级好用的 Javascript 技巧

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 前言 在实际的开发工作过程中&#xff0c;积累了一些常见又超级好用的 Javascript 技巧和代码片段&#xff0c;包括整理的其他大神的 JS 使用技巧&#xff0c;今天筛选了 9 个&#xff0c;以供大家参考…

从选型工具到内核优化,从替代方法到迁移改造,河北移动联合云和恩墨以创新树标杆,推进国产数据库应用落地...

势在必行&#xff0c;电信行业国产化改造适逢其时 自十四五规划以来&#xff0c;伴随着“科技创新”和“信息安全”等相关政策的密集出台&#xff0c;我国信创产业正式进入高速发展期&#xff0c;力求通过构建各行业全栈国产IT体系&#xff0c;实现科技技术自主可控&#xff0c…

原来我真的不懂Spring

(1)Spring的生命周期:简单概括为4个阶段: 1.1 创建对象 1.2 DI属性赋值 1.3 初始化 1.4 销毁 (2) Bean的作用域 : 1. Singleton: 单例 2. Prototype: 多例 3. Request: 每次http请求都会创建一个新的bean 4. Session: ~ 5. ApplicationContext: ~ (3) 注册Bean的4种方式…

企业即时通讯如何让企业沟通变得简单

企业即时通讯&#xff0c;企业之间的沟通协作&#xff0c;最核心的价值在于能够将复杂的工作任务简化为更高效、更易于沟通的协作方式。如果员工之间没有协作&#xff0c;就没有办法进行高效的沟通&#xff0c;就会出现组织低效、沟通效率低等问题。那么如何将复杂的工作任务简…

多路复用是怎么回事?

《计算机组成原理》讲述的是如何去理解程序和计算。《操作系统》讲述的是如何去理解和架构应用程序。《计算机网络》讲述的是如何去理解今天的互联网。 现在来看&#xff0c;“计算机网络”也许是一个过时的词汇&#xff0c;它讲的是怎么用计算实现通信。今天我们已经发展到了一…

HTTPS(面试高频必须掌握)

目录 一、HTTPS背景 二、HTTPS 的工作过程 1. 对称加密 2.非对称加密 3. HTTPS 基本工作过程 3.1 使用对称密钥 3.2 引入非对称密钥&#xff08;面试高频问题&#xff09; 3.3 黑客的手段 3.4 引入证书 3.5 捋一捋 3.6 SSL/TLS 三、HTTP 与 HTTPS 区别&#xff08;…

强化学习的应用领域和案例

你好&#xff0c;我是zhenguo(郭震) 今天总结强化学习第四篇&#xff1a;强化学习的应用领域 第一&#xff1a;游戏领域。 强化学习在游戏领域有很多应用&#xff0c;如围棋、象棋、扑克等游戏的AI对战。 例如&#xff0c;AlphaGo使用强化学习技术&#xff0c;在围棋比赛中击败…

要做存储业务,我解析了一个项目的源码

最近在做存储相关的业务&#xff0c;更具体的来说是存储相关的研发&#xff0c;于是就上网查了一下相关的资料&#xff0c;思虑再三打算从最简单的 Json 数据交换格式开始研究。 JSON是独立于编程语言的数据交换格式&#xff0c;几乎所有与网络开发相关的语言都有JSON函数库&am…