如何避免spring aop通知引发的未处理异常淹没日志

y0u0uwnf  于 2021-07-24  发布在  Java
关注(0)|答案(1)|浏览(338)

我有一个SpringBootWeb应用程序,它有一个端点/用户信息

@RestController
public class HelloController {

    @GetMapping("/userInfo")
    @WithRoles({Roles.USER, Roles.VERIFIED_EMAIL})
    public User userInfo() {
        return RequestContextHelper.getCurrentUser();
    }

}

这个 @WithRoles 注解是一个方面的标记:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface WithRoles {
    Roles[] value();
}

这就是实现方面:

@Aspect
@Component
public class RolesAspect {

    @Before("@annotation(com.chuque.aspectj.aspect.marker.WithRoles)")
    public void validatePermission(final JoinPoint joinPoint) throws UnauthorizedException {

        User currentUser = RequestContextHelper.getCurrentUser();

        WithRoles annotation = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(WithRoles.class);

        boolean userHasAllRoles = currentUser.getRoles().containsAll(Arrays.asList(annotation.value()).stream().map(Enum::toString).collect(Collectors.toList()));

        if(!userHasAllRoles){
            throw new UnauthorizedException();
        }

    }
}

如您所见,有一个条件抛出异常,导致方法甚至没有被调用。当端点抛出异常时 @RestControllerAdvice 处理它并返回 401 Unauthorized :

@RestControllerAdvice
public class GenericExceptionHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(GenericExceptionHandler.class);

    @ExceptionHandler(UnauthorizedException.class)
    public ResponseEntity handleUnauthorizedException(Exception e) {
        HttpStatus response = HttpStatus.UNAUTHORIZED;
        LOGGER.error(String.format("Unauthorized exception - Returning %d %s", response.value(), response.getReasonPhrase()), e);
        return ResponseEntity.status(response).body(e.getMessage());
    }

}

代码运行良好。如果用户没有所需的角色,则不会调用端点,并且 @RestControllerAdvice 真正处理通知引发的异常。
问题是,当通知抛出异常时,日志中充斥着 UndeclaredThrowableException 堆栈跟踪:

2021-01-21 18:57:46.942 ERROR 19071 --- [nio-8080-exec-3] c.c.a.r.e.GenericExceptionHandler        : Unauthorized exception - Returning 401 Unauthorized

java.lang.reflect.UndeclaredThrowableException: null
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:767) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692) ~[spring-aop-5.3.3.jar:5.3.3]
    at com.chuque.aspectj.rest.HelloController$$EnhancerBySpringCGLIB$$4ae73bfc.userInfo(<generated>) ~[classes/:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:564) ~[na:na]
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:197) ~[spring-web-5.3.3.jar:5.3.3]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:141) ~[spring-web-5.3.3.jar:5.3.3]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106) ~[spring-webmvc-5.3.3.jar:5.3.3]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894) ~[spring-webmvc-5.3.3.jar:5.3.3]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) ~[spring-webmvc-5.3.3.jar:5.3.3]
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.3.3.jar:5.3.3]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1060) ~[spring-webmvc-5.3.3.jar:5.3.3]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:962) ~[spring-webmvc-5.3.3.jar:5.3.3]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.3.3.jar:5.3.3]
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.3.3.jar:5.3.3]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:626) ~[tomcat-embed-core-9.0.41.jar:4.0.FR]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.3.3.jar:5.3.3]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:733) ~[tomcat-embed-core-9.0.41.jar:4.0.FR]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.41.jar:9.0.41]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.41.jar:9.0.41]
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.41.jar:9.0.41]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.41.jar:9.0.41]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.41.jar:9.0.41]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.3.jar:5.3.3]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.3.jar:5.3.3]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.41.jar:9.0.41]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.41.jar:9.0.41]
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.3.jar:5.3.3]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.3.jar:5.3.3]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.41.jar:9.0.41]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.41.jar:9.0.41]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.3.jar:5.3.3]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.3.jar:5.3.3]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.41.jar:9.0.41]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.41.jar:9.0.41]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.41.jar:9.0.41]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[tomcat-embed-core-9.0.41.jar:9.0.41]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) ~[tomcat-embed-core-9.0.41.jar:9.0.41]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143) ~[tomcat-embed-core-9.0.41.jar:9.0.41]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.41.jar:9.0.41]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-9.0.41.jar:9.0.41]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[tomcat-embed-core-9.0.41.jar:9.0.41]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374) ~[tomcat-embed-core-9.0.41.jar:9.0.41]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.41.jar:9.0.41]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:888) ~[tomcat-embed-core-9.0.41.jar:9.0.41]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1597) ~[tomcat-embed-core-9.0.41.jar:9.0.41]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.41.jar:9.0.41]
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130) ~[na:na]
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630) ~[na:na]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.41.jar:9.0.41]
    at java.base/java.lang.Thread.run(Thread.java:832) ~[na:na]
