1.相关概念
AOP核心概念
1、横切关注点
对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点
2、切面(aspect)-》(通知+切点)
类是对物体特征的抽象,切面就是对横切关注点的抽象。
通知+切点
意思就是所有要被应用到增强(advice)代码的地方。(包括方法的方位信息)
3、连接点(joinpoint)-》(被拦截的方法)
被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截的方法,实际上连接点还可以是字段或者构造器
4、切入点(pointcut)-》(描述拦截那些方法的部分)
对连接点进行拦截的定义
5、通知(advice)-》(拦截后执行自己业务逻辑的那些部分)
所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类
这玩意也叫 增强
在逻辑层次上包括了我们抽取的公共逻辑和方位信息。因为Spring只能方法级别的应用AOP,也就是我们常见的before,after,after-returning,after-throwing,around五种,意思就是在方法调用前后,异常时候执行我这段公共逻辑呗。
6、目标对象
代理的目标对象
7、织入(weave)
将切面应用到目标对象并导致代理对象创建的过程。
比如根据Advice中的方位信息在指定切点的方法前后,执行增强。这个过程Spring 替我们做好了。利用的是CGLIB动态代理技术。
8、引入(introduction)
关于这些概念,不清楚的同学可以多看看Spring-AOP相关博客或者书籍.
2.自定义日志注解
自定义日志注解,可以更加控制我们需要落库的请求日志以及方法.
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface YuanLiLog {
String name();//所调用接口的名称
/**
* 这里我们可以根据自己的需要定制很多属性
* 比如 intoDb :标识该条操作日志是否需要持久化存储
*/
// boolean intoDb() default false;
}
3.完成切面逻辑
@Aspect
@Component
@Slf4j
public class WebLogAspect {
private ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal<Map<String, Object>>();
/**
* 横切点
*/
@Pointcut("execution(public * com.yuanfudao.risk.server.rpc..*.*(..))")
public void webLog() {
}
/**
* 接收请求,并记录数据
* @param joinPoint
* @param yuanLiLog
*/
@Before(value = "@annotation(yuanLiLog)")
public void doBefore(JoinPoint joinPoint, YuanLiLog yuanLiLog) {
// 接收到请求
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
HttpServletRequest request = sra.getRequest();
YuanliLogDoBuilder yuanliLogDoBuilder = YuanliLogDo.builder()
.ip(request.getRemoteAddr())
.url(request.getRequestURL().toString())
.classMethod(joinPoint.getSignature().getDeclaringTypeName() + "."
+ joinPoint.getSignature().getName())
.method(yuanLiLog.name());
log.info("WebLogAspect doBefore request: {}",RiskJsonUtil.toJson(joinPoint.getArgs()));
if (joinPoint.getArgs()!=null&&joinPoint.getArgs().length>0){
RiskRequest riskRequest = RiskJsonUtil.toEntity(RiskJsonUtil.toJson(joinPoint.getArgs()[0]), RiskRequest.class);
yuanliLogDoBuilder.requestParam(RiskJsonUtil.toJson(joinPoint.getArgs()[0]))
.bizType(riskRequest.getBizType())
.bizCode(riskRequest.getBizCode())
.requestNo(riskRequest.getRequestNo()==null? UUID.randomUUID().toString():riskRequest.getRequestNo());
}
YuanliLogDo yuanliLogDo = yuanliLogDoBuilder.build();
//todo 比如这里我们可以进行落库
// Long id = yuanLiLogService.save(TableNameConstant.T_YUANLI_LOG, yuanliLogDo);
Map<String, Object> threadInfo = new HashMap<>();
// threadInfo.put("logId",id);
threadInfo.put("startTime",System.currentTimeMillis());
threadLocal.set(threadInfo);
}
/**
* 执行成功后处理
* @param yuanLiLog
* @param ret 响应结果
* @throws Throwable
*/
@AfterReturning(value = "@annotation(yuanLiLog)", returning = "ret")
public void doAfterReturning(YuanLiLog yuanLiLog, Object ret) throws Throwable {
Map<String, Object> threadInfo = threadLocal.get();
Long excuteTime = System.currentTimeMillis()-new Long(threadInfo.get("startTime")+"");
//TODO 比如这里我们可以获取执行时间 响应结果等等
/* String updateSql = yuanLiLogService.buildUpdateSql(YuanLiLogQuery.RETURN_PARAM, RiskJsonUtil.toJson(ret), YuanLiLogQuery.EXCUTE_TIME, excuteTime);
String whereSql =YuanLiLogQuery.ID+SqlKeyConstant.EQ+threadInfo.get("logId");
yuanLiLogService.update(TableNameConstant.T_YUANLI_LOG,updateSql,whereSql);*/
log.info("RESPONSE : " + ret);
}
/**
* 获取执行时间
* @param proceedingJoinPoint
* @return
* @throws Throwable
*/
// @Around(value = "@annotation(yuanLiLog)")
/*public Object doAround(ProceedingJoinPoint proceedingJoinPoint,YuanLiLog yuanLiLog) throws Throwable {
long startTime = System.currentTimeMillis();
Object ob = proceedingJoinPoint.proceed();
Map<String, Object> threadInfo = threadLocal.get();
Long takeTime = System.currentTimeMillis() - startTime;
// threadInfo.put("takeTime", takeTime);
log.info("耗时:" + takeTime);
// threadLocal.set(threadInfo);
return ob;
}*/
/**
* 异常处理
* @param throwable
*/
@AfterThrowing(value = "webLog()", throwing = "throwable")
public void doAfterThrowing(Throwable throwable) {
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
HttpServletRequest request = sra.getRequest();
// 异常信息
log.error("{}接口调用异常,异常信息{}", request.getRequestURI(), throwable);
}
}
3.使用 @YuanLiLog(name="risk")记录方法请求日志
这里我通过Test请求该方法:
@Test
public void testRisk() throws TException {
RiskRequest riskRequest = new RiskRequest();
riskRequest.setBizType("BM");
riskRequest.setBizCode("01");
String uuid = UUID.randomUUID().toString();
riskRequest.setRequestNo(uuid);
VideoAuditRequest videoAuditRequest = VideoAuditRequest.builder()
.videoId(115122121121L)
.channel("banmapai")
.videoUrl("https://xxxxx.mp4")
.build();
Map<String, String> stringStringMap = JsonUtils.jsonToMap(RiskJsonUtil.toJson(videoAuditRequest));
System.out.println(stringStringMap);
riskRequest.setBizData(stringStringMap);
riskRpcHandler.risk(riskRequest);
}
请求成功后,在对应的库中(我在切面中进行了数据落库)可以查询到对应的数据:
只是简单的通过AOP完成的日志记录,如果服务QPS比较高,时间积累上,这种通过mysql去记录数据并不合适,可以去参考一些分布式的日志框架.