Spring中Aop的使用

AOP概念:AOP是一种设计思想,的目的就是在不修改源代码的基础上,对原有功能进行增强

AOP的实现涉及到spring的动态代理,spring有两种代理方式:

  1.   jdk动态代理:jdk动态代理只提供接口代理,通过实现代理接口创建代理类对象
  2. CGLIB动态代理:CGLIB动态代理是通过继承被代理类创建代理类对象的

spring2.0之前默认是使用JDK动态代理创建代理类对象的,因为JDK动态代理效率较高

spring2.0之改为了CGILB动态代理,因为CGILB动态代理不容易出现转换错误

# true: 使用cglib动态代理(默认)
# false: 使用jdk动态代理
spring.aop.proxy-target-class=false

Spring会根据被代理的类是否有接口去自动选择代理方式

AOP中相关概念

  • Target(目标对象):就是需要增强功能的方法对应的类产生的对象
  • Joinpoint(连接点):目标对象中的所有方法
  • Pointcut (切入点):切入点是连接点的过滤条件,确定要对哪些方法增强。
  • Advice(通知):就是共性功能,一个具体的增强功能(日志、事务...)
  • Aspect (切面):通知 + 切点的配置,切面是一种描述,描述通知和切点之间的关系,一个什么样的增强功能加入到了哪些的切点的什么位置上
  • Proxy(代理):通过代理(JDK/CGLib)创建处理具有增强功能的对象

如果需要使用AOP切面增强方法

第一步:导入jar

<!--AOP相关注解和配置解析实现-->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.7</version>
</dependency>

第二步:准备目标类+增强类

  • 目标类:AccountServiceImpl

    
    @Slf4j //使用Slf4j记录日志
    @Service
    public class AccountServiceImpl implements AccountService {
    
        @Autowired
        private TimeLog timeLog;
    
    
        @Override
        public void updateBalance(Account account) {
            timeLog.start();
    
            //调用mapper完成更新
            System.out.println("updateBalance:"+account);
    
            //记录结束时间并计算耗时
            timeLog.end();
        }
    
        @Override
        public Account getById(Integer id) {
            //记录开始时间
            timeLog.start();
            //模拟从mapper查询的结果
            Account account = new Account(1,"tom",100D);
            //记录结束时间并计算耗时
            timeLog.end();
            
            return account;
        }
        
        @Override
        public List<Account> list() {
            //记录开始时间
            timeLog.start();
    
            //模拟假数据
            ArrayList<Account> accounts = new ArrayList<>();
            accounts.add(new Account(1,"tom",100D));
            accounts.add(new Account(2,"jack",200D));
            accounts.add(new Account(3,"lily",300D));
    
            //记录结束时间并计算耗时
            timeLog.end();
    
            return accounts;
        }
    }

  • 增强类:TimeLog

    @Slf4j
    @Component
    public class TimeLog {
    
        private Long start = 0L;
    
        /*记录开始时间*/
        public void start(){
            //记录开始时间
            start = System.nanoTime();
        }
        
        /*计算耗时*/
        public void end(){
            Long end = System.nanoTime();
            log.info("用时:"+(end-start)+" 纳秒");
        }
    }

 第三步:在增强类中进行切面配置

  1. 加@Aspect和@Component注解:标识这是个增强/增强类并交给容器管理
  2. 定义一个空方法加@Pointcut注解并指定增强位置:标识这个方法是个切点方法
  3. 再在增强类中的方法加环绕注解,指定切点方法:告诉spring在执行切点
@Component
@Aspect
public class LogAspect extends ConsoleHandler {


    //对加了LogAnnotation中所有方法进行增强
    @Pointcut("@annotation(cn.ffcs.uam.usercenter.annotation.LogAnnotation)")
    public void pt() {
    }


    //环绕通知
    @Around("pt()")
    public Object addLog(ProceedingJoinPoint proceedingJoinPoint) {
        Object result = null;
        OperLog operLog = new OperLog();
        try {
            //得到当前方法入参列表
            System.out.println("在被增强方法之前执行");

            //执行目标方法,得到结果
            result = proceedingJoinPoint.proceed()

            //被增强方法之后执行
            System.out.println("实际传入参数:"+ Arrays.toString(proceedingJoinPoint.getArgs()));
            MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
            System.out.println("方法所属类:"+signature.getDeclaringTypeName());
            System.out.println("方法名:"+signature.getName());
            System.out.println("参数类型:"+ Arrays.toString(signature.getParameterTypes()));
            System.out.println("返回值类型:"+signature.getReturnType());
        } catch (Throwable throwable) {

            System.out.println("【环绕通知】在目标方法出现异常之后执行...异常信息:" + throwable.getMessage());
            //一定要把异常抛出去,否则事务失效
            throw new RuntimeException(throwable);

        } finally {

            System.out.println("【环绕通知】不管目标方法是否出现异常都会执行...");

        }
        return result;
    }
}

 ProceedingJoinPoint类常用API

//---------------------------
        System.out.println("实际传入参数:"+Arrays.toString(pjp.getArgs()));
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        System.out.println("方法所属类:"+signature.getDeclaringTypeName());
        System.out.println("方法名:"+signature.getName());
        System.out.println("参数类型:"+ Arrays.toString(signature.getParameterTypes()));
        System.out.println("返回值类型:"+signature.getReturnType());
        //---------------------------
        result = pjp.proceed();//执行目标方法

 当一个方法有多个切面增强类匹配时

切面类按照bean名称默认规则或者order排序起来,先执行所有的前置通知---->执行被增强方法(被增强方法只执行一次)----->后置通知

前置通知先执行排序最小的,后置通知先执行排序最大的

通知起作用的前提:执行该方法的对象是被代理对象

被增强类方法中调用本类其他方法时会导致通知失效,因为本类方法中调用本类其他方法本质是本类对象调用,而非代理对象,故会出现通知失效的情况,解决方法:

1.将本类注入进本类局部变量中,并打开循环依赖

/*
通知失效
 */
@Service
@Slf4j
public class AdviceInvalid {
    //注入代理对象,但是此时出现自己依赖自己,循环依赖
    @Autowired
    AdviceInvalid adviceInvalid;

    public void fun1(){
        log.info("fun1()执行了...");
        //方法内部调用当前对象的方法
        //this.fun2();
        //System.out.println(this); //打印调用方法的对象
        adviceInvalid.fun2(); //使用代理对象调用方法
    }

    public void fun2(){
        log.info("fun2()执行了...");
        System.out.println(this);
    }

}
spring.main.allow-circular-references=true

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值