在开发过程中,根据枚举值设置中文描述的情景非常常见并且重复性极高、代码可读性差(换个人用老实体写新代码可能就要问一遍枚举对应策略),所以想通过自定义注解来实现自动注入。
只需在属性上加入注解
@EnumValueAutoAnnotation(enumClass = TipsStatusEnum.class)
,即可省去重复的set枚举值代码块,一次抒写多次复用。
public class TipsInfoVo implements Serializable {
@ApiModelProperty(value = "状态;(0:未提醒,1:已提醒)")
@EnumValueAutoAnnotation(enumClass = TipsStatusEnum.class)
private Integer status;
@ApiModelProperty(value = "状态-中文描述")
private String statusName;
...
}
1. 自定义注解
import com.tips.ai.domain.enums.EnumValueFieldsConstant;
import java.lang.annotation.*;
/**
* 自定义注解-自动填充枚举描述字段
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.FIELD})
public @interface EnumValueAutoAnnotation {
/**
* 指定枚举类
*/
Class<? extends Enum> enumClass();
/**
* 自动填充字段名 (暂未扩展)
*/
String fillFieldName() default Strings.EMPTY;
/**
* 映射枚举描述字段名
*/
EnumValueFieldsConstant autoValueField() default EnumValueFieldsConstant.desc;
}
常量类
import com.tips.ai.config.annotation.EnumValueAutoAnnotation;
/**
* 自定义注解{@link EnumValueAutoAnnotation}属性 - 表示用来注入值的枚举成员属性
* 注意:如有枚举属性增加不在此类范围内,要使用自动填充应当在此类中增加成员属性
* 例如:设置{@link EnumValueAutoAnnotation#autoValueField()} = desc ,那么会寻找到对应枚举并且填充desc字段的值到指定字段
*/
public enum EnumValueFieldsConstant {
code,
desc,
descText;
}
2. aop切面+反射自动填充枚举描述到字段
import com.ruoyi.common.utils.StringUtils;
import com.tips.ai.config.annotation.EnumValueAutoAnnotation;
import com.tips.ai.domain.enums.EnumValueFieldsConstant;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Optional;
/**
* <p>
* 自定义切面 - service层查询方法
* </p>
*
* @author lee
* @implNote 根据指定字段以及该字段上的注解 {@link EnumValueAutoAnnotation} 自动填充枚举描述
* @since 2022-06-21 22:47:14
*/
@Aspect
@Component
@Slf4j
@SuppressWarnings("all")
public class EnumValueAutofillAspect {
/**
* 将所有service层级的查询方法 作为切入点
*/
@Pointcut("execution(* com.tips.ai.service.*.select*(..)) || execution(* com.tips.ai.service.*.query*(..))")
public void handlePlaceholder() {
}
/**
* <p>
* 切面增强具体实现
* </p>
* <em>@AfterReturning注解描述</em>
* <p>
* @param joinPoint 切面属性
* @param returnObject 方法返回值
* @throws Throwable
*/
@AfterReturning(returning = "returnObject", pointcut = "handlePlaceholder()")
public void doAfterReturning(JoinPoint joinPoint, Object returnObject) throws Throwable {
if (returnObject == null) {
return;
}
// 返回值类型为集合类型
if (returnObject instanceof Collection) {
Collection ListValue = (Collection) returnObject;
for (Object v : ListValue) {
fillEnumNameField(v);
}
} else {
// 返回值类型为单一对象类型
fillEnumNameField(returnObject);
}
}
/**
* 填充枚举描述字段
*
* @param object 填充对象
*/
private void fillEnumNameField(Object object) {
if (object == null) {
return;
}
Field[] fields = object.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
try {
Object value = field.get(object);
if (value instanceof Collection) {
try {
Collection collection = (Collection) value;
collection.forEach(s -> fillEnumNameField(s));
} catch (SecurityException e) {
log.error("(Collection) value error: {}", ExceptionUtils.getFullStackTrace(e));
}
}
if (field.getAnnotations().length < 1 || value == null) {
continue;
}
EnumValueAutoAnnotation enumValueAutoAnnotation = field.getDeclaredAnnotation(EnumValueAutoAnnotation.class);
if (enumValueAutoAnnotation == null) {
continue;
}
log.debug("开始自动填充枚举值 field: {}.{}", object.getClass(), field.getName());
reflectFindAndFillEnumName(object, field, enumValueAutoAnnotation);
} catch (Exception e) {
log.error("enum name auto fill error: {}", ExceptionUtils.getFullStackTrace(e));
}
}
}
/**
* 利用反射查找对应枚举项,并设置到对象属性
*
* @param object 枚举填充对象
* @param field 枚举值字段
* @param enumValueAutoAnnotation
* @throws Exception
*/
private void reflectFindAndFillEnumName(Object object, Field field, EnumValueAutoAnnotation enumValueAutoAnnotation) throws Exception {
Object value = field.get(object);
Class<? extends Enum> enumClass = enumValueAutoAnnotation.enumClass();
Method valuesMethod = enumClass.getMethod("values");
Object[] enums = (Object[]) valuesMethod.invoke(null);
for (Object e : enums) {
Field codeField = e.getClass().getDeclaredField(EnumValueFieldsConstant.code.name());
codeField.setAccessible(true);
// 取出code对应的枚举
if (codeField.get(e).equals(value)) {
Field descTextField = e.getClass().getDeclaredField(enumValueAutoAnnotation.autoValueField().name());
descTextField.setAccessible(true);
// 取出对应枚举的value
Object descTextValue = descTextField.get(e);
String fillFieldName = StringUtils.isNotBlank(enumValueAutoAnnotation.fillFieldName())
? enumValueAutoAnnotation.fillFieldName() : field.getName() + "Name";
Optional<Field> fieldOptional = Arrays.stream(object.getClass().getDeclaredFields())
.filter(s -> StringUtils.equals(s.getName(), fillFieldName))
.findFirst();
if (!fieldOptional.isPresent()) {
log.error("枚举值填充属性{}不存在", fillFieldName);
continue;
}
Field enumNameField = object.getClass().getDeclaredField(fillFieldName);
enumNameField.setAccessible(true);
if (!descTextValue.getClass().equals(enumNameField.getType())) {
log.warn("枚举值填充属性{}类型{}无法转换到枚举类型{},请更改为枚举类型一致的类型", fillFieldName, descTextValue.getClass().getSimpleName(), enumNameField.getType().getSimpleName());
continue;
}
if (enumNameField.get(object) == null) {
enumNameField.set(object, descTextValue);
}
}
}
}
}
3. 在方法返回实体类的属性加上自定义注解
3.1 简约使用方法
public class TipsInfoVo implements Serializable {
@ApiModelProperty(value = "状态;(0:未提醒,1:已提醒)")
@EnumValueAutoAnnotation(enumClass = TipsStatusEnum.class)
private Integer status;
@ApiModelProperty(value = "状态-中文描述")
private String statusName;
...
}
3.2 指定填充字段
public class TipsInfoVo implements Serializable {
@ApiModelProperty(value = "提醒级别-中文描述")
@EnumValueAutoAnnotation(enumClass = TipsLevelEnum.class, fillFieldName = "tipsLevelStr")
private Integer tipsLevel;
@ApiModelProperty(value = "提醒级别;(0:正常,1:重要,2:紧急)")
private String tipsLevelStr;
...
}