先上效果:

在开发过程中,根据枚举值设置中文描述的情景非常常见并且重复性极高、代码可读性差(换个人用老实体写新代码可能就要问一遍枚举对应策略),所以想通过自定义注解来实现自动注入。

只需在属性上加入注解@EnumValueAutoAnnotation(enumClass = TipsStatusEnum.class),即可省去重复的set枚举值代码块,一次抒写多次复用。

1
2
3
4
5
6
7
8
9
10
public class TipsInfoVo implements Serializable {

@ApiModelProperty(value = "状态;(0:未提醒,1:已提醒)")
@EnumValueAutoAnnotation(enumClass = TipsStatusEnum.class)
private Integer status;

@ApiModelProperty(value = "状态-中文描述")
private String statusName;
...
}

1. 自定义注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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;

}

常量类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import com.tips.ai.config.annotation.EnumValueAutoAnnotation;

/**
* 自定义注解{@link EnumValueAutoAnnotation}属性 - 表示用来注入值的枚举成员属性
* 注意:如有枚举属性增加不在此类范围内,要使用自动填充应当在此类中增加成员属性
* 例如:设置{@link EnumValueAutoAnnotation#autoValueField()} = desc ,那么会寻找到对应枚举并且填充desc字段的值到指定字段
*/
public enum EnumValueFieldsConstant {

code,
desc,
descText;

}

2. aop切面+反射自动填充枚举描述到字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
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 简约使用方法

1
2
3
4
5
6
7
8
9
10
public class TipsInfoVo implements Serializable {

@ApiModelProperty(value = "状态;(0:未提醒,1:已提醒)")
@EnumValueAutoAnnotation(enumClass = TipsStatusEnum.class)
private Integer status;

@ApiModelProperty(value = "状态-中文描述")
private String statusName;
...
}

3.2 指定填充字段

1
2
3
4
5
6
7
8
9
10
public class TipsInfoVo implements Serializable {  

@ApiModelProperty(value = "提醒级别-中文描述")
@EnumValueAutoAnnotation(enumClass = TipsLevelEnum.class, fillFieldName = "tipsLevelStr")
private Integer tipsLevel;

@ApiModelProperty(value = "提醒级别;(0:正常,1:重要,2:紧急)")
private String tipsLevelStr;
...
}