Java 基于Accept头的Spring MVC-@ExceptionHandler

Java 基于Accept头的Spring MVC-@ExceptionHandler,java,spring,spring-mvc,Java,Spring,Spring Mvc,我有一个HandlerInterceptorAdapter,它拦截所有请求并执行用户授权检查。基本上: @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { User user = ... // get user checkIfAuthorized(user); // throws

我有一个
HandlerInterceptorAdapter
,它拦截所有请求并执行用户授权检查。基本上:

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    User user = ... // get user
    checkIfAuthorized(user); // throws AuthorizationException
    return true;
}
然后我有一个
@ExceptionHandler
用于该
授权异常

@ExceptionHandler(value = AuthorizationException.class) 
public ResponseEntity<String> handleNotAuthorized(AuthorizationException e) {
    // TODO Custom EXCEPTION HANDLER for json/jsp/xml/other types, based on content type
    ResponseEntity<String> responseEntity = new ResponseEntity<>("You are not authorized to access that page.", HttpStatus.UNAUTHORIZED);
    return responseEntity;
}
@ExceptionHandler(值=AuthorizationException.class)
公共响应处理未授权(授权例外e){
//基于内容类型的json/jsp/xml/其他类型的TODO自定义异常处理程序
ResponseEntity ResponseEntity=新ResponseEntity(“您无权访问该页面。”,HttpStatus.UNAUTHORIZED);
返回响应性;
}
如果(未经授权的)请求接受
text/plain
(并且可以很容易地更改为json),则这是可以接受的。 如何为特定的
接受
头创建不同的
@ExceptionHandler
s


@RequestMapping
具有
products()
@ExceptionHandler
是否有类似的功能?

我认为有两种方法:

手动操作

public ResponseEntity<String> handleNotAuthorized(AuthorizationException e, HttpServletRequest request) {
    // TODO Custom EXCEPTION HANDLER for json/jsp/xml/other types, based on content type
    if (/*read header accept from request and build appropiate response*/) {}
    ResponseEntity<String> responseEntity = new ResponseEntity<>("You are not authorized to access that page.", HttpStatus.UNAUTHORIZED);
    return responseEntity;
@ResponseBody
public SomeObject handleNotAuthorized(AuthorizationException e, HttpServletRequest request) {
    // TODO Custom EXCEPTION HANDLER for json/jsp/xml/other types, based on content type
    /* Construct someObject and let Spring MessageConverters transform it to JSON or XML. I don't remember what happens in case of HTML (it should go to a view)*/
    return someObject;

别忘了设置响应的状态代码。

我知道这很晚才出现,但我一直在寻找解决方案,遇到了这个问题,找到了我认为更好的解决方案。您可以在@ExceptionHandler中返回“forward:/error”(返回字符串),将请求转发给

@RequestMapping("/error")
ErrorController {...}
和使用

@RequestMapping(produces = "text/html") 
ModelAndView errorPage() {...}
在ErrorController的一种方法上

@RequestMapping(produces = "application/json") // or no 'produces' attribute for a default
MyJsonObject errorJson() {...} on another.
我认为这是一个非常好的方法,它可能已经存在了,但我在试图查找它时没有找到它


所以基本上@ExceptionHandler对所有人都是一样的,但是它转发给一个控制器,这个控制器可以做一些普通的事情,不是完全相同的用例,而是相同的需求。我用一个定制的HttpMessageConverter实现来解决这个问题

@RestController
@RequestMapping("/foo")
public class MyResource {

    @GetMapping(path = "/{id}", produces = "application/json")
    public ResponseEntity<MyDto> get (@PathVariable(ID) long id)
            throws IOException {

        throw new MyCustomException();
    }

    @GetMapping(path = "/{id}/export", produces = "application/zip")
    public ResponseEntity<byte[]> export (@PathVariable(ID) long id)
            throws IOException {

        throw new MyCustomException();
    }
}

@EnableWebMvc
@配置
@组件扫描({“com.foo”})
公共类应用程序配置实现WebMVCConfiguer{
...
@凌驾

公共无效配置MessageConverters(列表在第二个示例中,它将使用html。我不希望jsp以路径命名。在第一个示例中,我不想为
text/html
返回视图名称,是否可以使用
ResponseEntity
?看起来ResponseEntity更倾向于REST响应。您应该返回ModelAndView(或仅返回视图)对于这种情况。您建议将返回类型设置为简单的
Object
,并根据标题返回其中的一个?是的。在这种情况下,Spring不关心类型安全。它会立即检查对象是什么。这很有意义,在我的情况下也起作用。在返回“forward:/error”之前,我必须调用
request.setAttribute(“javax.servlet.error.status_code”,HttpStatus.NOT_FOUND.value());
(根据需要替换状态),因为AbstractErrorController使用该属性确定状态,然后将其映射到错误页。如果我没有设置该属性,它将默认为“200”“很明显,我没有为其提供错误页面。这是正确的。我后来还发现,在ErrorController中需要一个额外的@ExceptionHandler方法-确保它不会在错误控制器调用期间发生异常时通过将请求转发给self来循环(例如ClientBort,无论您的控制器有多安全都可能发生)谢谢,我实际上捕获到了Spring在从我的ErrorController返回响应后引发的HttpMediaTypeNotAcceptableException。这是在iOS Firefox(FxiOS)的不规则favicon请求上发生的。
@ControllerAdvice
public class MyCustomExceptionHandler {

    @ResponseBody
    @ExceptionHandler
    @ResponseStatus(BAD_REQUEST)
    public JsonAPIErrorDocument handleException (MyCustomException e) {

        return ....;
    }
}
public class JsonAPIErrorDocumentToByteArrayMessageConverter extends AbstractHttpMessageConverter {

    public ErrorDocumentToByteArrayMessageConverter () {

        super(new MediaType("application", "zip"), MediaType.ALL);
    }

    @Override
    protected boolean supports (Class clazz) {

        return JsonAPIErrorDocument.class == clazz;
    }

    @Override
    protected Object readInternal (Class clazz, HttpInputMessage inputMessage)
            throws IOException,
            HttpMessageNotReadableException {

        return new byte[0];
    }

    @Override
    protected void writeInternal (Object t, HttpOutputMessage outputMessage)
            throws IOException,
            HttpMessageNotWritableException {

    }
}
@EnableWebMvc
@Configuration
@ComponentScan({ "com.foo" })
public class ApplicationConfig implements WebMvcConfigurer {

    ...

    @Override
    public void configureMessageConverters (List<HttpMessageConverter<?>> converters) {

        converters.add(new MappingJackson2HttpMessageConverter(objectMapper));
        converters.add(new ByteArrayHttpMessageConverter());
        converters.add(new JsonAPIErrorDocumentToByteArrayMessageConverter());
    }

    ...
}