通知方法中可以声明参数:
- 任何advice通知方法的第一个参数都可以被声明为org.aspectj.lang.JoinPoint类型
- JoinPoint是连接点方法的抽象,提供了访问当前被通知方法的目标对象、代理对象、方法参数等数据的方法;
- 经过测试发现,如果要在通知方法中声明JoinPoint参数,那么它必须是第一个,否则会报错;
- 环绕通知的方法的第一个参数必须是ProceedingJoinPoint类型
- 通过returning传递目标方法的返回值或者throwing传递抛出的异常给相关的后置通知或者异常通知方法
- 通知方法中可以声明使用args(xxx)所切到的方法传入的参数,但是声明这样的参数有要求:
- 第一点-在通知注解或xml的arg-names中,最好与通知方法的参数名保持一致(参数名可以不一致,一致的话,就可以省略arg-names了,不一致的话,就必须得写arg-names),多个参数之间使用逗号隔开,并且参数顺序和参数个数要与通知方法对应上
- 第二点-arg-names中逗号隔开的参数中,除了Joinpoint类型参数外,其它的参数都应该能在切点表达式中找到(或者是以returning或throwing所声明),而且切点表达式中args(xxx),不能写通知方法中未声明的方法参数名(当然里面可以写全类名)
- 比如:之前我们可以这样写-args(int),这表示切入所有只有一个参数,并且第一个参数是int类型的方法,但是如果要在通知方法中访问到这个参数,我们就应当这样写切点表达式-arg(i) ,但是这样显然意思就变了,真变了吗?没有变!首先arg(i)表示切入到只有一个参数的方法,至于这个参数是什么类型的,这里并没有指定,然后就会拿着这个参数i这个名字,到通知的arg-names中,找到是第几个参数(比如是第N个),然后到通知方法中,找到第N个参数,这里就有类型了,类型不匹配的话,这个通知方法是不会执行的!类型匹配就执行!
- 再补充一点:切点表达式中所写的args参数名与通知方法的参数名一致时,arg-names属性是可以省略的
- 通知方法中可以声明使用@annotation(xxx)所切入的方法上的注解实例
- 使用方法:使用逻辑是-在arg-names中填写通知方法参数名(使用逗号隔开,并且可以与通知方法参数名不一致,但是,参数的顺序、个数必须要一致。如果arg-names省略不写,那就意味着默认是通知方法的所有参数的参数名,按顺序,使用逗号隔开,拼接作为arg-names的值),@annotation(xxx)中使用的参数名必须要在arg-names中(否则无法确定类型嘛)。
AOP通知执行顺序
package com.zzhua.aspect;
import com.alibaba.fastjson.JSON;
import com.zzhua.enums.StatusEnums;
import com.zzhua.pojo.Log;
import com.zzhua.service.LogService;
import com.zzhua.utils.StringUtils;
import com.zzhua.utils.ThreadLocalContext;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Date;
@Aspect
@Component
@Slf4j
public class RequestAspect {
    @Autowired
    private LogService logService;
    /**
     * 正常执行顺序: 请求先到达 -> 环绕通知,调用连接点时 -> 进入前置通知 -> 执行controller方法 
     *                                                                 -> 回到环绕通知连接点完毕处 -> 后置通知 -> 返回通知
     *                         
     * 
     * 异常执行顺序: 请求先到达 -> 环绕通知,调用连接点时 -> 进入前置通知 -> 执行controller方法(发生异常) 
     *                                                                 -> 后置通知 -> 异常通知 -> 全局异常处理器
     * 
     */
    @Pointcut("execution(* com.zzhua.controller..*(..))")
    public void pointCut() {
    }
    /**
     * 请求进入controller方法前拦截
     * @param joinPoint
     */
    @Before("pointCut()")
    public void doBefore(JoinPoint joinPoint) {
        // Tomcat在处理请求时,封装request对象,并将此对象放入ThreadLocal变量中(键为当前线程对象)
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        log.info("请求地址: {}", request.getRequestURI());
        log.info("请求方式: {}", request.getMethod());
        // 获取真实ip
        log.info("IP: {}" , StringUtils.getRemoteIp(request));
        // 访问方法
        log.info("方法: {}" , joinPoint.getSignature());
        // 访问参数
        log.info("参数: {}" , Arrays.toString(joinPoint.getArgs()));
        Log logger = ThreadLocalContext.get().getLog();
        logger.setLogUrl(request.getRequestURI());
        logger.setLogParams( Arrays.toString(joinPoint.getArgs()));
        logger.setLogStatus(StatusEnums.REQUEST_SUCCESS.getCode());
        logger.setLogMethod(request.getMethod());
        logger.setLogIp(StringUtils.getRemoteIp(request));
    }
    @After("pointCut()")
    public void doAfter(){
        // System.out.println("doAfter...");
    }
    /**
     * 环绕通知,统计请求花费时间
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("pointCut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long beginTime = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long endTime = System.currentTimeMillis();
        long duration = endTime - beginTime;
        log.info("耗时: {}" , duration);
        ThreadLocalContext.get().getLog().setLogTime(duration);
        return result;
    }
    /**
     * 返回通知 (如果发生异常,不会走此通知,后置通知会走)
     * @param ret
     */
    @AfterReturning(value = "pointCut()",returning = "ret")
    public void doAfterReturning(Object ret){
        log.info("返回: {}",JSON.toJSONString(ret) );
        Log logger = ThreadLocalContext.get().getLog();
        logger.setLogResult(JSON.toJSONString(ret));
        logger.setCreatedTime(new Date());
        logService.insert(logger);
    }
    /**
     * 异常通知
     * @param ex
     */
    @AfterThrowing(value = "pointCut()", throwing = "ex")
    public void doAfterThrowing(Exception ex){
        Log logger = ThreadLocalContext.get().getLog();
        logger.setLogStatus(StatusEnums.REQUEST_ERROR.getCode());
        logger.setLogMessage(StringUtils.getPackageException(ex,"com.zzhua"));
        logger.setLogTime(0L);
        logger.setCreatedTime(new Date());
        logService.insert(logger);
    }
}                
 
 
                     
            
        













 
                    

 
                 
                    