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

news/2024/7/10 21:22:15 标签: 开源, java, mybatis

项目介绍

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

定义实体类

java">@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;
}

测试效果

java">@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注解的方法。

java">@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,进行翻译。

java">    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方法中会进行注入

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

TransService#registerTransType,注入翻译服务。

java">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

java">    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

java">    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是字典对应的值。

java">    @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的一部分。

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

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

java">    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

java">@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实现。

java">    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

java">    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。

java">    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适配配置。

java">@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

java">@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。

java">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

java">    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服务的具体处理。

java">    @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,对数据进行翻译

java">@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方法。

java">    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,创建代理类,通过属性赋值赋值到代理类中。

java">    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来生成新的代理类

java">    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.niftyadmin.cn/n/350792.html

相关文章

大数据如何助力营销(5)活动复盘

在市场竞争日益激烈的今天&#xff0c;营销活动已经成为吸引用户、提升品牌影响力、增加销售转化的重要手段。然而&#xff0c;一场营销活动在举办后&#xff0c;往往难以评估活动的效果&#xff0c;而大数据技术将从方方面面、科学有效地复盘活动&#xff0c;并为下一次举办活…

R语言绘制山脊图(也叫峰峦图、山峦图)

山脊图也叫也叫峰峦图、山峦图&#xff0c;主要是通过展示一个相同的X轴数据&#xff0c;可以是时间序列、基因数据等&#xff0c;对应不同的Y轴数据&#xff0c;清晰的展示不同数据见变量的关系。今天我们通过R语言来演示山脊图。需要使用到ggridges包&#xff0c;需要提前安装…

都过度疲劳了,还谈什么效率?

阅读本文大概需要 1.22 分钟。 上周我发过一篇文章&#xff0c;末尾提到说&#xff0c;「现在社会生活压力大&#xff0c;每个行业都很卷&#xff0c;导致大家都异常拼命&#xff0c;然而回头想想&#xff0c;人怎么都是一辈子&#xff0c;太拼&#xff0c;太卷&#xff0c;最后…

代码随想录算法训练营第49天 | 121、122

121. 买卖股票的最佳时机 题目描述 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&#xff0c;并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 返回…

第二章 编程基础

2.1 注释 单行注释 # 输出“你好&#xff0c;世界&#xff01;” print("你好&#xff0c;世界&#xff01;")快速注释&#xff1a;ctrl? # print("你好&#xff0c;世界&#xff01;") # print("你好&#xff0c;世界&#xff01;") # print(&…

mssql计划

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

面对数字化新技术的变革风口,企业应如何借由技术创新开拓新局,完成数字化转型和升级?

为使企业有效借力科技创新&#xff0c;实现数字化转型升级&#xff0c;企业可以采取以下几个关键步骤&#xff1a; 评估业务目标&#xff1a;首先明确企业的业务目标并确定技术创新可以帮助企业实现这些目标的领域。了解企业当前的挑战、竞争格局和客户需求&#xff0c;以确定数…

gradio入门示例

随着chat-gpt等机器人对话框架的流行&#xff0c;让一个名为gradio的框架也火热起来&#xff0c;这个框架可以开启一个http服务&#xff0c;并且带输入输出界面&#xff0c;可以让对话类的人工智能项目快速运行。 gradio号称可以快速部署ai可视化项目。 下面通过两个示例来感受…