简体   繁体   English

Spring ControllerAdvice不会针对某些异常触发,而针对其他异常

[英]Spring ControllerAdvice not firing for some exceptions but others

I have a springboot rest service that can be accessed through the /test endpoint. 我有一个springboot rest服务,可以通过/test端点访问。 I have setup a error handling controller like so: 我已经设置了一个错误处理控制器,如下所示:

@ControllerAdvice
public class AppErrorController extends ResponseEntityExceptionHandler {

    @ExceptionHandler(InvalidHeaderException.class)
    public ResponseEntity<JsonNode> handleHeaderError(InvalidHeaderException ex) {
        return ResponseEntity
                .status(HttpStatus.NOT_ACCEPTABLE)
                .body(generateResponseJson(ex));
    }

    @ExceptionHandler(InvalidRequestException.class)
    public ResponseEntity<JsonNode> handleRequestError(InvalidRequestException ex) {
        return ResponseEntity
                .status(HttpStatus.NOT_ACCEPTABLE)
                .body(generateResponseJson(ex));
    }

    @ExceptionHandler(ServiceResponseException.class)
    public ResponseEntity<JsonNode> handleServiceError(ServiceResponseException ex) {
        return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(generateResponseJson(ex));
    }

    private JsonNode generateResponseJson(Exception ex) {
        return ResponseBuilder.newBuilder()
                .withException(ex.getClass().getSimpleName())
                .withDescription(ex.getMessage())
                .withTimestamp(LocalDateTime.now())
                .buildAsJson();
    }
}

I've tested this out and this works perfectly fine. 我已经对此进行了测试,并且效果很好。 If I test the api with a bad input then it returns: 如果我用错误的输入测​​试api,则返回:

{
  "exception": "InvalidRequestException",
  "description": "A valid input value is needed that is non-empty or non-null.",
  "timestamp": "2016-03-28T11:35:39.765"
}

And this is what the log looks like in correlation with the AppErrorController handling the exception: 这是与处理异常的AppErrorController相关联的日志的样子:

11:35:39.706 [http-bio-8080-exec-3] DEBUG org.springframework.web.servlet.DispatcherServlet - DispatcherServlet with name 'Foo_Engine' processing POST request for [/test]
11:35:39.706 [http-bio-8080-exec-3] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Looking up handler method for path /test
11:35:39.707 [http-bio-8080-exec-3] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Returning handler method [public org.springframework.http.ResponseEntity<com.fasterxml.jackson.databind.JsonNode> controllers.FooController.getFoosForRest(controllers.model.Request,org.springframework.validation.BindingResult)]
11:35:39.708 [http-bio-8080-exec-3] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor - Read [class controllers.model.Request] as "application/json" with [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@3086f443]
11:35:39.709 [http-bio-8080-exec-3] DEBUG org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver - Resolving exception from handler [public org.springframework.http.ResponseEntity<com.fasterxml.jackson.databind.JsonNode> controllers.FooController.getFoosForRest(controllers.model.Request,org.springframework.validation.BindingResult)]: controllers.exceptions.InvalidRequestException: A valid input value is needed that is non-empty or non-null.
11:35:39.761 [http-bio-8080-exec-3] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'appErrorController'
11:35:39.762 [http-bio-8080-exec-3] DEBUG org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver - Invoking @ExceptionHandler method: public org.springframework.http.ResponseEntity<com.fasterxml.jackson.databind.JsonNode> controllers.AppErrorController.handleRequestError(controllers.exceptions.InvalidRequestException)
11:35:39.815 [http-bio-8080-exec-3] DEBUG org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor - Written [{"resultCode":false,"exception":"InvalidRequestException","description":"A valid input value is needed that is non-empty or non-null.","timestamp":"2016-03-28T11:35:39.765"}] as "application/json" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@3086f443]
11:35:39.816 [http-bio-8080-exec-3] DEBUG org.springframework.web.servlet.DispatcherServlet - Null ModelAndView returned to DispatcherServlet with name 'Foo_Engine': assuming HandlerAdapter completed request handling
11:35:39.816 [http-bio-8080-exec-3] DEBUG org.springframework.web.servlet.DispatcherServlet - Successfully completed request