Caused by: com.chuque.aspectj.exception.UnauthorizedException: null
    at com.chuque.aspectj.aspect.runner.RolesAspect.validatePermission(RolesAspect.java:35) ~[classes/:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:564) ~[na:na]
    at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:634) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:617) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.aop.aspectj.AspectJMethodBeforeAdvice.before(AspectJMethodBeforeAdvice.java:44) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:57) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:175) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) ~[spring-aop-5.3.3.jar:5.3.3]
    ... 55 common frames omitted

每次抛出未经授权的异常时都有这个stacktrace是不好的。我怎样才能解决这个问题?也许是清除堆栈痕迹?也许有一种方法可以禁用通知抛出的异常日志?感谢您的帮助。

rxztt3cl

rxztt3cl1#

实际上,对我来说,这个例外看起来和你发布的有点不同:

java.lang.reflect.UndeclaredThrowableException: null
    at de.scrum_master.spring.q65836243.HelloController$$EnhancerBySpringCGLIB$$261c9226.userInfo(<generated>) ~[classes/:na]

所以,我可以直接看到异常是在 HelloController aop代理,更确切地说是 userInfo() 方法。
正如错误消息所暗示的,您试图在未声明异常的方法中抛出选中的异常。制造 UnauthorizedException 延伸 RuntimeException 取而代之的是 UndeclaredThrowableException 他走了。
或者,使 userInfo()UnauthorizedException . 但这并不是很好,因为这样应用程序代码就可以有效地感知aop,这不是很好的风格。最好保持核心关注点不受横切关注点(如异常处理)的影响。
p、 s:讽刺的是,你没有发布异常源代码,所以我不得不尝试模拟你的情况,做一个有根据的猜测,以便找出答案。下一次,请发布一个完整的mcve?你可能认为你知道哪些信息对其他人解决你的问题是必要的,但实际上你不知道,因为否则你可能已经知道解决你问题的方法了。所以你在未知的领域。让你的助手看到全貌,这对你和他们都非常有帮助,节省了很多时间。
更新:以下是我的mcve供参考:

package de.scrum_master.spring.q65836243;

public enum Roles {
  VERIFIED_EMAIL, USER
}
package de.scrum_master.spring.q65836243;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface WithRoles {
  Roles[] value();
}
package de.scrum_master.spring.q65836243;

import org.springframework.security.core.userdetails.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;

@RestController
public class HelloController {
  @GetMapping("/userInfo")
  @WithRoles({ Roles.USER, Roles.VERIFIED_EMAIL })
  public User userInfo() {
    return new User("john", "secret", new ArrayList<>());
  }
}
package de.scrum_master.spring.q65836243;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GenericExceptionHandler {
  private static final Logger LOGGER = LoggerFactory.getLogger(GenericExceptionHandler.class);

  @ExceptionHandler(UnauthorizedException.class)
  public ResponseEntity handleUnauthorizedException(Exception e) {
    HttpStatus response = HttpStatus.UNAUTHORIZED;
    LOGGER.error(String.format("Unauthorized exception - Returning %d %s", response.value(), response.getReasonPhrase()), e);
    return ResponseEntity.status(response).body(e.getMessage());
  }
}
package de.scrum_master.spring.q65836243;

public class UnauthorizedException extends /*Exception*/ RuntimeException {}
package de.scrum_master.spring.q65836243;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class RolesAspect {
  @Before("@annotation(withRoles)")
  public void validatePermission(JoinPoint joinPoint, WithRoles withRoles) throws UnauthorizedException {
    boolean userHasAllRoles = false;
    if (!userHasAllRoles)
      throw new UnauthorizedException();
  }
}
package de.scrum_master.spring.q65836243;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@Configuration
public class DemoApplication {
  public static void main(String[] args) throws InterruptedException {
    try (ConfigurableApplicationContext appContext = SpringApplication.run(DemoApplication.class, args)) {
      doStuff(appContext);
    }
  }

  private static void doStuff(ConfigurableApplicationContext appContext) {
    try {
      new RestTemplate().getForObject("http://localhost:8080/userInfo", String.class);
    }
    catch (RestClientException ignored) {}
  }
}

运行示例应用程序时,现在可以看到以下控制台日志:

.   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.8.RELEASE)

2021-01-22 12:00:34.001  INFO 17396 --- [           main] d.s.spring.q65836243.DemoApplication     : Starting DemoApplication on Xander-Ultrabook with PID 17396 (C:\Users\alexa\Documents\java-src\spring-aop-playground\target\classes started by alexa in C:\Users\alexa\Documents\java-src\spring-aop-playground)
(...)
2021-01-22 12:00:39.213 ERROR 17396 --- [nio-8080-exec-1] d.s.s.q65836243.GenericExceptionHandler  : Unauthorized exception - Returning 401 Unauthorized

de.scrum_master.spring.q65836243.UnauthorizedException: null
    at de.scrum_master.spring.q65836243.RolesAspect.validatePermission(RolesAspect.java:19) ~[classes/:na]
(...)

2021-01-22 12:00:39.272  INFO 17396 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'
(...)
Process finished with exit code 0

相关问题