Java 中 Spring 如何实现注解根据不同类分别处理属性过滤逻辑?

87 天前
 tiRolin

目前我有一个 Spring 项目,该项目有一个请求如下所示

  @ApiOperation("用户提交报告")
  @PostMapping
  public ResponseDTO<Boolean> save(@RequestBody @Valid AacmReportInsertBo aacmReportInsertBo) {
    aacmReportUserInfoService.save(aacmReportInsertBo);
    return ResponseDTO.ok();
  }

传入的对象 AacmReportInsertBo 拥有如下属性

@Data
public class AacmReportInsertBo {
  /**
   * 所属文书类型 (QUERY——查询,COMPLAINT——意见,REPORT——报告)
   */
  private String type;

  /**
   * 性别
   */
  private String sex;
  
  // 省略
}

这个请求的处理逻辑是根据传入的 type 返回相应的处理器,调用处理器中的 handle 方法来处理请求

  public void save(AacmReportInsertBo aacmReportInsertBo) {
    IReportHandler handler = ReportHandlerFactory.getReportHandlerService(ReportType.getType(aacmReportInsertBo.getType()));
    ReportHandlerErrorEnum.HANDLER_NULL_ERROR.isNull(handler);
    handler.handle(aacmReportInsertBo);
  }

我想要实现一个注解,注解名是 NotNull ,只能在属性中使用,用于保证该属性不为 Null ,但是同时我希望这个注解可以接收一个 Class 数组,数组中传入相应的处理器的 Class ,只有当该请求的处理器存在于 class 数组中时,才执行参数过滤的逻辑,否则不执行

比方说假如我有三个处理器类分别是 QueryHandler 、SecurityHandler 、OpinionComplainHandler ,现在我在我的 AacmReportInsertBo 中这样使用 NotNull 注解

@Data
public class AacmReportInsertBo {
  /**
   * 所属文书类型 (QUERY——查询,COMPLAINT——意见,REPORT——报告)
   */
  private String type;

  /**
   * 性别
   */
  @NotNull(groups = {QueryHandler.class,SecurityHandler.class})
  private String sex;
  
  // 省略
}

也就意味着我希望只有当我的本次请求的返回的处理器为 QueryHandler 或 SecurityHandler 时,才执行这个保证 sex 不为 null 的过滤行为,否则不执行这个过滤行为

我最开始想到了通过实现 ConstraintValidator 的方式来实现我的需求,但是使用这个方法虽然可以实现保证字段不为 Null ,但是不可以实现根据不同的处理器来判断过滤逻辑要不要执行,所以这个实现方案就 pass 了

@Constraint(validatedBy = NotNullAspect.class)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNull {

  String message() default "该字段不能为空";
  Class<?>[] groups() default {};
  Class<? extends Payload>[] payload() default {};

}


@Aspect
@Component
@NoArgsConstructor
public class NotNullAspect implements ConstraintValidator<NotNull, Object> {

  @Override
  public boolean isValid(Object value, ConstraintValidatorContext context) {
    if(value == null){
      return false;
    }
    if(value instanceof String){
      return !((String) value).isEmpty();
    }
    return true;
    
    // 由于没有实现根据不同的处理器类来判断是否进行过滤操作的方案,因此该方法弃用
  }
}

然后我想到使用注解+AOP 的方式来实现我的需求,但是 AOP 提取代码好像又仅仅适用于注解用于修饰方法的情况,我用注解修饰属性并且写入对应的处理逻辑得到的结果是根本就不执行我的代码

@Target(ElementType.FIELD)
@Retention(RUNTIME)
public @interface NotNull {

  Class<?>[] groups() default {};

}

@Aspect
@Component
@NoArgsConstructor
public class NotNullAspect {

  @Pointcut("@annotation(com.org.example.verification.NotNull) && execution(* org.example..*(..))")
  public void fieldNotNullPointCut() {

  }

