【问题标题】:What is the correct way to capture the Http Status code with Spring AOP?使用 Spring AOP 捕获 Http 状态代码的正确方法是什么?
【发布时间】:2019-11-19 21:01:38
【问题描述】:

我正在创建一个方面来使用org.springframework.web.bind.annotation.RestController@Pointcut 注册我的应用程序,当我的类正常响应时,这非常有效,但是当由于某种原因发生异常时,返回的 httpStatus 始终为 200,即使我的 http发生错误时响应返回500,我认为这是因为RestController没有设置http状态,而是将其委托给异常处理程序,我该如何解决这个问题并且仍然在restcontroller之上具有可追溯性?

跟随我的休息控制器

@Slf4j
@RestController
@RequestMapping("/api/conta")
public class ContaResourceHTTP {


    @JetpackMethod("Pagamento de conta")
    @PostMapping("/pagamento")
    public void realizarPagamento(@RequestBody DTOPagamento dtoPagamento) throws InterruptedException
    {

    }

    @JetpackMethod("Transferência entre bancos")
    @PostMapping("/ted")
    public void realizarTED(@RequestBody DTOPagamento dtoPagamento) throws java.lang.Exception
    {
        if(true)
            throw new Exception("XXX");
        //log.info(dtoPagamento.toString());
    }

}

我的 AOP 实现:

@Aspect
@Component
@EnableAspectJAutoProxy(proxyTargetClass = true)
@Slf4j
public class MetricsAspect {

    //@Pointcut("within(@org.springframework.web.bind.annotation.RestController *)")
    @Pointcut("execution(* javax.servlet.http.HttpServlet.*(..)) *)")
    public void springBeanPointcut() {
    }

    @Autowired
    Tracer tracer;

    @Around("springBeanPointcut()")
    public void logAround(ProceedingJoinPoint joinPoint) throws Throwable {

        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
                .getRequest();

        long inicioProcesso = System.currentTimeMillis();

        joinPoint.proceed();

        long finalProcesso = System.currentTimeMillis();

        long duracaoProcesso = finalProcesso - inicioProcesso;

        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
                .getResponse();

        Metrics metricas = new Metrics();

        metricas.setDuracaoMs(duracaoProcesso);
        metricas.setDataHoraRequisicao(milissegundosToStringDate(inicioProcesso));
        metricas.setDataHoraResposta(milissegundosToStringDate(finalProcesso));
        metricas.setServidorOrigem(request.getRemoteAddr());
        metricas.setPortaOrigem(request.getRemotePort());
        metricas.setDominioAcesso(request.getLocalName());
        metricas.setPortaAcesso(request.getLocalPort());
        metricas.setUrlPath(request.getRequestURI());
        metricas.setMetodoHttp(request.getMethod());
        metricas.setIdTransacao(tracer.currentSpan().context().traceIdString());
        metricas.setIdSpan(tracer.currentSpan().context().spanIdString());
        metricas.setStatusHttp(response.getStatus());

        log.info(JSONConversor.toJSON(metricas));

    }

    public String milissegundosToStringDate(long ms) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");

        Date dataInicial = new Date(ms);

        return dateFormat.format(dataInicial);
    }
}

我的异常处理程序:

@ControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ExceptionControllerAdvice {


    @ExceptionHandler({ Throwable.class })
    public ResponseEntity<ApiError> handlerValidationException2(Throwable e) {
        return new ResponseEntity<>(new ApiError(HttpStatus.INTERNAL_SERVER_ERROR, e, traceRespostaAPI),
                HttpStatus.INTERNAL_SERVER_ERROR);
    }


}

