Java SpringRESTAPI多请求参数与控制器实现
我想知道在给定多个请求参数的GET请求的情况下,控制器的正确实现方式。在我对REST的理解中,使用一个端点和额外的过滤/排序参数要比使用多个端点(每种情况一个端点)要好得多。我只是想知道这种端点的维护性和可扩展性。请查看下面的示例:Java SpringRESTAPI多请求参数与控制器实现,java,spring,rest,api,spring-boot,Java,Spring,Rest,Api,Spring Boot,我想知道在给定多个请求参数的GET请求的情况下,控制器的正确实现方式。在我对REST的理解中,使用一个端点和额外的过滤/排序参数要比使用多个端点(每种情况一个端点)要好得多。我只是想知道这种端点的维护性和可扩展性。请查看下面的示例: @RestController @RequestMapping("/customers") public class CustomerController { @Autowired private CustomerRepository custom
@RestController
@RequestMapping("/customers")
public class CustomerController {
@Autowired
private CustomerRepository customerRepo;
@GetMapping
public Page<Customer> findCustomersByFirstName(
@RequestParam("firstName") String firstName,
@RequestParam("lastName") String lastName,
@RequestParam("status") Status status, Pageable pageable) {
if (firstName != null) {
if (lastName != null) {
if (status != null) {
return customerRepo.findByFirstNameAndLastNameAndStatus(
firstName, lastName, status, pageable);
} else {
return customerRepo.findByFirstNameAndLastName(
firstName, lastName, pageable);
}
} else {
// other combinations omitted for sanity
}
} else {
// other combinations omitted for sanity
}
}
}
@RestController
@请求映射(“/customers”)
公共类客户控制器{
@自动连线
私人客户存储位置客户存储;
@GetMapping
公共页面findCustomersByFirstName(
@RequestParam(“firstName”)字符串firstName,
@RequestParam(“lastName”)字符串lastName,
@RequestParam(“状态”)状态,可分页(可分页){
if(firstName!=null){
if(lastName!=null){
如果(状态!=null){
返回customerRepo.findByFirstNameAndLastNameAndStatus(
名字、姓氏、状态、可分页);
}否则{
返回customerRepo.FindByFirstName和LastName(
名字、姓氏、可分页);
}
}否则{
//为保持理智,省略了其他组合
}
}否则{
//为保持理智,省略了其他组合
}
}
}
这样的端点似乎使用起来非常方便(参数的顺序无关紧要,所有参数都是可选的…),但维护这样的端点看起来像是地狱(组合的数量可能非常多)
我的问题是——处理这种事情的最佳方式是什么?在“专业”API中它是如何设计的?实际上,您自己回答了一半的答案,查询参数用于过滤目的,正如您在代码中看到的,这将通过GET请求得到允许。但是关于验证的问题是一种权衡 比如,;如果不希望进行此类检查,可以依赖于强制
required=true
,这是@RequestParam
中的默认值,并立即在响应中处理它
或者,您也可以使用支持@Valid的@RequestBody来获得更清晰的错误信息;比如说
@PostMapping(value=“/order”)
public ResponseEntity submitRequest(@RequestBody@Valid OrderRequest RequestBody,
错误)引发异常{
if(errors.hasErrors())
抛出新的BusinessException(“ERR-0000”,HttpStatus.NOT_ACCEPTABLE);
返回新的ResponseEntity(sendOrder(requestBody),HttpStatus.CREATED);
}
//你的Pojo
公共类OrderRequest{
@NotNull(消息=“通道是必需的”)
专用字符串通道;
@NotNull(消息=“需要第三方ID”)
私人长派对;
}
有关更多信息,请查看此
这种方式将使您的验证机制从控制器层到业务层解耦。这反过来将节省大量的样板代码,但正如您所注意到的,更改为POST
因此,一般来说,您的问题没有直接的答案,简短的答案是这取决于您的情况,因此选择任何对您来说简单、功能良好、维护较少的方法将是最佳做法
处理这样的事情最好的方法是什么
处理它的最好方法是使用已有的工具。当您使用SpringBoot时,我假设SpringDataJPA随后为SpringDataJPA启用QueryDsl支持和web支持扩展
然后,您的控制器简单地变成:
@GetMapping
public Page<Customer> searchCustomers(
@QuerydslPredicate(root = Customer.class) Predicate predicate, Pageable pageable) {
return customerRepo.findBy(predicate, pageable);
}
@GetMapping
公众网页搜寻客户(
@QuerydslPredicate(root=Customer.class)谓词,可分页(Pageable){
返回customerRepo.findBy(谓词,可分页);
}
并且您的存储库仅扩展为支持QueryDsl:
public interface CustomerRepository extends PagingAndSortingRepository<Customer, Long>,
QueryDslPredicateExecutor<Customer>{
}
公共接口CustomerRepository扩展了分页和排序存储库,
QueryDSL谓词执行器{
}
您现在可以通过参数的任意组合进行查询,而无需编写任何进一步的代码。
早安。我不能称自己为专业人士,但这里有一些技巧可以让这个控制器看起来更好
- 使用DTO而不是使用一组参数
@GetMapping
public Page<Customer> findCustomersByFirstName(CustomerDTO customerDTO, Pageable pageable) {
...
}
不要忘记在控制器中的DTO参数之前添加@Valid注释
@PostMapping(value = "/findCustomer",produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> findCustomersByFirstName(@Valid @RequestBody Customer customer){
return customerRepo.findByFirstNameAndLastNameAndStatus(customer.getFirstName, customer.getLastName(), customer.getStatus(), pageable);
- 使用规格,而不是带if-else的此块
- 使用服务层,不需要从控制器调用存储库
@GetMapping
公共页findCustomersByFirstName(@Valid CustomerDTO CustomerDTO,BindingResult BindingResult,Pageable Pageable){
if(bindingResult.hasErrors()){
//错误处理
}
返回customerService.findAllBySpecification(新CustomerSpecification(customerDTO));
}
您的控制器不应该包含任何与实体或某些业务相关的逻辑。它只涉及处理请求/错误、重定向、视图等。最好是有一个带有此类验证的POST请求,而不是GET请求。您可以对控制器使用以下方法
@PostMapping(value = "/findCustomer",produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> findCustomersByFirstName(@Valid @RequestBody Customer customer){
return customerRepo.findByFirstNameAndLastNameAndStatus(customer.getFirstName, customer.getLastName(), customer.getStatus(), pageable);
}
并添加控制器级别的异常建议以返回错误响应
@ControllerAdvice
public class ControllerExceptionAdvice {
private static final String EXCEPTION_TRACE = "Exception Trace:";
private static Logger log = LoggerFactory.getLogger(ControllerExceptionAdvice.class);
public ControllerExceptionAdvice() {
super();
}
@ExceptionHandler({ BaseException.class })
public ResponseEntity<String> handleResourceException(BaseException e, HttpServletRequest request,
HttpServletResponse response) {
log.error(EXCEPTION_TRACE, e);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
BaseExceptionResponse exceptionDto = new BaseExceptionResponse(e);
return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, e.getHttpStatus());
}
@ExceptionHandler({ Exception.class })
public ResponseEntity<String> handleException(Exception e, HttpServletRequest request,
HttpServletResponse response) {
log.error(EXCEPTION_TRACE, e);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
BaseExceptionResponse exceptionDto = new BaseExceptionResponse(httpStatus.value(),
ExceptionMessages.INTERNAL_DEFAULT);
return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, httpStatus);
}
@ExceptionHandler({ MethodArgumentNotValidException.class })
public ResponseEntity<String> handleValidationException(MethodArgumentNotValidException e,
HttpServletRequest request, HttpServletResponse response) {
log.error(EXCEPTION_TRACE, e);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
ValidationException validationEx = new ValidationException(e);
BaseExceptionResponse exceptionDto = new BaseExceptionResponse(validationEx);
return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, validationEx.getHttpStatus());
}
@ExceptionHandler({ HttpMediaTypeNotSupportedException.class, InvalidMimeTypeException.class,
InvalidMediaTypeException.class, HttpMessageNotReadableException.class })
public ResponseEntity<String> handleMediaTypeNotSupportException(Exception e, HttpServletRequest request,
HttpServletResponse response) {
log.error(EXCEPTION_TRACE, e);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
BaseExceptionResponse exceptionDto = new BaseExceptionResponse(httpStatus.value(),
ExceptionMessages.BAD_REQUEST_DEFAULT);
return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, httpStatus);
}
@ExceptionHandler({ HttpRequestMethodNotSupportedException.class })
public ResponseEntity<String> handleMethodNotSupportException(Exception e, HttpServletRequest request,
HttpServletResponse response) {
log.error(EXCEPTION_TRACE, e);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpStatus httpStatus = HttpStatus.METHOD_NOT_ALLOWED;
BaseExceptionResponse exceptionDto = new BaseExceptionResponse(httpStatus.value(),
ExceptionMessages.METHOD_NOT_ALLOWED);
return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, httpStatus);
}
@ExceptionHandler({ MissingServletRequestParameterException.class })
public ResponseEntity<String> handleMissingServletRequestParameterException(Exception e, HttpServletRequest request,
HttpServletResponse response) {
log.error(EXCEPTION_TRACE, e);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
BaseExceptionResponse exceptionDto = new BaseExceptionResponse(httpStatus.value(),
ExceptionMessages.BAD_REQUEST_DEFAULT);
return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, httpStatus);
}
@ControllerAdvice
公共类控制器异常通知{
私有静态最终字符串异常\u TRACE=“异常跟踪:”;
私有静态记录器log=LoggerFactory.getLogger(ControllerExceptionAdvice.class);
公共控制例外广告(){
超级();
}
@ExceptionHandler({BaseException.class})
公共响应处理人员
@PostMapping(value = "/findCustomer",produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> findCustomersByFirstName(@Valid @RequestBody Customer customer){
return customerRepo.findByFirstNameAndLastNameAndStatus(customer.getFirstName, customer.getLastName(), customer.getStatus(), pageable);
public class Customer {
private String firstName;
private String lastName;
private String status;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName= firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName= lastName;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status= status;
}
public LivenessInputModel(String firstName, String lastName, String status) {
this.firstName= firstName;
this.lastName= lastName;
this.status= status;
}
public LivenessInputModel() {
}
@ControllerAdvice
public class ControllerExceptionAdvice {
private static final String EXCEPTION_TRACE = "Exception Trace:";
private static Logger log = LoggerFactory.getLogger(ControllerExceptionAdvice.class);
public ControllerExceptionAdvice() {
super();
}
@ExceptionHandler({ BaseException.class })
public ResponseEntity<String> handleResourceException(BaseException e, HttpServletRequest request,
HttpServletResponse response) {
log.error(EXCEPTION_TRACE, e);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
BaseExceptionResponse exceptionDto = new BaseExceptionResponse(e);
return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, e.getHttpStatus());
}
@ExceptionHandler({ Exception.class })
public ResponseEntity<String> handleException(Exception e, HttpServletRequest request,
HttpServletResponse response) {
log.error(EXCEPTION_TRACE, e);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
BaseExceptionResponse exceptionDto = new BaseExceptionResponse(httpStatus.value(),
ExceptionMessages.INTERNAL_DEFAULT);
return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, httpStatus);
}
@ExceptionHandler({ MethodArgumentNotValidException.class })
public ResponseEntity<String> handleValidationException(MethodArgumentNotValidException e,
HttpServletRequest request, HttpServletResponse response) {
log.error(EXCEPTION_TRACE, e);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
ValidationException validationEx = new ValidationException(e);
BaseExceptionResponse exceptionDto = new BaseExceptionResponse(validationEx);
return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, validationEx.getHttpStatus());
}
@ExceptionHandler({ HttpMediaTypeNotSupportedException.class, InvalidMimeTypeException.class,
InvalidMediaTypeException.class, HttpMessageNotReadableException.class })
public ResponseEntity<String> handleMediaTypeNotSupportException(Exception e, HttpServletRequest request,
HttpServletResponse response) {
log.error(EXCEPTION_TRACE, e);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
BaseExceptionResponse exceptionDto = new BaseExceptionResponse(httpStatus.value(),
ExceptionMessages.BAD_REQUEST_DEFAULT);
return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, httpStatus);
}
@ExceptionHandler({ HttpRequestMethodNotSupportedException.class })
public ResponseEntity<String> handleMethodNotSupportException(Exception e, HttpServletRequest request,
HttpServletResponse response) {
log.error(EXCEPTION_TRACE, e);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpStatus httpStatus = HttpStatus.METHOD_NOT_ALLOWED;
BaseExceptionResponse exceptionDto = new BaseExceptionResponse(httpStatus.value(),
ExceptionMessages.METHOD_NOT_ALLOWED);
return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, httpStatus);
}
@ExceptionHandler({ MissingServletRequestParameterException.class })
public ResponseEntity<String> handleMissingServletRequestParameterException(Exception e, HttpServletRequest request,
HttpServletResponse response) {
log.error(EXCEPTION_TRACE, e);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
BaseExceptionResponse exceptionDto = new BaseExceptionResponse(httpStatus.value(),
ExceptionMessages.BAD_REQUEST_DEFAULT);
return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, httpStatus);
}
@Repository
interface CustomerRepository extends JpaSpecificationExecutor<Customer> {
}
@RestController
@RequestMapping("/customers")
public class CustomerController {
private final CustomerRepository repository;
@Autowired
public CustomerController(CustomerRepository repository) {
this.repository = repository;
}
@GetMapping
public Page<Customer> findAll(@RequestBody HashMap<String, String> filters, Pageable pageable) {
return repository.findAll(QueryUtils.toSpecification(filters), pageable);
}
}
class QueryUtils {
public static Specification<Customer> toSpecification(Map<String, String> filters) {
Specification<Customer> conditions = null;
for (Map.Entry<String, String> entry : filters.entrySet()) {
Specification<Customer> condition = Specification.where((root, query, cb) -> cb.equal(root.get(entry.getKey()), entry.getValue()));
if (conditions == null) {
conditions = condition;
} else {
conditions = conditions.and(condition);
}
}
return conditions;
}
}