This works great when with the InvalidHeaderException and InvalidRequestException but for some reason it doesn't work for ServiceResponseException . 当与InvalidHeaderExceptionInvalidRequestException一起使用时,这很好用,但由于某些原因,它不适用于ServiceResponseException The first exception gets thrown by a header interceptor, the second gets thrown by the controller sitting up top and the 3rd one gets thrown by the inner code. 第一个异常由标头拦截器引发,第二个异常由坐在顶部的控制器引发,第三个异常由内部代码引发。 Here is where it gets thrown: 这是它抛出的地方:

public class Foo {
    private final Cache<String, List<Foo>> fooCache;

    public Foo() {
        fooCache = CacheBuilder.newBuilder().build();
    }

    public List<Foo> getFoo() {
        List<Foo> foos = null;
        try {
            foos = fooCache.get(foo.getId(),
                    () -> populateCacheWithFoo());
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        return foos;
    }

    public List<Foo> populateCacheWithFoo() {
        JsonNode response = null;
        try {
            response = operation.postForObject(/* resource url */,
                    /* build header body */,
                    JsonNode.class);
        } catch (Throwable t) {
            throw new ServiceResponseException("Error with backend service - getResourceA operation");
        }

        List<Foo> foos = response.get("Foos");

        fooCache.put(foo.get(1).getId(), foos);
        return foos;
    }
}

For some reason, whenever I throw this exception it just doesn't get caught by the AppErrorController and instead I get this stack trace: 出于某种原因,每当我抛出此异常时,它都不会被AppErrorController ,而是得到此堆栈跟踪:

11:23:43.085 [http-bio-8080-exec-1] DEBUG org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver - Resolving exception from handler [public org.springframework.http.ResponseEntity<com.fasterxml.jackson.databind.JsonNode> controllers.FooController.getFoosForRest(controllers.model.Request,org.springframework.validation.BindingResult)]: com.google.common.util.concurrent.UncheckedExecutionException: controllers.exceptions.ServiceResponseException: Error with backend service - getResourceA operation
11:23:43.086 [http-bio-8080-exec-1] DEBUG org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver - Resolving exception from handler [public org.springframework.http.ResponseEntity<com.fasterxml.jackson.databind.JsonNode> controllers.FooController.getFoosForRest(controllers.model.Request,org.springframework.validation.BindingResult)]: com.google.common.util.concurrent.UncheckedExecutionException: controllers.exceptions.ServiceResponseException: Error with backend service - getResourceA operation
11:23:43.086 [http-bio-8080-exec-1] DEBUG org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver - Resolving exception from handler [public org.springframework.http.ResponseEntity<com.fasterxml.jackson.databind.JsonNode> controllers.FooController.getFoosForRest(controllers.model.Request,org.springframework.validation.BindingResult)]: com.google.common.util.concurrent.UncheckedExecutionException: controllers.exceptions.ServiceResponseException: Error with backend service - getResourceA operation
11:23:43.096 [http-bio-8080-exec-1] DEBUG org.springframework.web.servlet.DispatcherServlet - Could not complete request
com.google.common.util.concurrent.UncheckedExecutionException: controllers.exceptions.ServiceResponseException: Error with backend service - getResourceA operation
    at com.google.common.cache.LocalCache$Segment.get(LocalCache.java:2207)
    at com.google.common.cache.LocalCache.get(LocalCache.java:3953)
    at com.google.common.cache.LocalCache$LocalManualCache.get(LocalCache.java:4790)
    at services.Foo.getFoo(IdentityClientImpl.java:73)
    at controllers.FooController.getFoosForRest(FooController.java:61)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:222)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:814)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:737)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:969)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:871)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:650)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:845)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:731)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    at org.springframework.boot.context.web.ErrorPageFilter.doFilter(ErrorPageFilter.java:120)
    at org.springframework.boot.context.web.ErrorPageFilter.access$000(ErrorPageFilter.java:61)
    at org.springframework.boot.context.web.ErrorPageFilter$1.doFilterInternal(ErrorPageFilter.java:95)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.boot.context.web.ErrorPageFilter.doFilter(ErrorPageFilter.java:113)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:505)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:957)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:423)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1079)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:620)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:316)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:745)
