【问题标题】:How do I create an annotation in Java and access variables it creates?如何在 Java 中创建注释并访问它创建的变量?
【发布时间】:2021-08-30 15:11:29
【问题描述】:

我正在使用 Spring Boot 创建一个非常标准的 REST 服务。这部分意味着很多方法将返回结果页面,因此方法的输入将需要页码、页面大小等 - 例如:

    @GetMapping(
            value = "",
            produces = MediaType.APPLICATION_JSON_VALUE
    )
    @ResponseBody
    ResponseEntity readAll(
            @RequestParam("pn") Integer pageNumber,
            @RequestParam("ps") Integer pageSize,
            @RequestParam("sc") String sortColumn,
            @RequestParam("so") String sortOrder
    ) {

然后在方法本身中,我们将验证参数(例如,确保页面大小

由于这种模式会一遍又一遍地重复,我只想做一个封装所有这些的注释,但我不确定如何将 4 个 RequestParam 变量转换为单个 Pageable 对象,或者如何访问在方法体的注解中创建的对象。

我尝试了一些基本的注释工作,例如

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ServicePageSizeValidator.class)
public @interface ServicePageSize {
    Integer DEFAULT_MAX = 100;
    String max() default "100";
}

class ServicePageSizeValidator
        implements ConstraintValidator<ServicePageSize, Integer> {
    private static final Logger LOGGER =
            LoggerFactory.getLogger(ServicePageSizeValidator.class);

    private Integer max = ServicePageSize.DEFAULT_MAX;

    @Override
    public void initialize(final ServicePageSize annotation) {
        try {
            max = Integer.valueOf(annotation.max());
        } catch (Exception e) {
            LOGGER.warn(
                    "Exception caught trying to parse page size " +
                            "constraint {}.", annotation.max(), e
            );
            max = ServicePageSize.DEFAULT_MAX;
        }
    }

    @Override
    public boolean isValid(final Integer pageSize,
                           final ConstraintValidatorContext context) {
        try {
            if (pageSize == null) {
                throw new InvalidPageSizeException("Null page size.");
            }
            if (pageSize > max) {
                throw new InvalidPageSizeException(
                        "Page size " + pageSize + "larger than maximum " +
                                "allowed (" + max + ")."
                );
            }
        } catch (Exception e) {
            LOGGER.warn("Invalid page size.", e);
            return false;
        }
        return true;
    }
}

这通常似乎有效,但它只对单个参数进行操作,如果参数错误,整个注释就会失败 - 我无法访问有关方法中验证的信息,假设它通过了。

【问题讨论】:

    标签: java spring-boot annotations


    【解决方案1】:

    正如@DrTeeth 所提到的,Spring 已经提供了很棒的分页功能,所以我会坚持使用它,因为它为您提供了还可以与存储库一起使用的选项。 Pageable 绝对是非常有用的。

    一般来说,您可以使用 JEE Validation API 执行此类验证。此外,您可以使用 Spring Resolvers 以非常简单的方式在每个需要它们的控制器中提供参数。让我给你举个例子。首先,让我们创建一个映射参数的类:

    public class Pagination {
        @Max(100)
        int pageNumber;
    
        int pageSize;
        String sortColumn;
        String sortOrder;
    }
    

    如您所见,我已经在使用验证 API。您可以通过在 pom 中添加此依赖项来将功能添加到 Spring 项目:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    

    出于示例的目的,我只在页码上添加了验证。 您现在可以创建一个界面。我稍后会解释它的用途。

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.PARAMETER)
    public @interface PaginationConfig {
    }
    

    这将告诉 Spring 它可以期望使用 PaginationConfig 作为方法的参数装饰器。 现在是我们的解析器的时候了:

    public class PaginationResolver implements HandlerMethodArgumentResolver {
    
        @Override
        public boolean supportsParameter(MethodParameter methodParameter) {
            return methodParameter.getParameterAnnotation(PaginationConfig.class) != null;
        }
    
        @Override
        public Object resolveArgument(
                MethodParameter methodParameter,
                ModelAndViewContainer modelAndViewContainer,
                NativeWebRequest nativeWebRequest,
                WebDataBinderFactory webDataBinderFactory) throws BadRequestException {
            Pagination pagination = new Pagination();
            HttpServletRequest request
                    = (HttpServletRequest) nativeWebRequest.getNativeRequest();
    
            try {
                pagination.setPageNumber(Integer.parseInt(request.getParameter("pn")));
                pagination.setPageSize(Integer.parseInt(request.getParameter("ps")));
            } catch (NumberFormatException e) {
                throw new BadRequestException();
            }
            pagination.setSortColumn(request.getParameter("sc"));
            pagination.setSortOrder(request.getParameter("so"));
    
            return pagination;
        }
    }
    

    它绑定到我们刚刚创建的接口,它从请求中读取参数值,创建一个分页对象。请注意,验证尚未发生。我们只是告诉 Spring 如何读取这些值并捕获 NumberFormatException,因此我们可以将其转换为自定义异常(在此示例中我称之为 BadRequestException,我将映射到 BAD_REQUEST HTTP 响应代码)。

    我们快完成了。我们现在必须添加一些配置:

    @Configuration
    public class MyConfig implements WebMvcConfigurer {
        @Override
        public void addArgumentResolvers(
                List<HandlerMethodArgumentResolver> argumentResolvers) {
            argumentResolvers.add(new PaginationResolver());
        }
    
        @Bean
        public MethodValidationPostProcessor methodValidationPostProcessor() {
            return new MethodValidationPostProcessor();
        }
    }
    

    通过这种方式,我们告诉 Spring 它必须使用自定义解析器并且必须验证方法的参数。 现在是时候进入我们的控制器了。我给你举个例子:

    @RestController
    @Validated
    public class MyController {
    
        @GetMapping
        public String validateAndPaginate(@Valid @PaginationConfig Pagination pagination) {
            return "hello world " + pagination.getPageSize();
        }
    }
    

    注意事项:

    • 我们需要在类级别使用@Validated;
    • 我们需要在参数级别使用@Valid;
    • 我们正在用我们创建的接口@PaginationConfig 来装饰参数。 Spring 知道如何找到需要在该对象中绑定的值,因为我们已经告诉它使用我们的解析器;
    • 我们正在使用验证 API,因此所有内容都将根据我们放入 Pagination 类的注释进行验证;
    • 如果验证失败,Spring 会给我们一个 500 错误,但您可以轻松地将其映射到您想要的任何内容。

    【讨论】:

      【解决方案2】:

      这可能仍然是一个合理的问题,因为需要的其他注释可能存在类似的情况。但是对于分页,至少 Spring Boot 显然已经涵盖了我。

      (外部链接):https://reflectoring.io/spring-boot-paging/

      基本上:

      ResponseEntity readAll(Pageable pageable)
      

      会起作用,并且可以通过多种方式配置 URL 参数名称,例如在 application.properties 中:

      spring.data.web.pageable.size-parameter=size
      spring.data.web.pageable.page-parameter=page
      spring.data.web.pageable.default-page-size=20
      spring.data.web.pageable.max-page-size=2000
      

      或通过注释:

      ResponseEntity readAll(
          @PageableDefault(page = 0, size = 20) Pageable pageable
      )
      

      我将保持开放状态,这不是公认的答案,因为我觉得如何做到这一点的更普遍的问题仍然有效。但看起来答案可能是创建一个将请求参数自我映射到自身的类,然后在传入的单个对象上使用注释来配置它。

      【讨论】:

      • 是的,正如我所怀疑的,有人会想出一个很好的通用解释。使用 Gregorio 的答案。
      猜你喜欢
      • 2011-04-06
      • 1970-01-01
      • 1970-01-01
      • 2016-10-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-19
      • 2013-03-25
      相关资源
      最近更新 更多