Spring Boot自定义注解实现属性增强(含源码)
一.背景
实际开发场景中,当有涉及到数据码表(数据字典)时,业务数据库表通常存储的是字典值,不是字典描述。所以在实际业务开发当中需要将字典值翻译为字典描述,在代码中每次去单独的遍历十分繁琐,所以这里实现的将字典值翻译为字典描述的动作单独提出到切面,由切面去实现动态翻译和字段查询赋值。
二.实现效果
通过接口查询业务表,系统自动将业务表中的数据翻译为数据字典描述值,并且在接口查询结果中添加描述值
三.实现步骤
1.添加自定义注解
package com.cpl.tsl.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 自定义注解 * * @author: lll * @date: 2022年03月21日 17:03:08 */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD }) @Documented public @interface DataDict { /** * 分类 * * @return 分类 */ String type(); }
2.添加拦截器,拦截请求结果进行解析
package com.cpl.tsl.listener;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.cpl.tsl.bean.ResultMap;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* 属性增强拦截器
*
* @author: lll
* @date: 2022年03月20日 07:03:34
*/
@Component("dataDictionary")
@Aspect
public class DataDictionaryListener {
private static final Logger logger = LoggerFactory.getLogger(DataDictionaryListener.class);
//返回对象属性值名称
private String MESSAGE = "message";
private String STATUS = "status";
private String DATA = "data";
//拦截解析结果类
private String resultMapName = "com.cpl.tsl.bean.ResultMap";
//表示这个包下面的类才有效
private static final String NEED_SCAN_PACKAGE = "com.cpl.tsl.bean";
/**
* 在请求之中拦截获取拦截
*/
@Around("execution(* com.cpl.tsl.controller..*.*(..))")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
// result的值就是被拦截方法的返回值
Object result = pjp.proceed();
Signature signature = pjp.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
// 被切的方法
Method method = methodSignature.getMethod();
// 返回类型
Class<?> methodReturnType = method.getReturnType();
if (!resultMapName.equals(methodReturnType.getName())) {
return result;
}
// 实例化
ResultMap resultMap = new ResultMap();
Field[] fieldInfo = methodReturnType.getDeclaredFields();
for (Field field : fieldInfo) {
field.setAccessible(true);
if (MESSAGE.equals(field.getName()) && field.get(result) != null) {
resultMap.setMessage(field.get(result).toString());
}
if (STATUS.equals(field.getName()) && field.get(result) != null) {
resultMap.setStatus(field.get(result).toString());
}
if (DATA.equals(field.getName()) && field.get(result) != null) {
logger.info(field.get(result).getClass().getName());
DataDictSerializeFilter dataDictSerializeFilter = new DataDictSerializeFilter();
// list,特殊处理一下
if (field.get(result) instanceof List) {
List list = (List) field.get(result);
List resultList = new ArrayList();
for (Object o : list) {
JSONObject resultJson = writeFieldToObject(dataDictSerializeFilter, o);
resultList.add(resultJson);
}
resultMap.setData(resultList);
} else {
JSONObject resultJson = writeFieldToObject(dataDictSerializeFilter, field.get(result));
resultMap.setData(resultJson);
}
}
}
return resultMap;
}
private JSONObject writeFieldToObject(DataDictSerializeFilter dataDictSerializeFilter, Object result) throws IllegalAccessException {
String dataString = JSON.toJSONString(result, dataDictSerializeFilter);
JSONObject resultJson = JSONObject.parseObject(dataString);
Field[] fields = result.getClass().getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
//field Name
String fieldName = fields[i].getName();
String packageName = fields[i].getClass().getPackage().getName();
//include other bean
if (packageName.startsWith(NEED_SCAN_PACKAGE)) {
String sonJsonString = JSON.toJSONString(fields[i].get(result), dataDictSerializeFilter);
JSONObject sonResultJson = JSONObject.parseObject(sonJsonString);
resultJson.put(fieldName, sonResultJson);
}
//include list
fields[i].setAccessible(true);
if (fields[i].get(result) instanceof List) {
List list = (List) fields[i].get(result);
List resultList = new ArrayList();
for (Object o : list) {
String sonListJsonString = JSON.toJSONString(o, dataDictSerializeFilter);
JSONObject sonListResultJson = JSONObject.parseObject(sonListJsonString);
resultList.add(sonListResultJson);
}
resultJson.put(fieldName, resultList);
}
}
return resultJson;
}
}
3.添加过滤器,属性翻译并添加字段
package com.cpl.tsl.listener; import com.alibaba.fastjson.serializer.AfterFilter; import com.cpl.tsl.annotation.DataDict; import com.cpl.tsl.bean.SysDataDict; import com.cpl.tsl.service.Impl.SysDataDictServiceImpl; import com.cpl.tsl.utils.SpringUtil; import org.springframework.util.StringUtils; import java.lang.annotation.Annotation; import java.lang.reflect.Field; /** * 序列化属性增强 * * @author: lll * @date: 2022年03月20日 07:03:09 */ public class DataDictSerializeFilter extends AfterFilter { /** * 表示这个包下面的类才有效 */ private static final String NEED_SCAN_PACKAGE = "com.cpl.tsl.bean"; private static final String DISPLAY_SUFFIX = "Description"; private SysDataDictServiceImpl sysDataDictServiceImpl = (SysDataDictServiceImpl) SpringUtil.getBean("sysDataDictServiceImpl"); @Override public void writeAfter(Object object) { String packageName = object.getClass().getPackage().getName();//该方法是获取包名,可以利用该方法的结果来缩小范围 if (!packageName.startsWith(NEED_SCAN_PACKAGE)) { return; } //获取所有的字段 Field[] fields = object.getClass().getDeclaredFields(); //遍历字段 try { for (Field f : fields ) { //获取字段上的自定义注解 Annotation annotation = f.getAnnotation(DataDict.class); if (annotation == null) { continue; } //获取DataDict类型 String type = f.getAnnotation(DataDict.class).type(); if (StringUtils.isEmpty(type)) { continue; } f.setAccessible(true); //获取属性值 String o = f.get(object).toString(); if (StringUtils.isEmpty(o)) { continue; } //获取字典值 SysDataDict sysDataDict = sysDataDictServiceImpl.getSysDataDictByCodeAndFCode(o, type); if (sysDataDict != null && !StringUtils.isEmpty(sysDataDict.getDesp())) { //构造一个新的key value super.writeKeyValue(f.getName() + DISPLAY_SUFFIX, sysDataDict.getDesp()); } } } catch (IllegalAccessException e) { e.printStackTrace(); } } }
4.添加工具类
package com.cpl.tsl.utils; import org.springframework.beans.BeansException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; /** * 手动注入service工具类 * * @author: lll * @date: 2022年03月21日 16:03:42 */ @Component public class SpringUtil implements ApplicationContextAware { private static ApplicationContext applicationContext = null; public SpringUtil() { } public void setApplicationContext(ApplicationContext arg0) throws BeansException { if (applicationContext == null) { applicationContext = arg0; } } public static ApplicationContext getApplicationContext() { return applicationContext; } public static void setAppCtx(ApplicationContext webAppCtx) { if (webAppCtx != null) { applicationContext = webAppCtx; } } /** * 拿到ApplicationContext对象实例后就可以手动获取Bean的注入实例对象 */ public static <T> T getBean(Class<T> clazz) { return getApplicationContext().getBean(clazz); } public static <T> T getBean(String name, Class<T> clazz) throws ClassNotFoundException { return getApplicationContext().getBean(name, clazz); } public static final Object getBean(String beanName) { return getApplicationContext().getBean(beanName); } public static final Object getBean(String beanName, String className) throws ClassNotFoundException { Class clz = Class.forName(className); return getApplicationContext().getBean(beanName, clz.getClass()); } public static boolean containsBean(String name) { return getApplicationContext().containsBean(name); } public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException { return getApplicationContext().isSingleton(name); } public static Class<?> getType(String name) throws NoSuchBeanDefinitionException { return getApplicationContext().getType(name); } public static String[] getAliases(String name) throws NoSuchBeanDefinitionException { return getApplicationContext().getAliases(name); } }
5.在实体类的字段上添加自定义的注解 @DataDict(type = "SEX")
package com.cpl.tsl.bean; import com.cpl.tsl.annotation.DataDict; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import java.io.Serializable; @ApiModel(value = "Employee", description = "Employee实体类") public class Employee implements Serializable { @ApiModelProperty(value = "id") private Integer id; @ApiModelProperty(value = "姓名") private String lastName; @DataDict(type = "SEX") @ApiModelProperty(value = "性别") private Integer gender; @ApiModelProperty(value = "邮箱") private String email; @ApiModelProperty(value = "父级id") private Integer dId; public void setId(Integer id) { this.id = id; } public void setLastName(String lastName) { this.lastName = lastName; } public void setGender(Integer gender) { this.gender = gender; } public void setEmail(String email) { this.email = email; } public void setdId(Integer dId) { this.dId = dId; } public Integer getId() { return id; } public String getLastName() { return lastName; } public Integer getGender() { return gender; } public String getEmail() { return email; } public Integer getdId() { return dId; } }
四.注意事项
1.目前代码只能对返回结果类型为ResultMap的接口进行解析,其他类型直接跳过
2.需要将实际实体类属性赋值给ResultMap中的data
五.源码