Caused by: controllers.exceptions.ServiceResponseException: Error with backend service - getResourceA operation
    at services.Foo.populateCacheWithFoo(Foo.java:99)
    at services.Foo.lambda$getFoo$0(Foo.java:74)
    at com.google.common.cache.LocalCache$LocalManualCache$1.load(LocalCache.java:4793)
    at com.google.common.cache.LocalCache$LoadingValueReference.loadFuture(LocalCache.java:3542)
    at com.google.common.cache.LocalCache$Segment.loadSync(LocalCache.java:2323)
    at com.google.common.cache.LocalCache$Segment.lockedGetOrLoad(LocalCache.java:2286)
    at com.google.common.cache.LocalCache$Segment.get(LocalCache.java:2201)
    ... 56 common frames omitted

Any idea why the first 2 exceptions are getting caught by the AppErrorController but not the 3rd one? 知道为什么前两个异常会被AppErrorController而不是第三个异常吗? Is there some configuration I need to do in my java config files? 我的Java配置文件中需要做一些配置吗? Here's my mvc configuration file: 这是我的mvc配置文件:

@Configuration
@EnableWebMvc
public class MvcConfiguration extends WebMvcConfigurerAdapter {
    @Autowired
    ServletContext servletContext;

    @Autowired
    HeaderInterceptor interceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(interceptor);
    }

    @Override
    public void configureDefaultServletHandling(
            DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    @Bean
    public ViewResolver getJspViewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("WEB-INF/jsp/views/");
        resolver.setSuffix(".jsp");
        resolver.setOrder(1);
        return resolver;
    }
}

Seems like the problem is that the Callable in the guava get cache catch the exception and re-throw them encapsulated : http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/cache/Cache.html#get(K,%20java.util.concurrent.Callable) 似乎问题在于,番石榴获取缓存中的Callable捕获异常并重新封装它们: http : //docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/cache/Cache .html#get(K,%20java.util.concurrent.Callable)

You might want to add a catch clause in your getFoo() method and re-throw the original exception. 您可能想要在getFoo()方法中添加catch子句,然后重新引发原始异常。 Something like this : 像这样的东西:

public List<Foo> getFoo() throws ServiceResponseException{
    List<Foo> foos = null;
    try {
        foos = fooCache.get(foo.getId(),
                () -> populateCacheWithFoo());
    } catch (ExecutionException e) {
        Throwables.propagateIfPossible(
        e.getCause(), ServiceResponseException.class);
    } 
    return foos;
}

But you might want to put a breakpoint in the catch to confirm that the .getCause() is your actual exception or if it's a level bellow 但是您可能想在断点中放置一个断点,以确认.getCause()是您的实际异常,或者是下面的级别


Or you could do this if you don't want to change your ServiceException to a checked exception : 或者,如果您不想将ServiceException更改为已检查的异常,则可以执行以下操作:

public List<Foo> getFoo() throws ServiceResponseException{
    List<Foo> foos = null;
    try {
        foos = fooCache.get(foo.getId(),
                () -> populateCacheWithFoo());
    } catch (ExecutionException e) {
        e.printStackTrace();
    } catch (UncheckedExecutionException e) {
        Throwables.propagateIfPossible(
        e.getCause(), ServiceResponseException.class);
    } 
    return foos;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM