有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

ExceptionSpring和ExceptTypeTmpHandler@java异常

我有一个用@ControllerAdvice注释的类,其中有一个方法:

@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
@ResponseBody
public ExceptionInfo resourceNotFoundHandler(ResourceNotFoundException ex) {
    List<ErrorContent> errors = new ArrayList<>();
    errors.add(new ErrorContent(ExceptionsCodes.NOT_FOUND_CODE, null,
            "test"));
    return fillExceptionInfo(HttpStatus.NOT_FOUND, errors, ex);
}

以下是fillExceptionInfo

public ExceptionInfo fillExceptionInfo(HttpStatus status, List<ErrorContent> errors, 
        Exception ex) {
    String msg = ex.getMessage();

    return new ExceptionInfo(status.toString(), errors, (msg != null && !msg.equals(""))
            ? ex.getMessage()
            : ExceptionUtils.getFullStackTrace(ex));
}

当web客户机发送一些无法找到的json数据请求时,此方法正常工作。但当服务器收到镜像请求时,会抛出一个HttpMediaTypeNotAcceptableException,而不是我的异常。我知道这是因为错误的内容类型造成的,但是我如何解决这个问题呢

更新

我的目标是在json数据和文件的两种情况下抛出ResourceNotFoundException

我得到的异常(因此它是从AbstractMessageConverterMethodProcessor抛出的):

ERROR o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver - doResolveHandlerMethodException - Failed to invoke @ExceptionHandler method: public com.lia.utils.GlobalExceptionHandler$ExceptionInfo com.lia.utils.GlobalExceptionHandler.resourceNotFoundHandler(com.lia.app.controllers.exceptions.ResourceNotFoundException) 
    org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
        at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:168) ~[spring-webmvc-4.1.1.RELEASE.jar:4.1.1.RELEASE]
        at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:101) ~[spring-webmvc-4.1.1.RELEASE.jar:4.1.1.RELEASE]
        at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:198) ~[spring-webmvc-4.1.1.RELEASE.jar:4.1.1.RELEASE]
        at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:71) ~[spring-web-4.1.1.RELEASE.jar:4.1.1.RELEASE]
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:122) ~[spring-webmvc-4.1.1.RELEASE.jar:4.1.1.RELEASE]
        at org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver.doResolveHandlerMethodException(ExceptionHandlerExceptionResolver.java:362) ~[spring-webmvc-4.1.1.RELEASE.jar:4.1.1.RELEASE]
        at org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver.doResolveException(AbstractHandlerMethodExceptionResolver.java:60) [spring-webmvc-4.1.1.RELEASE.jar:4.1.1.RELEASE]
        at org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver.resolveException(AbstractHandlerExceptionResolver.java:138) [spring-webmvc-4.1.1.RELEASE.jar:4.1.1.RELEASE]
        at org.springframework.web.servlet.DispatcherServlet.processHandlerException(DispatcherServlet.java:1167) [spring-webmvc-4.1.1.RELEASE.jar:4.1.1.RELEASE]
        at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1004) [spring-webmvc-4.1.1.RELEASE.jar:4.1.1.RELEASE]
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:955) [spring-webmvc-4.1.1.RELEASE.jar:4.1.1.RELEASE]
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:877) [spring-webmvc-4.1.1.RELEASE.jar:4.1.1.RELEASE]
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:966) [spring-webmvc-4.1.1.RELEASE.jar:4.1.1.RELEASE]
        at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:857) [spring-webmvc-4.1.1.RELEASE.jar:4.1.1.RELEASE]
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:687) [javax.servlet-api-3.1.0.jar:3.1.0]
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:842) [spring-webmvc-4.1.1.RELEASE.jar:4.1.1.RELEASE]
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:790) [javax.servlet-api-3.1.0.jar:3.1.0]
        at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:717) [jetty-servlet-9.1.1.v20140108.jar:9.1.1.v20140108]
        at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1644) [jetty-servlet-9.1.1.v20140108.jar:9.1.1.v20140108]
....

