使用 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;
}
}
我正在创建一个方面来使用 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;
}
}