用Spring AOP、Java注解、反射对返回的结果集进行特殊处理

1.目标:业务数据中的某些字段列,需要根据用户选择的语言类型自动处理后返回对应语言的数据

2.背景:系统需要适应多种语言:中文简体、中文繁体、英语等,一个业务数据中的某些列,会同时设置多种语言的数据,用户可根据自己喜欢的语言展示对应的数据

3.用户选择语言后,前端把该参数通过header头传递到后端,后端查询业务数据后,根据header头中的多语言参数,查询业务数据中某些列对应语言的数据,再覆盖到当前的数据中,然后返回给客户端

4.难点:1.结果集的数据有数组嵌套,2.尽量少改动业务代码,且提供好的性能,来满足业务要求,因此通过aop + 注解 的方式统一处理这种类型的业务

5.实现逻辑:返回的结果集对象中有List集合,在aop切面中把结果集获取出来,并循环结果集中的每一列,判断是否为多语言字段,或者是否为List对象,把所有需要多语言处理的字段全部放到一个List集合中,通过mysql in 查询一次把数据查询出来,并组装数据为map 键值对结构,再次对结果集进行循环,再次使用反射,对需要多语言处理的字段通过key在map 集合中查询,有数据则覆盖现有数据

6.实现方法:1.在返回实体字段中添加注解,标识某些字段是多语言字段,2.在业务方法上使用注解,标识该方法需要进行多语言处理,3.添加aop切面,对服务层使用了多语言处理注解的方法进行横切,来统一多语言处理

6.代码实现

  • 1.重要依赖
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-web</artifactId>
   </dependency>
   <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.5</version>
  </dependency>
  • 2.添加注解:标识某些字段是多语言字段,注解中的字段与我们系统的业务逻辑有关
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MultiLanguageAnnotation {
     

    /**
     * 表名
     */
    String tabName() default "";

    /**
     * 列名
     */
    String colName() default  "";

    /**
     * 主键名
     * @return
     */
    String pkName() default "";
}
  • 3.添加注解:标识该方法需要进行多语言处理
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MultiLanguageResponse {
     

}
  • 4.反射处理帮助类,这里写法不太优雅,功能倒是实现了
package com.es.util;

import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSON;
import com.egovchina.apicenter.es.config.MultiLanguageAnnotation;
import com.egovchina.apicenter.es.searchlogic.po.TabMultiLanguagePo;
import com.egovchina.apicenter.es.searchlogic.service.MultiLanguageService;
import org.springframework.util.Base64Utils;
import org.springframework.util.StringUtils;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 多语言帮助类
 */
public class MultiLanguageUtil {
     

