• AOP的概念和使用


    Spring有两个核心概念,一个是IOC/DI,另一个是AOP

    AOP:面向切面编程(Aspect Oriented Programing)

    OOP:面向对象编程(Object Oriented Programing)

    AOP功能:在不惊动原始设计的基础上增强代码,符合Spring的无入侵式/无侵入式思想

            

     的案例中BookServiceImpl中有save , update , delete和select方法,这些方法我们给起了一 个名字叫连接点

    四个方法中,update和delete只有打印没有计算万次执行消耗时间, 但是在运行的时候已经有该功能,那也就是说update和delete方法都已经被增强,所以对于需要增 强的方法我们给起了一个名字叫切入点

    通知是要增强的内容,会有多个,切入点是需要被增强的方法,也会有多个,那哪个切入点需要添 加哪个通知,就需要提前将它们之间的关系描述清楚,那么对于通知和切入点之间的关系描述,我们 给起了个名字叫切面

    通知是一个方法,方法不能独立存在需要被写在一个类中,这个类我们也给起了个名字叫通知类

    总结

    在SpringAOP中,一个切入点可以描述一个具体方法,也可也匹配多个方法

    一个具体的方法:如com.itheima.dao包下的BookDao接口中的无形参无返回值的save方 法 匹配多个方法:

    所有的save方法,所有的get开头的方法,所有以Dao结尾的接口中的任意 方法,所有带有一个参数的方法

    AOP实例

    使用注解开发

    1.导入坐标

    在pom.xml文件中导入相关坐标

    2.制作连接点(原始操作,dao接口,实现类)

    添加bookdao和bookdaoimpl类

    1. public class BookDaoImpl implements BookDao {
    2. public void save() {
    3. System.out.println(System.currentTimeMillis());
    4. System.out.println("book dao save ...");
    5. }
    6. public void update(){
    7. System.out.println("book dao update ...");
    8. }
    9. }

     创建Spring配置类

    编写APP运行类

    3.制作共性功能(通知类和通知)

    通知就是将共性功能抽取出来后形成的方法,共性功能指的就是当前系统时间的打印。

    1. @Component //通知类必须配置成Spring管理的bean
    2. @Aspect //设置当前类为切面类类
    3. public class MyAdvice {
    4. //设置切入点,要求配置在方法上方
    5. @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    6. private void pt(){}
    7. //设置在切入点pt()的前面运行当前操作(前置通知)
    8. // @Before("pt()")
    9. public void method(){
    10. System.out.println(System.currentTimeMillis());
    11. }
    12. }

    4.定义切入点

    通过@Pointcut定义切入点,且依赖一个不具有实际意义的方法(pt())进行,配置在pt()方法上方,

    5.绑定切入点与通知关系(制作切面)

    通过@Before绑定切入点和通知的关系,设置在切入点的前面运行操作(前置)

    6.开启注解格式AOP功能

    @EnableAspJAutoproxy

    1. @Configuration
    2. @ComponentScan("com.itheima")
    3. @EnableAspectJAutoProxy
    4. public class SpringConfig {
    5. }

     

     AOP工作流程

    1.容器启动

    2.读取所有切面配置中的切点

    3.初始化bean

    此时要被实例化的bean对象类中的方法和切入点进行匹配

     

    匹配成功就,创建原始对象的代理对象,匹配成功说明需要对其进行增强

    匹配失败,创建原始对象

    对哪个类做增强,这个类对应的对象就叫做目标对象

    因为要对目标对象进行功能增强,而采用的技术是动态代理,所以会为其创建一个代理对象

    最终运行的是代理对象的方法,在该方法中会对原始方法进行功能增强

    4.获取bean执行方法

    获取的bean是原始对象时,调用方法并执行,完成操作

    获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作

    结论:容器中存入的是目标对象的代理对象

    AOP的两个核心概念

    目标对象(Target):原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终 工作的

    代理(Proxy):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实 现

    上面这两个概念比较抽象,简单来说, 目标对象就是要增强的类[如:BookServiceImpl类]对应的对象,也叫原始对象,不能说它不能运 行,只能说它在运行的过程中对于要增强的内容是缺失的。 SpringAOP是在不改变原有设计(代码)的前提下对其进行增强的,它的底层采用的是代理模式实现 的,所以要对原始对象进行增强,就需要对原始对象创建代理对象,在代理对象中的方法把通知 [如:MyAdvice中的method方法]内容加进去,就实现了增强,这就是我们所说的代理(Proxy)。

    AOP切入点表达式 

    1.语法格式

    切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数) 异常 名)

    1. //通过以下的例子来理解格式
    2. execution(public User com.itheima.service.UserService.findById(int))

    2.通配符

    1. execution(void com.itheima.dao.BookDao.update())
    2. //匹配接口,能匹配到
    3. execution(void com.itheima.dao.impl.BookDaoImpl.update())
    4. //匹配实现类,能匹配到
    5. execution(* com.itheima.dao.impl.BookDaoImpl.update())
    6. //返回值任意,能匹配到
    7. execution(* com.itheima.dao.impl.BookDaoImpl.update(*))
    8. //返回值任意,但是update方法必须要有一个参数,无法匹配,要想匹配需要在update接口和实现类添加参数
    9. execution(void com.*.*.*.*.update())
    10. //返回值为void,com包下的任意包三层包下的任意类的update方法,匹配到的是实现类,能匹配
    11. execution(void com.*.*.*.update())
    12. //返回值为void,com包下的任意两层包下的任意类的update方法,匹配到的是接口,能匹配
    13. execution(void *..update())
    14. //返回值为void,方法名是update的任意包下的任意类,能匹配
    15. execution(* *..*(..))
    16. //匹配项目中任意类的任意方法,能匹配,但是不建议使用这种方式,影响范围广
    17. execution(* *..u*(..))
    18. //匹配项目中任意包任意类下只要以u开头的方法,update方法能满足,能匹配
    19. execution(* *..*e(..))
    20. //匹配项目中任意包任意类下只要以e结尾的方法,update和save方法能满足,能匹配
    21. execution(void com..*())
    22. //回值为void,com包下的任意包任意类任意方法,能匹配,*代表的是方法
    23. execution(* com.itheima.*.*Service.find*(..))
    24. //将项目中所有业务层方法的以find开头的方法匹配
    25. execution(* com.itheima.*.*Service.save*(..))
    26. //将项目中所有业务层方法的以save开头的方法匹配

     3.书写技巧

    AOP通知类型

    前置通知

    后置通知

    环绕通知(重点)

    返回后通知(了解)

    抛出异常后通知(了解)

     前置通知:@Before

    @Pointcut定义任意一个切入点,在update()方法上,此时update()方法就是切入点,切点依赖pt()方法

    @Before表示前置通知,@Before("pt()")表示切入点和通知已经绑定,通知在切入点之前运行通知

    后置通知@After

    1. @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    2. private void pt(){}
    3. @After("pt()") //表示在切入点之后运行该通知
    4. public void after() {
    5. System.out.println("after advice ...");
    6. }

    环绕通知@Around()

    使用环绕通知,需要对原始操作进行增强,因为环绕通知需要在原始方法的前后进行增强

    1. @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    2. private void pt(){}
    3. @Around("pt()")
    4. public void around(ProceedingJoinPoint pjp) throws Throwable{
    5. System.out.println("around before advice ...");
    6. //表示对原始操作的调用
    7. pjp.proceed();
    8. System.out.println("around after advice ...");
    9. }

     原始方法中存在返回值的处理时,例如

    1. public int select(){
    2. System.out.println("book dao select is running");
    3. return 100;
    4. }
    5. @Pointcut("execution(int com.itheima.dao.BookDao.select())")
    6. private void pt2(){}
    7. @Around("pt2()")
    8. public void around(ProceedingJoinPoint pjp) throws Throwable{
    9. System.out.println("around before advice ...");
    10. //表示对原始操作的调用
    11. pjp.proceed();
    12. System.out.println("around after advice ...");
    13. }
    14. int num = bookDao.select();
    15. System.out.println(num);

    运行出现错误,空的返回不匹配原始方法的int返回 

     void就是返回Null 原始方法就是BookDao下的select方法

    所以如果我们使用环绕通知的话,要根据原始方法的返回值来设置环绕通知的返回值,具体解决方案 为:

    1. @Around("pt2()")
    2. public Object aroundSelect(ProceedingJoinPoint pjp) throws Throwable {
    3. System.out.println("around before advice ...");
    4. //表示对原始操作的调用
    5. Object ret = pjp.proceed();
    6. System.out.println("around after advice ...");
    7. return ret;
    8. }

    object类型比int类型更通用 

    返回后通知@AfterReturning

    异常后通知@AfterThrowing

     

     1.环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法 调用前后同时添加通知

    2. 通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行

    3. 对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,最好设定为 Object类型

    4. 原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成 Object

    5. 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须要处理Throwable异常

  • 相关阅读:
    嵌入式编程实现记录开发板启动次数
    2022年11月5日(星期六)骑行回回营村
    数据结构与算法:排序专题
    入门数据库Days4
    本地提权的学习
    在CentOS7系统中安装MySQL5.7
    springboot+个人博客 毕业设计-附源码191613
    Java面试题学习-单例模式
    【C++】特殊类的设计(只在堆、栈创建对象,单例对象)
    基于51单片机的花样流水灯设计
  • 原文地址:https://blog.csdn.net/cvpatient/article/details/125979723