使用 Spring AOP 捕获 Http 状态代码的正确方法是什么?

What is the correct way to capture the Http Status code with Spring AOP?

我正在创建一个方面来使用 org.springframework.web.bind.annotation.RestController 注册我的应用程序,例如 @Pointcut,这在我的 class 正常响应时完美运行,但是当由于某种原因发生异常时,返回的httpStatus总是200,即使我的http response returns 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);
    }


}

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

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

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

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

此 class 拦截注释并将其值添加到 header。

@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);

    }

}

另一个 class 记录了我需要的 servlet 指标,并且可以检索之前在 header 中输入的值。

@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;

    }
}