不积跬步,无以至千里。不积小流,无以成江海。
通常我们有这种需求,某一个字段是由前台维护的:编码 <==> 含义,类似一个键值对。含义是可能会变化的,但编码是固定的,一般我们都会在数据库中直接存储编码,但数据返回给前台显示时需要展示为含义。当然我们可以在从数据库查询时去join到该值的含义一起返回,这里提供另一种方式来实现这个功能。
实现思路
主要是使用注解加切面。在数据返回前通过切面把被注解的字段替换成该值的含义。
这里使用性别字段,键值关系为:
M - 男
W - 女
新建注解
新建一个注解用于标识字段需要被切面处理
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SexValue {
}
新建第二个注解,作为一个切点。
@Target(value = ElementType.METHOD )
@Retention(value = RetentionPolicy.RUNTIME)
public @interface ProcessResult {
}
注解参数含义:
- @interface : 表示定义一个注解
- @Target 表示该注解可以用于什么地方,可能的ElementType参数有:
- CONSTRUCTOR:构造器的声明
- FIELD:域声明(包括enum实例)
- LOCAL_VARIABLE:局部变量声明
- PACKAGE:包声明
- PARAMETER:参数声明
- TYPE:类、接口(包括注解类型)或enum声明
- @Retention 表示需要在什么级别保存该注解信息。可选的RetentionPolicy参数包括:
- SOURCE:注解将被编译器丢弃
- CLASS:注解在class文件中可用,但会被VM丢弃
- RUNTIME:VM将在运行期间保留注解,因此可以通过反射机制读取注解的信息
- @Document 将注解包含在Javadoc中
- @Inherited 允许子类继承父类中的注解
Entity
实体类如下,在 sex
字段上添加注解
public class Person {
private Long id;
private String name;
private Integer age;
@SexValue
private String sex;
private String address;
private String phoneNum;
private LocalDateTime createdDate;
private LocalDateTime lastUpdateDate;
private Long objectVersionNumber;
public void createBefore(){
this.createdDate = LocalDateTime.now();
this.lastUpdateDate = LocalDateTime.now();
this.objectVersionNumber = 1L;
}
public void updateBefore(Long objectVersionNumber){
this.lastUpdateDate = LocalDateTime.now();
this.objectVersionNumber = objectVersionNumber + 1L;
}
}
Controller
在Controller
中,把需要翻译的方法加上注解。
@RestController
@RequestMapping("/v1/hjwjw/person")
public class PersonController {
private IPersonService personService;
public PersonController(IPersonService personService) {
this.personService = personService;
}
@GetMapping
@ProcessResult
public ResponseEntity<List<Person>> query(){
return ResponseEntity.ok(personService.queryPerson());
}
@PostMapping
@ProcessResult
public ResponseEntity<Person> createPerson(@RequestBody Person personVO){
return ResponseEntity.ok(personService.createPerson(personVO));
}
@PutMapping
@ProcessResult
public ResponseEntity<Person> updatePerson(@RequestBody Person personVO){
return ResponseEntity.ok(personService.updatePerson(personVO));
}
@DeleteMapping("/{personId}")
public ResponseEntity delPerson(@PathVariable("personId") Long personId){
personService.delPerson(personId);
return ResponseEntity.ok(HttpStatus.OK);
}
}
新建切面
新建一个切面类,在Controller中加了 @ProcessResult 注解的方法,在返回前会进入切面进行处理。
返回的Object需要判断是否为集合,并把其父类字段都需要遍历查找是否有添加@SexValue
注解。
@Component
@Aspect
public class SexAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(SexAspect.class);
@AfterReturning(value = "@annotation(processResult)",returning = "result")
public Object aftreReturning(JoinPoint joinPoint, ProcessResult processResult,Object result) throws IllegalAccessException {
LOGGER.info(joinPoint.toString());
LOGGER.info("<===================Aspect======================>");
LOGGER.info(result.toString());
if (result == null){
return null;
}
if (result instanceof ResponseEntity){
Object body = ((ResponseEntity<?>) result).getBody();
if (body == null){
return null;
}
if (body instanceof Collection ){
for (Object obj : (Collection<?>) body){
//处理
processObj(obj);
}
}else {
//处理
processObj(body);
}
}else if (result instanceof Collection){
//处理
for (Object obj : (Collection<?>) result){
//处理
processObj(obj);
}
}else {
processObj(result);
}
return result;
}
private void processObj(Object obj) throws IllegalAccessException {
//取出Obj所有 Field,以及父类 Field
List<Field> fieldList = new ArrayList<>();
Class<?> tempClass = obj.getClass();
while (tempClass != null){
fieldList.addAll(Arrays.asList(tempClass.getDeclaredFields()));
tempClass = tempClass.getSuperclass();
}
Field[] fields = new Field[fieldList.size()];
fieldList.toArray(fields);
//遍历Field,查找添加 @SexValue 注解的字段 并 做翻译
for (Field field : fields){
if (field.isAnnotationPresent(SexValue.class)){
field.setAccessible(true);
String fieldValue = String.valueOf(field.get(obj));
LOGGER.info("fieldValue:{}",fieldValue);
if ("M".equals(fieldValue)){
field.set(obj,"男");
}else {
field.set(obj,"女");
}
}
}
}
}
这里只是做了简单的值转换。至于如何获取到对应的含义,建议把配置的值集缓存到Redis,这样在切面处理时可以根据编码从 Redis 中直接取出含义进行替换。