  @Before("fieldNotNullPointCut()")
  public void before(JoinPoint joinPoint) throws Exception {
    Object target = joinPoint.getTarget();
    Class<?> targetClass = target.getClass();
    String className = joinPoint.getTarget().getClass().getName();
    Class<?> processorClass = Class.forName(className);
    for (Field field : targetClass.getDeclaredFields()) {
      NotNull annotation = field.getAnnotation(NotNull.class);
      if (annotation != null) {
        for (Class<?> procClass : annotation.message()) {
          if (procClass.equals(processorClass)) {
            field.setAccessible(true);
            Object value = field.get(target);
            if (value == null || value.toString().isEmpty()) {
              throw new Exception();
            }
          }
        }
      }
    }
  }
}

上面的代码别说逻辑对不对了,根本就不会执行,打断点会发现这个请求根本不会到断点上

想问下各位有没有什么办法能实现我的需求?就给我一个大概思路就可以了,我会照着这个思路去做,现在最大的问题是我感觉我的思路就有问题导致我不知道该怎么实现我的需求好

1640 次点击
所在节点    Java
12 条回复
guozi1117
87 天前
```java
public void save(AacmReportInsertBo aacmReportInsertBo) {
IReportHandler handler = ReportHandlerFactory.getReportHandlerService(ReportType.getType(aacmReportInsertBo.getType()));
handler.checkNull(aacmReportInsertBo);
handler.handle(aacmReportInsertBo);
}

void checkNull(AacmReportInsertBo aacmReportInsertBo) {
if(StringUtils.isBlank(aacmReportInsertBo.getSex())) {
//do something
}

// 或者基于注解实现
}

```
tiRolin
87 天前
@guozi1117 我知道可以通过非注解的方式来实现我的需求,但是我就想知道基于注解应该要怎么实现,能不能实现这样的
Dream95
87 天前
用 javax.validation 包可以满足你的需求
YoungAD
87 天前
validated group 可以满足这个需求吧
jinxjhin
87 天前
leogt
87 天前
切面作用的位置错了,增加一个注解让切面在这个方法切入 save(AacmReportInsertBo aacmReportInsertBo)。
在切面中获取参数 aacmReportInsertBo ,通过反射获取 sex 字段上 NotNull 注解的 groups ,拿到 group 就可以随便处理了。
bbchannails
87 天前
害怕, 你是框架的入门指导都不看的啊
cobbage
87 天前
注解无感化大部分都是代理实现的
oneisall8955
87 天前
需要手动校验,根据 type 获取对应的 group
baolinliu442k
86 天前
可以套个模板方法


abstract class CheckableIReportHandler implements IReportHandler {
void handleWithCheck(AacmReportInsertBo aacmReportInsertBo) throws IllegalAccessException {
check(aacmReportInsertBo);
handle(aacmReportInsertBo);
}

abstract void check(AacmReportInsertBo aacmReportInsertBo) throws IllegalAccessException;
}


class QueryHandler extends CheckableIReportHandler {
@Override
public void handle(AacmReportInsertBo aacmReportInsertBo) {
System.out.println("handle " + aacmReportInsertBo.toString());
}

@Override
void check(AacmReportInsertBo aacmReportInsertBo) throws IllegalAccessException {
Field[] declaredFields = aacmReportInsertBo.getClass().getDeclaredFields();
for (Field declaredField : declaredFields) {
declaredField.setAccessible(true);
NotNull annotation = declaredField.getAnnotation(NotNull.class);
if (annotation != null) {
Class<?>[] groups = annotation.groups();

boolean flag = false;
for (Class<?> group : groups) {
if (group == this.getClass()) {
flag = true;
}
}

if(flag) {
if (declaredField.get(aacmReportInsertBo) == null) {
throw new IllegalArgumentException(declaredField.getName() + " is null");
}
}
}
}
}
}

public void save(AacmReportInsertBo aacmReportInsertBo) throws IllegalAccessException {
IReportHandler handler = ReportHandlerFactory.getReportHandlerService(ReportType.getType(aacmReportInsertBo.getType()));
// ReportHandlerErrorEnum.HANDLER_NULL_ERROR.isNull(handler);
if(handler instanceof CheckableIReportHandler) {
((CheckableIReportHandler)handler).handleWithCheck(aacmReportInsertBo);
}

}
hapeman
86 天前
使用 AOP 的话,切点不应该是处理器的 handle 方法吗?反射获取参数再遍历字段有无 @NotNull 注解
tiRolin
86 天前
@leogt 谢谢谢谢,根据你的回复我成功实现了我的需求,太感谢你了

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1087475

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX