Guns——AOP日志框架(一)

1.简单回顾AOP

  • @Before 前置通知(Before advice) :在某连接点(JoinPoint)——核心代码(类或者方法)之前执行的通知,但这个通知不能阻止连接点前的执行。为啥不能阻止线程进入核心代码呢?因为@Before注解的方法入参不能传ProceedingJoinPoint,而只能传入JoinPoint。要知道从aop走到核心代码就是通过调用ProceedingJionPoint的proceed()方法。而JoinPoint没有这个方法。
  • 这里牵扯区别这两个类:Proceedingjoinpoint 继承了 JoinPoint 。是在JoinPoint的基础上暴露出 proceed 这个方法。proceed很重要,这个是aop代理链执行的方法。暴露出这个方法,就能支持 aop:around 这种切面(而其他的几种切面只需要用到JoinPoint,这跟切面类型有关), 能决定是否走代理链还是走自己拦截的其他逻辑。建议看一下 JdkDynamicAopProxy的invoke方法,了解一下代理链的执行原理。这样你就能明白 proceed方法的重要性。
  • @After 后通知(After advice) :当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
  • @AfterReturning 返回后通知(After return advice) :在某连接点正常完成后执行的通知,不包括抛出异常的情况。
  • @Around 环绕通知(Around advice) :包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。这时aop的最重要的,最常用的注解。用这个注解的方法入参传的是ProceedingJionPoint pjp,可以决定当前线程能否进入核心方法中——通过调用pjp.proceed();
  • @AfterThrowing 抛出异常后通知(After throwing advice) : 在方法抛出异常退出时执行的通知。

2.spring boot AOP使用流程

  • 首先将一个类声明成切面类
@Aspect
@Component
public class LogAop {}
  • 接着定义切点,在此使用注解切点
@Pointcut(value = "@annotation(cn.stylefeng.guns.core.common.annotion.BussinessLog)")
    public void cutService() { }
  •  接着定义建言
    @Around("cutService()")
    public Object recordSysLog(ProceedingJoinPoint point) throws Throwable {
        //先执行业务
        Object result = point.proceed();
        try {
            handle(point);
        } catch (Exception e) {
            log.error("日志记录出错!", e);
        }
        return result;
    }
  • 最后在需要织入建言的方法上加入注解即可
    @RequestMapping(value = "/add")
    @BussinessLog(value = "添加角色", key = "name", dict = RoleDict.class)
    public ResponseData add(@Valid Role role, BindingResult result) {
        if (result.hasErrors()) {
            throw new ServiceException(BizExceptionEnum.REQUEST_NULL);
        }
        role.setId(null);
        this.roleService.insert(role);
        return SUCCESS_TIP;
    }

3.日志框架的基本功能

  • 对用户的登陆状态进行记录(成功、失败、退出)------直接在相应登陆控制器上记录日志
  • 对异常操作进行记录(自定义异常和运行时异常)------直接在全局异常拦截控制器上记录日志
  • 对业务操作进行记录(增删改查)------使用AOP方式记录日志

4.日志框架实现

注意:  在实际记录日志时候,需要开启线程记录,因为记录操作会非常多防止堵塞主线程

  • 第一种方法如guns一样自定义线程池
  //异步操作记录日志的线程池
    private ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(10);
  • 第二种方法使用spring的线程池,需要配置相关的配置类,之后在需要执行的方法打上@Async上打上注解即可异步调用
@Async
public void async(){}

5.分析AOP的主要逻辑

  • 可以看到主要方法在handle(),并且是在执行具体业务逻辑之后才开始执行aop的逻辑
@Around("cutService()")
    public Object recordSysLog(ProceedingJoinPoint point) throws Throwable {
        //先执行业务
        Object result = point.proceed();
        try {
            handle(point);
        } catch (Exception e) {
            log.error("日志记录出错!", e);
        }
        return result;
    }
  •  首先获取的是拦截方法名
 private void handle(ProceedingJoinPoint point) throws Exception {

        //获取拦截的方法名
        Signature sig = point.getSignature();
        MethodSignature msig = null;
        if (!(sig instanceof MethodSignature)) {
            throw new IllegalArgumentException("该注解只能用于方法");
        }
        msig = (MethodSignature) sig;
        Object target = point.getTarget();
        Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
        String methodName = currentMethod.getName();
  •  下方是一些常用操作
//拦截的实体类
Object target = point.getTarget();
//拦截的方法名称
String methodName = point.getSignature().getName();
//拦截的方法参数
Object[] args = point.getArgs();
//拦截的放参数类型
Class[] parameterTypes = ((MethodSignature)point.getSignature()).getMethod().getParameterTypes();
Method m = null;
try {
//通过反射获得拦截的method
m = target.getClass().getMethod(methodName, parameterTypes);
//如果是桥则要获得实际拦截的method
if(m.isBridge()){
  for(int i = 0; i < args.length; i++){
    //获得泛型类型
    Class genClazz = GenericsUtils.getSuperClassGenricType(target.getClass());
    //根据实际参数类型替换parameterType中的类型
    if(args[i].getClass().isAssignableFrom(genClazz)){
     parameterTypes[i] = genClazz;
    }
}
//获得parameterType参数类型的方法
m = target.getClass().getMethod(methodName, parameterTypes);
  • 获取注解上的值写入日志 ,包括业务内容value、主键key、字典类dict
  //获取操作名称
        BussinessLog annotation = currentMethod.getAnnotation(BussinessLog.class);
        String bussinessName = annotation.value();
        String key = annotation.key();
        Class dictClass = annotation.dict();
 @RequestMapping(value = "/add")
    @BussinessLog(value = "添加角色", key = "name", dict = RoleDict.class)
    @Permission(Const.ADMIN_NAME)
    @ResponseBody
    public ResponseData add(@Valid Role role, BindingResult result) {
        if (result.hasErrors()) {
            throw new ServiceException(BizExceptionEnum.REQUEST_NULL);
        }
        role.setId(null);
        this.roleService.insert(role);
        return SUCCESS_TIP;
    }

 


---------------------
参考原文:https://blog.csdn.net/zhanglf02/article/details/78132304

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值