共 (4) 个答案

  1. # 1 楼答案

    你的ExceptionInfo类是什么样子的?在@ControllerAdvice注释类中定义了几个异常处理程序之后,我遇到了类似的问题。当异常发生时,它被捕获,尽管响应没有返回并且org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation被抛出

    我发现这个问题是由以下事实造成的:我没有将getter方法添加到我的ErrorResponse类中。在添加getter方法(这个类是不可变的,所以没有setter方法)之后,一切都很顺利

  2. # 2 楼答案

    问题在于请求的内容类型与返回的对象不兼容。请参阅my response了解如何配置ContentNegotiationConfigurer,以便Spring根据您的需要确定请求的内容类型(查看路径扩展、URL参数或Accept头)

    根据请求的内容类型的确定方式,当客户端请求图像时,您有以下选项:

    • 如果请求的内容类型由Accept头确定,并且如果客户端可以/想要处理JSON响应而不是图像数据,则客户端应使用Accept: image/*, application/json发送请求。这样Spring就知道它可以安全地返回图像字节数据或错误JSON消息
    • 在任何其他情况下,最好的解决方案是只返回HTTP错误代码,而不返回任何错误消息。在控制器中,可以通过以下几种方式实现:

    直接在响应上设置错误代码

    public byte[] getImage(HttpServletResponse resp) {
        try {
            // return your image
        } catch (Exception e) {
            resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
    }
    

    使用ResponseEntity

    public ResponseEntity<?> getImage(HttpServletResponse resp) {
        try {
            byte[] img = // your image
            return ReponseEntity.ok(img);
        } catch (Exception e) {
            return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
    

    在该控制器中使用一个单独的@ExceptionHandler方法,它将覆盖默认的Spring异常处理。这假设您有一个专用的异常类型来处理图像请求,或者有一个单独的控制器来处理图像。否则,异常处理程序也将处理来自该控制器中其他端点的异常

  3. # 3 楼答案

    (基于Sander Verhagen和Adam Michalik之前的回答)

    最后,我编写了一种避免两次异常结果的方法:在已经执行异常处理程序方法时发生内容协商失败(“Accept”无效)

    我告诉spring,如果请求媒体类型格式不好,请将其解析为应用程序_JSON。 因此,通过这种方式,在生成异常处理程序响应时,不再出现“动态”内容协商错误

    @Configuration
    public class MyWebMvcConfigurer implements WebMvcConfigurer {
        @Override
        public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
            configurer.strategies(singletonList(
                    new EnsureValidMediaTypesNegotiationStrategy()
            ));
        }
    }
    
    @Slf4j
    class EnsureValidMediaTypesNegotiationStrategy extends HeaderContentNegotiationStrategy {
        @Override
        public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
            try {
                return super.resolveMediaTypes(request);
            } catch (Exception negotiationException) {
                // this fix is used to avoid twice exceptions due to media negotiation
                log.info("Client Accept header is not recognized (json response enforced):{}", negotiationException.getMessage());
                return singletonList(MediaType.APPLICATION_JSON);
            }
    
        }
    }
    

    注意:您可以使用此值作为“接受”标题向控制器添加一些测试:

    ../../../../../../../../../../../../../e*c/h*s*s{{
    
  4. # 4 楼答案

    如果您愿意忽略Accept标题中表达的客户的明确指示,您可以修改内容协商策略,如下所示:

    /**
     * Content negotiation strategy that adds the {@code application/json} media type if not present in the "Accept"
     * header of the request.
     * <p>
     * Without this media type, Spring refuses to return errors as {@code application/json}, and thus not return them at
     * all, which leads to a HTTP status code 406, Not Acceptable
     */
    class EnsureApplicationJsonNegotiationStrategy extends HeaderContentNegotiationStrategy {
        @Override
        public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
            List<MediaType> mediaTypes = new ArrayList<>(super.resolveMediaTypes(request));
            if (notIncludesApplicationJson(mediaTypes)) {
                mediaTypes.add(MediaType.APPLICATION_JSON);
            }
            return mediaTypes;
        }
    
        private boolean notIncludesApplicationJson(List<MediaType> mediaTypes) {
            return mediaTypes.stream()
                    .noneMatch(mediaType -> mediaType.includes(MediaType.APPLICATION_JSON));
        }
    }
    

    @Configuration类中使用此选项,如下所示:

    @Configuration
    public class ContentNegotiationConfiguration implements WebMvcConfigurer {
        @Override
        public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
            configurer.strategies(List.of(
                    new EnsureApplicationJsonNegotiationStrategy()
            ));
        }
    }
    

    单元测试(使用JUnit 5,Mockito):

    @ExtendWith(MockitoExtension.class)
    public class EnsureApplicationJsonNegotiationStrategyTest {
        @Mock
        private NativeWebRequest request;
    
        @InjectMocks
        private EnsureApplicationJsonNegotiationStrategy subject;
    
        @Test
        public void testAddsApplicationJsonToAll() throws HttpMediaTypeNotAcceptableException {
            when(request.getHeaderValues(HttpHeaders.ACCEPT)).thenReturn(new String[]{"*/*"});
    
            assertThat(subject.resolveMediaTypes(request), contains(
                    MediaType.ALL // this includes application/json, so... fine
            ));
        }
    
        @Test
        public void testAddsApplicationJsonToEmpty() throws HttpMediaTypeNotAcceptableException {
            when(request.getHeaderValues(HttpHeaders.ACCEPT)).thenReturn(new String[0]);
    
            assertThat(subject.resolveMediaTypes(request), contains(
                    MediaType.ALL // that's what the default does, which includes application/json, so... fine
            ));
        }
    
        @Test
        public void testAddsApplicationJsonToExisting() throws HttpMediaTypeNotAcceptableException {
            when(request.getHeaderValues(HttpHeaders.ACCEPT)).thenReturn(new String[]{"application/something"});
    
            assertThat(subject.resolveMediaTypes(request), containsInAnyOrder(
                    MediaType.valueOf("application/something"),
                    MediaType.APPLICATION_JSON
            ));
        }
    
        @Test
        public void testAddsApplicationJsonToNull() throws HttpMediaTypeNotAcceptableException {
            when(request.getHeaderValues(HttpHeaders.ACCEPT)).thenReturn(null);
    
            assertThat(subject.resolveMediaTypes(request), contains(
                    MediaType.ALL // that's what the default does, which includes application/json, so... fine
            ));
        }
    
        @Test
        public void testRetainsApplicationJson() throws HttpMediaTypeNotAcceptableException {
            when(request.getHeaderValues(HttpHeaders.ACCEPT)).thenReturn(new String[]{"application/json"});
    
            assertThat(subject.resolveMediaTypes(request), contains(MediaType.APPLICATION_JSON));
        }
    }