【问题讨论】:

    标签: java spring-boot aop aspectj


    【解决方案1】:

    过了一段时间,我能够用一个可能不是最优雅的解决方案来解决问题,基本上我使用了两个切入点,一个在restcontroller中截取@JetpackMethod注释值并将其添加到http之前带有建议的响应标头和围绕 HttpServlet 的另一个标头,这确实是真正返回修改后的 http 状态的标头。

    下面的代码解决了我的问题。

    该类拦截注解并将其值添加到标头中。

    @Aspect
    @Component
    public class InterceptRestAnnotationAspect {
    
        @Pointcut("within(@org.springframework.web.bind.annotation.RestController *)")
        public void restControllerExecution() {}
    
    
        @Before("restControllerExecution()")
        public void setMetodoHttpHeader(JoinPoint joinPoint) throws Throwable {
    
            HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
                    .getResponse();
    
            String origem = VerificadorOrigem.processarOrigem(joinPoint);
    
            response.setHeader("nomeMetodo", origem);
    
        }
    
    }
    

    这个其他类记录了我需要的 servlet 指标,并且可以检索之前在标头中输入的值。

    @Aspect
    @Component
    @Slf4j
    public class MetricsAspect {
    
        @Pointcut("execution(* javax.servlet.http.HttpServlet.*(..)) *)")
        public void servletService() {
        }
    
        @Autowired
        Tracer tracer;
    
        @Around("servletService()")
        public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
    
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
                    .getRequest();
    
            long inicioProcesso = System.currentTimeMillis();
    
            Object result = joinPoint.proceed();
    
            long finalProcesso = System.currentTimeMillis();
    
            long duracaoProcesso = finalProcesso - inicioProcesso;
    
            HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
                    .getResponse();
    
            Metrics metricas = new Metrics();
    
            String funcionalidade = response.getHeader("nomeMetodo") == null ? "Indeterminada"
                    : response.getHeader("nomeMetodo");
    
            metricas.setNivelLog("INFO");
            metricas.setFuncionalidade(funcionalidade);
            metricas.setDuracaoMs(duracaoProcesso);
            metricas.setDataHoraRequisicao(ManipulaData.milissegundosToStringDate(inicioProcesso));
            metricas.setDataHoraResposta(ManipulaData.milissegundosToStringDate(finalProcesso));
            metricas.setServidorOrigem(request.getRemoteAddr());
            metricas.setPortaOrigem(request.getRemotePort());
            metricas.setDominioAcesso(request.getLocalName());
            metricas.setPortaAcesso(request.getLocalPort());
            metricas.setUrlPath(request.getRequestURI());
            metricas.setMetodoHttp(request.getMethod());
            metricas.setIdTransacao(tracer.currentSpan().context().traceIdString());
            metricas.setIdSpan(tracer.currentSpan().context().spanIdString());
            metricas.setStatusHttp(response.getStatus());
    
            log.info(JSONConversor.toJSON(metricas));
    
            return result;
    
        }
    }
    

    【讨论】:

      【解决方案2】:

      我认为joinPoint.proceed(); 之后的代码不会在出现异常的情况下执行。 如果出现异常,您可以有不同的执行建议:

      @AfterThrowing(pointcut = "springBeanPointcut()", throwing = "e")
        public void afterThrowingAdvice(JoinPoint jp, Exception e) {
         ....
        }
      

      【讨论】:

      • 我已经尝试过这样做,甚至尝试过尝试 joinPoint.proceed () ;,当我这样做时,我的建议被完全执行,但即使稍后我的处理程序抛出异常http status continue 200 ,我想通过返回正确的代码并使用异常处理程序来使其与 restcontroller 一起工作。我使用@Pointcut ("execution (* javax.servlet.http.HttpServlet. * (..)) *)") 进行了测试,它返回正确的http代码工作正常,但是我丢失了我在方法中放入的注释数据休息控制器
      • 响应状态会在您执行 response.sendError(500,"error message"); 时更改,该response.sendError(500,"error message"); 在内部设置响应的状态。但是,如果它足够简单,可以假设是否抛出异常,那么正如我在您的@ExceptionHandler 中看到的那样,它是 500,在这种情况下,如果执行了@AfterThrowing 建议,您不能将状态设置为 500 吗?比如metricas.setStatusHttp(500)?
      • 不幸的是,这不是一个好的选择,因为这个处理程序是初始的,并且仍然想对 500 以外的 http 代码进行其他处理,我设法使用两个不同的切入点来纠正问题,我会发布答案。
      • @ViniciusVieira,很好,你实现了你想要的,+1 为你的答案。
      猜你喜欢
      • 1970-01-01
      • 2018-02-24
      • 1970-01-01
      • 2017-06-27
      • 2012-12-06
      • 2019-05-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-18
      相关资源
      最近更新 更多