V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
tiRolin
V2EX  ›  Java

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

  •  
  •   tiRolin · 35 天前 · 1523 次点击
    这是一个创建于 35 天前的主题,其中的信息可能已经有所发展或是发生改变。

    目前我有一个 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();
                }
              }
            }
          }
        }
      }
    }
    

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

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

    12 条回复    2024-11-08 23:40:19 +08:00
    guozi1117
        1
    guozi1117  
       35 天前
    ```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
        2
    tiRolin  
    OP
       35 天前
    @guozi1117 我知道可以通过非注解的方式来实现我的需求,但是我就想知道基于注解应该要怎么实现,能不能实现这样的
    Dream95
        3
    Dream95  
       35 天前
    用 javax.validation 包可以满足你的需求
    YoungAD
        4
    YoungAD  
       35 天前
    validated group 可以满足这个需求吧
    leogt
        6
    leogt  
       35 天前
    切面作用的位置错了,增加一个注解让切面在这个方法切入 save(AacmReportInsertBo aacmReportInsertBo)。
    在切面中获取参数 aacmReportInsertBo ,通过反射获取 sex 字段上 NotNull 注解的 groups ,拿到 group 就可以随便处理了。
    bbchannails
        7
    bbchannails  
       35 天前
    害怕, 你是框架的入门指导都不看的啊
    cobbage
        8
    cobbage  
       35 天前 via Android
    注解无感化大部分都是代理实现的
    oneisall8955
        9
    oneisall8955  
       34 天前 via Android
    需要手动校验,根据 type 获取对应的 group
    baolinliu442k
        10
    baolinliu442k  
       34 天前
    可以套个模板方法


    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
        11
    hapeman  
       34 天前
    使用 AOP 的话,切点不应该是处理器的 handle 方法吗?反射获取参数再遍历字段有无 @NotNull 注解
    tiRolin
        12
    tiRolin  
    OP
       33 天前
    @leogt 谢谢谢谢,根据你的回复我成功实现了我的需求,太感谢你了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3018 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 21ms · UTC 11:23 · PVG 19:23 · LAX 03:23 · JFK 06:23
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.