对路径变量使用MVC类型转换,并在空参数上返回404


问题内容

我的控制器。注意自定义@Exists注释:

@RestController
public class ClientApiController {

    @RequestMapping(path = "/{client}/someaction", method = RequestMethod.GET)
    String handleRequest(@Exists Client client) {
        // ...
    }
}

Exists注释:

/**
 * Indicates that a controller request mapping method parametet should not be
 * null. This is meant to be used on model types to indicate a required entity.
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Exists {}

转换器将Stringpath变量从转换为Client实例:

@Component
public class StringToClient implements Converter<String, Client> {

    @Autowired
    private ClientDAO clientDAO;

    @Override
    public Client convert(String source) {
        return clientDAO.getClientById(source);
    }
}

ResourceNotFoundException用于触发404 的异常

@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
}

我的控制器方法根据需要接收转换后的客户端。如果clientURL中使用的ID与客户端匹配,则一切正常。如果id不匹配,则controller方法中的client参数为
null 空(使用默认构造函数)handle()

我现在无法工作的是 声明式
检查Client是否不为空(即,该ID指向现有的客户端)。如果为null,ResourceNotFoundException则应引发a。检查方法主体中的参数是否为null并抛出我的自定义ResourceNotFoundException很容易,但是重复。同样,此声明方法应适用于实现接口的所有模型类,ModelWithId因此可以用于多种模型类型。

我已经搜索过Spring文档,但还没有找到实现该目标的方法。我需要在类型转换之后和控制器handleRequest方法之前的某个位置插入一些处理。

我正在使用Spring Boot 1.3.3


问题答案:

在类型转换之后,在控制器的方法之前,需要进行验证。您可以实现自定义验证器并在其中引发异常。将新的验证器添加到DataBinder,并将方法的参数标记为@Validated

@RestController
public class ClientApiController {

    @InitBinder
    public void initBinder(DataBinder binder){
        binder.addValidators(new Validator() {
            @Override
            public boolean supports(Class<?> aClass) {
                return aClass==Client.class;
            }

            @Override
            public void validate(Object o, Errors errors) {
                Client client = (Client)o;
                if(client.getId()==null) throw new ResourceNotFoundException();
            }
        });
    }

    @RequestMapping(path = "/{client}/someaction", method = RequestMethod.GET)
    String handleRequest(@Validated @Exists Client client) {
        // ...
    }

    @RequestMapping(path = "/{client}/anotheraction", method = RequestMethod.GET)
    String handleAnotherRequest(@Validated @Exists Client client) {
        // ...
    }
}

当然,您可以将验证器声明为单独的类,并在其他控制器中重复使用它。实际上,您可以在转换器中引发异常,但是有可能在应用程序的其他位置毫无例外地需要转换。