    /**
     * 设置多语言内容 
* 两种使用示例:
* 1.直接在方法上使用 @MultiLanguageResponse 注解,来处理多语言
* 2.使用代码方式:
List list = esDatas.getDatas();
String languageCode = RequestHolder.getDataLanguageHeaderValue();
MultiLanguageUtil.setMultiLanguageInfo(multiLanguageService , list , languageCode);
* * @param multiLanguageService es多语言查询服务 * @param documents 要设置多语言字段的文档集合 * @param languageCode 语言类型 * @param */
public static <T> void setMultiLanguageInfo(MultiLanguageService multiLanguageService , List<T> documents , String languageCode){ if(documents != null && documents.size() > 0){ List<String> multiInfoIds = getMultiInfoId(documents , languageCode); //System.out.println("util multiInfoIds:" + JSON.toJSONString(multiInfoIds)); if(multiInfoIds != null && multiInfoIds.size() > 0){ //过滤重复multiInfoId multiInfoIds = multiInfoIds.stream().distinct().collect(Collectors.toList()); //根据multiInfoId查询多语言内容 List<TabMultiLanguagePo> multiLanguageDocuments = multiLanguageService.getByMultiInfoId(multiInfoIds); if(multiLanguageDocuments != null && multiLanguageDocuments.size() > 0) { //把从es查询出来的多语言键值对,序列化成map Map<String , String> map = new HashMap<>(multiLanguageDocuments.size()); multiLanguageDocuments.forEach(item -> map.put(item.getMultiInfoId() , item.getContentText())); //System.out.println("util map:" + JSON.toJSONString(map)); //设置多语言内容 setMultiValue(documents , languageCode , map); } } } } /** * 获取文档中有多语言注解的 multiInfoId * @param documents * @param languageCode * @param * @return */ private static <T> List<String> getMultiInfoId(List<T> documents , String languageCode){ List<String> multiInfoIdList = new ArrayList<>(20); documents.forEach(item -> { try { Class clazz = item.getClass(); // 获取集合中的对象 Field[] fields = clazz.getDeclaredFields();// 获取他的字段数组 for (Field field : fields) { field.setAccessible(true); // 设置字段可访问 //获取带有 MultiLanguageAnnotation 注解的字段,并组装 multiInfoId MultiLanguageAnnotation languageAnnotation = field.getDeclaredAnnotation(MultiLanguageAnnotation.class); if (languageAnnotation != null) { String multiInfoId = languageAnnotation.tabName() + languageAnnotation.colName() + ReflectionUtils.getFieldValue(item, languageAnnotation.pkName()) + languageCode; //System.out.println("multiInfoId:" + multiInfoId); multiInfoIdList.add(multiInfoId); } //获取List泛型中带有 MultiLanguageAnnotation 注解的字段,并组装 multiInfoId if (List.class.isAssignableFrom(field.getType())) { Type genericType = field.getGenericType(); // 当前集合的泛型类型 if (null == genericType) { continue; } if (genericType instanceof ParameterizedType) { //ParameterizedType pt = (ParameterizedType) genericType; //Class clz = (Class) pt.getActualTypeArguments()[0];//得到对象list中实例的类型 Object obj = field.get(item); if(null == obj){ continue; } Class childClazz = obj.getClass(); //获取到属性的值的Class对象 if(null == childClazz){ continue; } Method m = childClazz.getDeclaredMethod("size"); int size = (Integer) m.invoke(field.get(item)); //调用list的size方法,得到list的长度 for (int i = 0; i < size; i++) { //遍历list,调用get方法,获取list中的对象实例 Method getM = childClazz.getDeclaredMethod("get", int.class); //getM.setAccessible(true); Object childObj = getM.invoke(field.get(item), i); //获取泛型的子类对象 Field[] childFields = childObj.getClass().getDeclaredFields(); //得到子类对象的字段数组 //System.out.println("list obj field size : " + objField.length); for (Field childField : childFields){ childField.setAccessible(true); MultiLanguageAnnotation childLanguageAnnotation = childField.getDeclaredAnnotation(MultiLanguageAnnotation.class); if (childLanguageAnnotation != null) { String childMultiInfoId = childLanguageAnnotation.tabName() + childLanguageAnnotation.colName() + ReflectionUtils.getFieldValue(childObj, childLanguageAnnotation.pkName()) + languageCode; //System.out.println("childMultiInfoId:" + childMultiInfoId); multiInfoIdList.add(childMultiInfoId); } } } } } } } catch (Exception e) { e.printStackTrace(); } }); return multiInfoIdList; } /** * 根据多语言 multiInfoId ,设置多语言内容 * @param documents * @param map * @param */ private static <T> void setMultiValue(List<T> documents , String languageCode , Map<String , String> map){ documents.forEach(item -> { try { Class clazz = item.getClass(); // 获取集合中的对象 Field[] fields = clazz.getDeclaredFields();// 获取他的字段数组 for (Field field : fields) { field.setAccessible(true); // 设置字段可访问 //获取带有 MultiLanguageAnnotation 注解的字段,并组装 multiInfoId MultiLanguageAnnotation languageAnnotation = field.getDeclaredAnnotation(MultiLanguageAnnotation.class); if (languageAnnotation != null) { String multiInfoId = languageAnnotation.tabName() + languageAnnotation.colName() + ReflectionUtils.getFieldValue(item, languageAnnotation.pkName()) + languageCode; String contentText = map.get(multiInfoId); if(!StringUtils.isEmpty(contentText)){ field.set(item , contentText); contentText = null; } } //获取List泛型中带有 MultiLanguageAnnotation 注解的字段,并组装 multiInfoId if (List.class.isAssignableFrom(field.getType())) { Type genericType = field.getGenericType(); // 当前集合的泛型类型 if (null == genericType) { continue; } if (genericType instanceof ParameterizedType) { //ParameterizedType pt = (ParameterizedType) genericType; //Class clz = (Class) pt.getActualTypeArguments()[0];//得到对象list中实例的类型 Object obj = field.get(item); if(null == obj){ continue; } Class childClazz = obj.getClass(); //获取到属性的值的Class对象 if(null == childClazz){ continue; } Method m = childClazz.getDeclaredMethod("size"); int size = (Integer) m.invoke(field.get(item)); //调用list的size方法,得到list的长度 for (int i = 0; i < size; i++) { //遍历list,调用get方法,获取list中的对象实例 Method getM = childClazz.getDeclaredMethod("get", int.class); Object childObj = getM.invoke(field.get(item), i); //获取泛型的子类对象 Field[] childFields = childObj.getClass().getDeclaredFields(); //得到子类对象的字段数组 //System.out.println("list obj field size : " + objField.length); for (Field childField : childFields){ childField.setAccessible(true); MultiLanguageAnnotation childLanguageAnnotation = childField.getDeclaredAnnotation(MultiLanguageAnnotation.class); if (childLanguageAnnotation != null) { String childMultiInfoId = childLanguageAnnotation.tabName() + childLanguageAnnotation.colName() + ReflectionUtils.getFieldValue(childObj, childLanguageAnnotation.pkName()) + languageCode; String childContentText = map.get(childMultiInfoId); if(!StringUtils.isEmpty(childContentText)){ childField.set(childObj , childContentText); childContentText = null; } } } } } } } } catch (Exception e) { e.printStackTrace(); } }); } }
  • 5.aop切面处理类
@Component
@Slf4j
@Aspect
public class MultiLanguageAspect {
     

    @Autowired
    private MultiLanguageService multiLanguageService;

    /**
     * 监听有多语言响应注解的方法,对结果集中的多语言字段进行处理
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @Around(value = "@annotation(com.apicenter.es.config.MultiLanguageResponse)")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
     
        Object result = proceedingJoinPoint.proceed();
        //取到header头信息中的多语言参数字段
        String languageCode = RequestHolder.getDataLanguageHeaderValue();
        if(!StringUtils.isEmpty(languageCode)){
     
            //只对返回值类型为:ResultTemplate、List 两种类型的结果集进行多语言处理
            if(result instanceof ResultTemplate){
     
                ResultTemplate resultTemplate = (ResultTemplate)result;
                MultiLanguageUtil.setMultiLanguageInfo(multiLanguageService , resultTemplate.getData() , languageCode);
            } else if(result instanceof List){
     
                List list = (List)result;
                MultiLanguageUtil.setMultiLanguageInfo(multiLanguageService , list , languageCode);
            }
        }
        return result;
    }
}
  • 6.其他帮助类
public class RequestHolder {
     

    private static HttpServletRequest getHttpServletRequest() {
     
        try {
     
            return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        } catch (Exception e) {
     
            return null;
        }
    }
    /**
     * 获取多语言header参数
     * @return
     */
    public static String getDataLanguageHeaderValue() {
     
        HttpServletRequest httpServletRequest = getHttpServletRequest();
        if (httpServletRequest != null) {
     
            String header = httpServletRequest.getHeader("Data-Language");
            if(!StringUtils.isEmpty(header)){
     
                return header;
            }
        }
        return ""; //默认返回空
    }
}




public class ReflectionUtils {
     

    private static Logger log = LoggerFactory.getLogger(ReflectionUtils.class);

    /**
     * 直接读取对象的属性值, 忽略 private/protected 修饰符, 也不经过 getter
     *
     * @param object
     * @param fieldName
     * @return
     */
    public static Object getFieldValue(Object object, String fieldName) {
     
        Field field = getDeclaredField(object, fieldName);
        if (field == null) {
     
            throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + object + "]");
        }
        makeAccessible(field);
        Object result = null;
        try {
     
            result = field.get(object);
        } catch (IllegalAccessException e) {
     
            log.error("setFieldValue:", e);
        }
        return result;
    }

    /**
     * 直接设置对象属性值, 忽略 private/protected 修饰符, 也不经过 setter
     *
     * @param object
     * @param fieldName
     * @param value
     */
    public static void setFieldValue(Object object, String fieldName, Object value) {
     
        Field field = getDeclaredField(object, fieldName);

        if (field == null) {
     
            throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + object + "]");
        }

        makeAccessible(field);

        try {
     
            field.set(object, value);
        } catch (IllegalAccessException e) {
     
            log.error("setFieldValue:", e);
        }
    }


    /**
     * 通过反射, 获得定义 Class 时声明的父类的泛型参数的类型
     * 如: public EmployeeDao extends BaseDao
     *
     * @param clazz
     * @param index
     * @return
     */
    @SuppressWarnings("unchecked")
    public static Class getSuperClassGenricType(Class clazz, int index) {
     
        Type genType = clazz.getGenericSuperclass();
        if (!(genType instanceof ParameterizedType)) {
     
            return Object.class;
        }
        Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
        if (index >= params.length || index < 0) {
     
            return Object.class;
        }
        if (!(params[index] instanceof Class)) {
     
            return Object.class;
        }
        return (Class) params[index];
    }
    /**
     * 循环向上转型, 获取对象的 DeclaredMethod
     *
     * @param object
     * @param methodName
     * @param parameterTypes
     * @return
     */
    public static Method getDeclaredMethod(Object object, String methodName, Class<?>[] parameterTypes) {
     
        for (Class<?> superClass = object.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) {
     
            try {
     
                return superClass.getDeclaredMethod(methodName, parameterTypes);
            } catch (NoSuchMethodException e) {
     
                //Method 不在当前类定义, 继续向上转型
            }
        }
        return null;
    }

    /**
     * 使 filed 变为可访问
     *
     * @param field
     */
    public static void makeAccessible(Field field) {
     
        if (!Modifier.isPublic(field.getModifiers())) {
     
            field.setAccessible(true);
        }
    }

    /**
     * 循环向上转型, 获取对象的 DeclaredField
     *
     * @param object
     * @param filedName
     * @return
     */
    public static Field getDeclaredField(Object object, String filedName) {
     
        for (Class<?> superClass = object.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) {
     
            try {
     
                return superClass.getDeclaredField(filedName);
            } catch (NoSuchFieldException e) {
     
                //Field 不在当前类定义, 继续向上转型
            }
        }
        return null;
    }
}

  • 7.在需要进行多语言处理的服务层方法上使用 @MultiLanguageResponse 注解即可实现该功能,且不改动原有业务代码
    @MultiLanguageResponse
    @Override
    public ResultTemplate<AffairsDocument> affairs(AffairsDto affairsDto) {
     
        //xxx 业务处理
        return ResultTemplateUtil.ofESDatasAndPage(data);
    }
  • 8.ok,结合aop、annotation、反射在不改动业务情况下实现业务功能

你可能感兴趣的:(SpringBoot,项目开发,aop,注解,反射)