关于SpringAop中@within的使用踩坑指南
Posted u012663412
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于SpringAop中@within的使用踩坑指南相关的知识,希望对你有一定的参考价值。
最近收到一个需求,需要采集下各个业务系统的日志,实现一个统一的SDK(语言Java),别的部门引用之后,就可以自动进行采集。我采用的是Spring aop自定义注解来实现的,网上的代码抄了一份。然后思考怎么可以在类上加或者是方法上加都可以采集。实现方法上加注解采集日志花了1个小时的时间,而集成给类上加注解采集日志和实现方法加注解采集日志花费了2天的时间,由此可见,扩展一个小的功能点,如果没有现成的方法,确实比较花时间。期间感谢群里大佬@小蛮同学
的指点。
代码呈上,删除了部分采集的数据信息,发送到kafka的也删除了,剩余如下:
其中切面
@Pointcut("@within(com.yto.logcollect.annotation.LogAspect) || @annotation(com.yto.logcollect.annotation.LogAspect)")
这里使用@within是可以通过aop拦截类上的注解。而@annotation是通过aop拦截方法上的注解。
附上官网对@within的定义
@within: Limits matching to join points within types that have the given annotation (the execution of methods declared in types with the given annotation when using Spring AOP).
这里有一个坑,就是用了@within之后,不能通过表达式来获取注解,如下:
@Around(value = ("logRecord() && @annotation(logAspect)"))
public Object doAroundAdvice(ProceedingJoinPoint joinPoint, LogAspect logAspect) {
这样死活都不会进入。试验了无数次,发现删除参数注解之后就可以了。
但是这样又存在问题,就是无法获取到注解中填写的值。这个就比较简单了,通过反射去获取就可以了。getClassAnnotation()
可以完美拿到。问题解决。
但是在测试的时候又发现,定义的HttpServletRequest, HttpServletResponse
这2个参数类型是无法被json转换的,因为里边可能会存在无限嵌套(测试框架用到了SpringSecurity)的问题。首次写的代码特别麻烦,虽然也成功过滤了,这个时候小蛮大佬又指了一条明路,通过lambda来过滤掉HttpServletRequest和HttpServletResponse来解决这个问题,代码变的整洁,观赏性也高了,更易于理解了。
如有兴趣可以共同学习探索!QQ:303579750
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@Aspect
@Slf4j
public class LogPointCut {
@Autowired
private ExchangePrincipleToUserCodeService principleService;
@Autowired
private KafkaTemplate<String, Object> kafkaTemplate;
@Pointcut("@within(com.yto.logcollect.annotation.LogAspect) || @annotation(com.yto.logcollect.annotation.LogAspect)")
public void logRecord() {
}
@Around(value = ("logRecord()"))
public Object doAroundAdvice(ProceedingJoinPoint joinPoint) {
LogAspect logAspect;
Object proceedResult = null;
Log logInfo = new Log();
HttpServletRequest request;
try {
logAspect = getTargetAnnotation(joinPoint);
request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
logInfo = collectDataBeforeProceed(joinPoint, logInfo, request, logAspect);
proceedResult = joinPoint.proceed();
logInfo = collectDataAfterProceed(proceedResult, logInfo);
if (!ObjectUtils.isEmpty(logAspect.topic())) {
sendDataToKafkaTopic(logAspect.topic(), logInfo);
}
} catch (Exception e) {
log.error("统计日志发生异常,原因为:{}" + e.getMessage(), e);
} catch (Throwable throwable) {
log.error("统计日志发生异常,原因为:" + throwable.getMessage(), throwable);
}
return proceedResult;
}
/**
* include TYPE Annotation and METHOD Annotation
* target = TYPE or METHOD
* 先去获取Class上的Annotation,如果Class上的Annotation为空,则去获取Method上的Annotation
*
* @param joinPoint
* @return
*/
private LogAspect getTargetAnnotation(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {
LogAspect annotation = getClassAnnotation(joinPoint);
if (ObjectUtils.isEmpty(annotation)) {
annotation = getMethodAnnotation(joinPoint);
}
return annotation;
}
private LogAspect getMethodAnnotation(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {
Method method = getTargetMethod(joinPoint);
return method.getAnnotation(LogAspect.class);
}
private Method getTargetMethod(ProceedingJoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
return methodSignature.getMethod();
}
private LogAspect getClassAnnotation(ProceedingJoinPoint joinPoint) {
return joinPoint.getTarget().getClass().getAnnotation(LogAspect.class);
}
private Log collectDataBeforeProceed(ProceedingJoinPoint joinPoint, Log log,
HttpServletRequest request, LogAspect logAspect) throws Throwable {
log.setId(UUID.randomUUID().toString().replaceAll("-", ""));
log.setRequestTime(System.currentTimeMillis());
log.setClientUserAgent(request.getHeader("User-Agent"));
log.setServerName(InetAddress.getLocalHost().getHostName());
log.setProtocol(request.getProtocol());
log.setClientPort(String.valueOf(request.getRemotePort()));
log.setMethod(request.getMethod());
log.setActionMethod(joinPoint.getSignature().getName());
log.setClientAddr(getIp(request));
log.setInstancePort(String.valueOf(request.getLocalPort()));
log.setParams(getRequestParams(joinPoint));
log.setUrl(request.getRequestURL().toString());
log.setActionClass(joinPoint.getTarget().getClass().getName());
log.setSendSize(Long.valueOf(request.getContentLength()));
log.setServerAddr(InetAddress.getLocalHost().getHostAddress());
log.setSessionId(request.getSession().getId());
return log;
}
/**
* 如果参数中有request和response的话,会出现递归调用的issue
* 此方法目的是去掉request和response参数
*
* @param joinPoint
* @return
*/
private String getRequestParams(ProceedingJoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
List<Object> argsExcludeRequestAndResponse = Arrays.stream(args).filter(a -> !(a instanceof HttpServletRequest) && !(a instanceof HttpServletResponse)).collect(Collectors.toList());
return argsExcludeRequestAndResponse.size() == 0 ? "" : JacksonUtil.to(argsExcludeRequestAndResponse);
}
/**
* 获取请求ip
*/
protected static String getIp(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
private Log collectDataAfterProceed(Object proceedResult, Log logInfo) {
logInfo.setResponseTime(System.currentTimeMillis());
logInfo.setActionResult(JacksonUtil.to(proceedResult));
logInfo.setReceiveSize(Long.valueOf(logInfo.getActionResult().getBytes().length));
return logInfo;
}
}
以上是关于关于SpringAop中@within的使用踩坑指南的主要内容,如果未能解决你的问题,请参考以下文章
Flutter踩坑记: There are multiple heroes that share the same tag within a subtree.