AOP.

本文详细介绍了Spring AOP的概念,包括连接点、切入点、通知和切面,并通过实例展示了如何在不修改原有业务代码的前提下,利用AOP实现方法调用前后的额外功能。此外,还探讨了AOP的代理对象工作原理,以及切入点表达式的简化方法。文章进一步讲解了不同类型的AOP通知,包括前置通知、后置通知和环绕通知,通过代码示例说明它们在实际应用中的作用和区别,并解决了环绕通知中处理返回值的问题。
摘要由CSDN通过智能技术生成

 一、AOP简介

注意:跑update、delete、select方法的时候,也想能够运行到该万次耗时计算的功能的前提是: update、delete、select方法中没有该功能 并且不动update、delete、select方法中的代码逻辑

 再次理解:

连接点:也可以理解为所有的方法

切入点:代表追加某个功能的方法

通知:也就是连接点共性的功能

切面:可以说是绑定通知和切入点的关系

通知类:MyAdvice 

二、AOP入门

 2.1、没有使用AOP思想时代码演示如下:

App:

 SpringConfig:

 com.itheima包下加了bean注解的BookDaoImpl:

 App测试程序中调用bean中的save()方法时输出结果如下所示:

 App测试程序中调用bean中的update()方法时输出结果如下所示:

 思考:假设update()方法当中也想打印系统时间的功能,但前提是在update()方法中不能写该打印系统时间的功能(也就是说:不动update方法中的逻辑代码)的情况下,有什么办法能够做到呢:

2.2、那么就用到了AOP面向切面编程的思想

第一步:导入坐标 <aspectjweaver>

第二步:制作连接点方法 (也就是上面演示的save()、update()方法)

第三步:制作共性功能 (也就是说把想要共性的打印系统时间的功能抽出来单独放入通知类MyAdvice中)

第四步:定义切入点(也就是说,哪个类中的方法想添加新的共性功能,那么就切入到该类的方法名下)

第五步:绑定切入点与通知关系(切面)

package com.itheima.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component  // 通知类必须配置成Spring管理的bean
@Aspect  // 该注解的目的是让spring知道这个MyAdvice类是专门储存处理共性功能的类

public class MyAdvice {

    /**
     *
     * @Pointcut("execution(void com.itheima.dao.BookDao.update())") 切入的是该包下的update方法中
     *  第二步:、设置切入点 (方法名任意取 这里取的是pt)
     */
    // execution(void com.itheima.dao.BookDao.update()) 表示切入的是该包下的update()方法(返回类型void)
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}


    /**
     *  @Before("pt()") 注解
     *  绑定切入点与通知之间的关系 : 设置在切入点pt()的前面运行当前操作(前置通知)
     */
    @Before("pt()")
    /**
     *  第一步:、把共性功能【打印系统时间的功能】抽出写入单独的方法中(方法名任意取 这里取的是method)
     */
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

总体代码如下所示:

App:

package com.itheima;

import com.itheima.config.SpringConfig;
import com.itheima.dao.BookDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App {
    public static void main(String[] args) {

        // 1、获取IOC容器
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);

        // 2、获取IOC容器中管理的bean
        BookDao bookDao = ctx.getBean(BookDao.class);
        bookDao.update();

    }
}

SpringConfig: (注解不能少)

package com.itheima.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy // 该注解的作用是:告诉spring项目中有用注解开发的AOP
public class SpringConfig {
}

com.itheima包下含有bean注解的BookDaoImpl:

package com.itheima.dao.impl;

import com.itheima.dao.BookDao;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;

@Repository
public class BookDaoImpl implements BookDao {

    public void save() {
        System.out.println(System.currentTimeMillis()); // 打印系统时间的功能
        System.out.println("book dao save ...");
    }

    public void update(){
        System.out.println("book dao update ...");
    }
}

MyAdvice:

package com.itheima.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component  // 通知类必须配置成Spring管理的bean
@Aspect  // 该注解的目的是让spring知道这个MyAdvice类是专门储存处理共性功能的类

public class MyAdvice {

    /**
     *
     * @Pointcut("execution(void com.itheima.dao.BookDao.update())") 切入的是该包下的update方法中
     *  第二步:、设置切入点 (方法名任意取 这里取的是pt)
     */
    // execution(void com.itheima.dao.BookDao.update()) 表示切入的是该包下的update()方法(返回类型void)
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}


    /**
     *  @Before("pt()") 注解
     *  第三步:绑定切入点与通知之间的关系 : 设置在切入点pt()的前面运行当前操作(前置通知)
     */
    @Before("pt()")
    /**
     *  第一步:、把共性功能【打印系统时间的功能】抽出写入单独的方法中(方法名任意取 这里取的是method)
     */
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

运行程序结果如下所示:

会发现拿到的IOC容器管理的bean后,调用的update()方法中本来没有打印系统时间的功能,但是结果我们缺拿到了该功能,这就是面向切面编程AOP的作用

三、AOP内部是使用代理对象形式进行工作的

四、AOP切入点表达式

 

 那么我们再描述切入点的时候,要按照上面的切入点表达式标准格式写,但是当我们项目中有很多需要描述切入点方法的时候,我们要按着标准的格式写的话是非常多的麻烦的,因此我们有什么简单的方法来简化上面的标准格式呢:

五、AOP通知类型

 5.1、前置通知:@Before("#")注解

代码演示如下:

BookDaoImpl:

 通知类:

 程序测试:

 输出结果分析:(使用前置通知注解后,输出结果的时候共性功能会先被调用输出)

5.2、后置通知:@After("#")注解

代码演示如下:

通知类:

 输出结果:

 5.3、环绕通知:@Aound(“#”)注解     (以后常用)

代码演示如下:

BookDaoImpl:

 通知类:(假定BookDaoImpl类中的update方法在不动代码的情况下,也想执行共性方法around方法)

 代码测试如下:

 会发现:使用环绕通知注解后,相比着前置/后置通知,输出结果不一样了,前置/后置通知不仅会调用原始自己的update方法,也会调用共性的方法,而使用环绕通知注解后,调用update()方法的时候,缺只调用了共性的功能 around()方法,update()方法缺没有调用

那么我们怎么让代码即调用输出原始自己的update()方法,又调用输出共性功能的方法呢:

1、共性功能方法中加参数ProceedingJoinPoint

2、pjp.proceed()    【该意思就是调用自己原始的方法如update()方法,注意:有返回类型的记得接收返回值类型】

注意1:pjp.proceed()放在共性功能输出around before advice 和  around after advice 的前后中间位置都可以,放在前面的话就先调用输出原始的update()方法....  pjp.proceed()其实就是调用执行原始的update()方法的

注意2:pjp.proceed()方法:其实就是调用原始自己的方法的(如原始自己的update方法)

原始自己的方法中如果有返回值类型的话,还可以接收返回值类型和返回值的值,如果不接收,那么就会报错

 输出结果演示:

 5.3.1、但是使用环绕通知会有一个问题

就是:当我们的原始连接点的方法如:select()方法的有返回值类型的时候【如:int select();】,那么就有可能不处理的话就会抛出异常

代码演示如下:

BookDaoImpl:

 原始select方法中有返回值类型int

通知类:

 程序测试结果:

 5.3.2、出现异常的原因(pjp.proceed();到底调哪个方法,就看测试程序调用的是哪个方法,这里程序测试时调用的时select()方法,因此pjp.proceed()调用的就是select()方法

 因此我们上面的代码,需要pjp.proceed()在调用原始的方法时,当原始的方法有返回类型的时候,需要接收一下返回类型然后返回,(直接用Object即可,因为Object是int long.....的所有的父亲)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值