1. AOP

  • AOP (Aspect Orient Programming) 面向切面编程是从动态角度考虑程序的运行过程
  • AOP底层,就是采用动态代理模式实现的,采用了两种代理,JDK的动态代理和CGLib的动态代理
  • 实际上AOP就是动态代理的一种规范化,因为动态代理种类繁多,掌握较难,所以就规范了一套统一的方式,这就是AOP
  • AOP把动态代理的实现步骤,方式都定义好了,让开发人员用一种统一的方式,去用动态代理。

    1.1 动态代理实现方式

    JDK:

  • jdk动态代理,要求jdk中有Proxy,Method,InvocationHandler创建代理对象

  • jdk代理要求目标类必须实现接口

CGLib:

  • 第三方的工具库,创建代理对象,原理是继承,通过继承目标类创建子类,子类就是代理对象
  • CGLib要求目标类不能是final的 方法也不能是final的

    1.2 动态代理的作用:

  1. 可以在目标类源代码不改变的情况下去增加功能
  2. 减少重复代码
  3. 专注业务逻辑代码
  4. 解耦合,让你的业务功能和日志,事务非业务功能分离

    1.3 理解AOP面向切面编程

    AOP Aspect:切面 给你目标类增加的功能就是切面,就比如要添加的日志 他就属于切面
    切面的特点:一般都是非业务方法,可以独立使用
    AOP Orient:面向
    AOP Programming:编程

  5. 需要在分析项目功能时,找出切面。(并不是所有功能都能当切面)

  6. 合理的安排切面的执行时间(在目标方法前面,还是在目标方法后面)
  7. 合理的安排切面执行的位置,在哪个类,在哪个方法增加增强功能

    1.4 什么时候使用AOP技术

  • 当你要给一个项目存在的类修改功能,但是原有的类的功能不完善,并且没有源代码,就可以使用aop增加功能
  • 给项目中的多个类,增加一个相同的功能,使用aop
  • 给业务方法增加是事务,日志输出

    1.5 面向切面编程的术语

  • Aspect:切面,表示增强的功能,就是一堆代码,完成某一个功能,非业务功能,

常见的切面功能:日志,事务,统计信息,参数检查,权限验证

  • JoinPoint:连接点 连接业务方法和切面的位置,其实就是类中的业务方法
  • Pointcut:切入点 指多个连接点方法的集合
  • 目标对象:给哪个类的方法添加功能,这个类就是目标对象
  • Advice:通知, 通知表示切面功能执行的时间,(在方法之前还是在方法之后)

    1.6 一个切面有三个关键的要素:

  1. 切面的功能代码,Aspect 切面要干什么
  2. 切面的执行位置,Pointcut 也就是在那个方法里加功能
  3. 切面执行的时间,Advice 也就是在方法前还是后

    2. Aspect/切入点表达式

    2.1 AOP

    AOP是一个规范,是动态的一个规范化,一个标准

    2.2 AOP的技术实现框架:

    Spring:
    spring在内部中实现了AOP规范,能做AOP的工作
    spring主要在事务处理时使用AOP
    我们项目开发中很少使用spring的AOP实现,因为springAOP比较笨重。
    AspectJ:
    一个开源的专门做AOP的框架,是业内最专业的AOP框架,又精又厉害
    spring中已经集成了AspectJ的框架,所以通过spring就可以直接使用AspectJ的功能了

    2.3 Aspect实现AOP有两种方式

  4. 使用xml配置文件:配置全局事务

  5. 使用注解,我们在项目中要做AOP功能,一般都是用注解,aspectJ有5个注解【表示切面的执行时间】

    2.4 AspectJ框架的使用

    切面的执行时间,这个执行时间在规范中,他叫做通知/增强 Advice
    在AspectJ中使用注解来表示,当然也可以使用xml文件中的标签
  • **@Before**
  • **@AfterReturn**``**ing**
  • **@Around**
  • **@AfterThrowing**
  • **@After**

    2.5 切入点表达式

    表示切面执行的位置使用的是 切入点表达式
    1. execution(modifiers-pattern? ret-type-pattren
    2. declaring-type-pattern?name-pattern(param-pattern)
    3. throws-pattern?)
    execution:关键字
    小括号开始 小括号结束
    第一个参数:方法的访问修饰符【modifiers-pattern】 后面问号表示可选,可以不填
    然后一个空格
    第二个参数:方法返回值的数据类型【ret-type-pattren】
    第三个参数:方法所在的包名类名 ?【declaring-type-pattern】
    第四个参数:方法名【name-pattern】
    (param-pattern)方法的参数,只填写类型就行,形参名不需要
    第五个:方法所抛出的异常

    execution(方法的访问修饰符? 方法的返回值类型 方法所在的包名类名? 方法名(方法的参数) 方法所抛出的异常?)
    ? 表示可选的部分。
    以上表达式最重要的四个部分
    Execution(访问权限 方法返回值 方法声明(参数) 异常类型)

    在parttern的地方表示都可以使用通配符

    2.6通配符

    | 符号 | 意义 | | —- | —- | | * | 0至多个任意字符 | | .. | 用在方法参数中,表示任意多个参数
    用在包名后,表示当前包及其子包路径 | | + | 用在类名后面,表示当前类及其子类
    用在接口后,表示当前接口及实现类 |

例如:

  • execution(public (..)) 指定切入点为:任意功能方法
  • execution( set(..)) 指定切入点为:任意一个以 set 开始的方法
  • execution( com.xyz.service..*(..)) 切入点:service包中的所有类 的所有方法参数任意(子包中不算)
  • execution( com.xyz.service...*(..)) 切入点:定义在service或者子包中任意类的任意方法
  • ..出现在类名中时,后面必须跟”*“表示包,子包下的所有类
  • execution( ..service..(..)) + 指定所有包下的service子包下所有类(接口)中所有方法为切入点
  • execution( .service..(..)) 指定只有以及包下的service子包下所有类(接口)中所以后方法为切入点

    3. AspectJ实现步骤(注解方式)

    使用AspectJ框架实现AOP(给一些已经存在的类和方法,增加额外的功能【前提是不改变原来类的代码】)
  1. 先创建一个maven功能

    • 加入spring依赖
    • 加入aspectJ依赖
      1. <dependency>
      2. <groupId>org.springframework</groupId>
      3. <artifactId>spring-aspects</artifactId>
      4. <version>5.2.5.RELEASE</version>
      5. </dependency>
  2. 创建目标类:必须有接口和他的实现类(底层目前是JDK动态代理)

给类中的方法增加功能

  1. //目标类
  2. @Component
  3. public class SomeServiceImpl implements SomeService{
  4. @Override
  5. public String doSome(String s) {
  6. //给doSome方法增加功能,在方法执行之前 输出方法的执行时间
  7. System.out.println("SomeServiceImpl的doSome()方法执行了");
  8. System.out.println(s);
  9. return s;
  10. }
  11. }
  1. 创建切面类:就是一个普通类
    1. 在类的上面添加注解 **@Aspect**
    2. 在类中定义方法,方法就是切面要执行的功能代码
    3. 在方法的上面加入aspectJ中的通知注解,例如**@Before**
    4. 指定切入点表达式execution() execution(public void ``com.yixuexi.ba01.SomeServiceImpl.doSome(..)) ```java /*
  • 注解作用:AspectJ框架中的注解,表示当前类是切面类
  • 切面类:是用来给业务方法增加功能的类,在这个类中有很多的功能代码
  • 使用位置:类的上面
  • / @Component @Aspect public class MyAspect { /在类里面定义方法,方法是实现切面功能的

    • 方法的定义要求
    • 1.公共方法
    • 2.方法没有返回值
    • 3.方法名称自定义
    • 4.方法可以有参数,也可以没有参数
    • 如果有参数,参数不是自定义的,有几个参数类型可以使用… *
    • / /
    • @Before:前置通知注解
    • 属性:value 是切入点表达式,表示切面的功能执行的位置
    • 位置:在方法的上面添加注解@Before
    • 特点:
    • 1.在目标方法之前先执行
    • 2.不会改变目标方法的执行结果
    • 3.不会影响目标方法的执行
    • / @Before(value = “execution(public (..))”) public void myBefore(JoinPoint joinPoint){ / System.out.println(joinPoint.getSignature().getDeclaringTypeName()); System.out.println(joinPoint.getSignature()); for (Object arg : joinPoint.getArgs()) {

      1. System.out.println(arg);

      }*/ //切面要执行的功能代码 System.out.println(“前置通知,切面功能:在目标方法执行之前输出时间” + new Date()); }

      /保证returning的值和方法的形参名一致就行/ @AfterReturning(value = “execution( (..))”, returning=”res”) public void doOther(Object res){ res += “Hello world”; System.out.println(res); System.out.println(“后置通知执行”); } } ```

  1. 创建spring的配置文件,在文件中声明对象,把对象交给容器统一管理(IOC管理)

    • 声明对象可以使用xml或者注解的方式
    • 声明目标对象(使用注解也可以)
    • 声明切面类对象(使用注解也可以)
    • 声明AspectJ框架中的自动代理生成器标签(自动代理生成器:完成代理对象自动创建功能)
      1. <aop:aspectj-autoproxy/>
      2. <!--声明自动代理生成器,使用aspectJ框架内部的功能,创建目标对象的代理对象
      3. 创建代理对象是在内存中实现的,修改目标对象的内存中的结构。创建为代理对象
      4. 所以目标对象就是被修改后的代理对象
      5. aop:aspectj-autoproxy:会把spring容器中所有的目标对象一次行都生成代理对象
      6. -->
  2. 创建测试类,从spring容器中获取目标对象(实际就是代理对象)

    1. 通过代理执行方法,实现aop的功能增强
    2. 这个返回的就不是哪个类的对象了,而是他的接口的实现类

      1. @org.junit.Test
      2. public void test(){
      3. String path = "application-context.xml";
      4. ApplicationContext ac = new ClassPathXmlApplicationContext(path);
      5. //从容器中获取目标对象,获取的目标对象不再是原来的类型 而是它的接口类型
      6. //实际上是 获取的就是代理对象
      7. String applicationName = ac.getApplicationName();
      8. System.out.println(applicationName);
      9. SomeService someServiceImpl = (SomeService) ac.getBean("someService");
      10. //通过代理对象执行方法,完成在调用方法时增强功能
      11. someServiceImpl.doSome("我是你爸爸");
      12. }

4. 前置/后置/环绕通知注解

表示切面的执行时间使用的是通知注解

4.1 前置通知注解 @Before

  • 在切面类的切面方法上使用**@Before** 表示在目标方法执行前执行(表示执行的时间,是在前还是在后)
  • 属性:**value**是切入点表达式,表示切面的功能执行的位置
  • 位置:在方法的上面添加注解@Before
  • 特点:

    1. 1.在目标方法之前先执行<br /> 2.不会改变目标方法的执行结果<br /> 3.不会影响目标方法的执行
  • 前置通知方法的定义要求

1.公共方法
2.方法没有返回值
3.方法名称自定义
4.方法可以有参数,也可以没有参数
如果有参数,参数不是自定义的,有几个参数类型可以使用…
未命名图片.png

4.2 后置通知 @AfterReturning

@AfterReturning
属性:

  1. value切入点表达式
  2. Returning自定义变量,表示目标方法的返回值

自定义的变量名必须和通知方法的形参名一样
使用位置:在方法定义的上面
特点

  1. 在目标方法执行之后执行
  2. 能够获取到目标方法的返回值,根据返回值做不同的处理功能

Object res = 调用目标类的目标方法返回值

  1. 可以修改返回值
    • 如果是引用传递 那么可以修改值,也就是说传递的是一个对象的话 返回值是可以在通知方法中修改的

因为传递的是内存中的同一个对象,通知方法中修改的也是同一个对象

  • 如果是值传递,也就是说传递的是字符串或者是数字的话 那么返回值是不能在通知方法里修改的

后置通知方法

  1. 公共方法
  2. 方法没有返回值
  3. 方法名称自定义
  4. 方法有参数的 推荐使用Object ```java /保证returning的值和方法的形参名一致就行/ @AfterReturning(value = “execution( (..))”, returning=”res”) public void myAfterReturning(Object res){ 一致 //res就是目标方法的返回值 System.out.println(“后置通知执行”); }

后置通知的执行 res相当于: Object res = doOther(); // 目标方法 myAfterReturning(res); // 在把目标方法的返回值 传递给通知方法

  1. <a name="P6TSE"></a>
  2. ### ![未命名图片.png](https://cdn.nlark.com/yuque/0/2021/png/12374561/1617343183910-284ad69e-8fc9-4bf5-941e-bb6a87230ba7.png#height=293&id=MjdCK&margin=%5Bobject%20Object%5D&name=%E6%9C%AA%E5%91%BD%E5%90%8D%E5%9B%BE%E7%89%87.png&originHeight=293&originWidth=972&originalType=binary&ratio=1&size=301456&status=done&style=none&width=972)
  3. <a name="CCZv6"></a>
  4. ### @After和@AfterReturning的区别
  5. @After通知是最终通知 也就是说就算抛出了异常 @After通知也是可以执行的,有点向finally<br />但是@AfterReturning不是 一但抛出异常@AfterReturning不执行
  6. <a name="5mGvc"></a>
  7. ## 4.3 指定通知方法的形参
  8. > (被通知注解修饰的方法叫通知方法)
  9. **[所有的通知方法都能使用 必须是形参的第一位]**<br />JoinPoint:代表的是业务方法 也就是`@Before("execution(public void ``**doSome**``(..))")`里的doSome方法<br />要加入切面功能的业务方法
  10. 作用是:在通知方法中获取方法执行时的信息,例如**方法名称,方法的实际参数**<br />**如果你的切面功能中需要用到方法的信息,就加入JoinPoint**<br />**使用要求**:<br />JionPoint的值是框架自动赋予的,必须是第一个位置的参数<br />
  11. - 获取方法的形参 ** **`** joinPoint.getArgs()**` //返回一个String数组 ,遍历即可
  12. - 获取方法的定义`joinPoint.getSignature() `
  13. <a name="wCef0"></a>
  14. # 5. 环绕/异常/最终通知注解
  15. <a name="nUnck"></a>
  16. ## 5.1 `@Around`环绕通知
  17. :::info
  18. 经常用来做 事务 在目标方法之前开启事务,目标方法之后结束事务
  19. :::
  20. **环绕通知方法的定义和格式**
  21. 1. public方法
  22. 1. 必须有返回值,推荐使用Object
  23. 1. 方法名称自定义
  24. 1. 方法有参数,固定参数 ProceedingJoinPoint
  25. ```java
  26. @Around("execution(public void doSome())")
  27. public Object myAspect(ProceedingJoinPoint pjp) throws Throwable {
  28. return null;
  29. }

@Around

  1. 属性:value 切入点表达式
  2. 位置:在方法的定义上面
  3. 特点:
    1. 功能最强的通知
    2. 可以在目标方法的前/后执行。
      • **pjp.proceed();** 方法上面的代码就是前,下面就是后
    3. 控制目标方法是否被调用执行
      • 通过 调不调用**pjp.proceed();**方法来控制目标方法是否被调用
      • 也可以 通过条件判断 如果xxx就执行 如果xxx不执行
      • ProceedingJoinPoint是接口 继承JoinPoint所以可以通过 他的getArgs()获取到参数然后进行判断
    4. 修改原来的目标方法的执行结果,影响最后的调用结果
      1. 最后的返回结果是环绕通知的return 决定的
  4. 跟JDK的动态代理一样

参数ProceedingJoinPoint :

  • 用来执行目标方法的 //O``bject obj = ``pjp.proceed(); 执行目标方法 接收返回值

返回值:

  • 就是目标方法的执行结果,可以被修改
  • [在测试类中写的 调用 doOther() 其实内部调用的是 myAspect()方法,这就是动态代理]
  • 调用目标方法 == 调用myAround()

**pjp.proceed()**上面的代码 是方法执行之前执行,下面是执行之后执行
未命名图片.png

5.2 @AfterThrowing 异常通知(了解即可)

在程序抛出异常是被触发调用
异常通知方法定义:

  1. public
  2. 没有返回值
  3. 方法名字自定义
  4. 方法可以有参数,如果有就是JoinPoint/ Exception
    1. public void myAfterThrowing(Exception e){
    2. }

    @AfterThrowing

    属性:
  • value 切入点表达式
  • throwing 自定义的变量,表示目标方法抛出的异常对象
  • throwing=“xx” xx必须和 Exception xx 一致

特点:

  • 在目标方法抛出异常时执行的
  • 可以做异常的监控程序,监控目标方法执行时是不是有异常,如果有异常可以发送邮件或者短信

执行原理就是:在底层其实是有一个try{}catch(){}的 如果遇到了异常就进入到异常 然后调用你的异常通知方法

5.3 @After最终通知

最终通知方法定义

  1. public
  2. 没有返回值
  3. 名称自定义
  4. 没参数,如果有JoinPoint

属性:value 切入点表达式
使用位置:方法的上面
特点:

  1. 总是会执行(就算抛出了异常也是会执行的) 类似于finally
  2. 在目标方法之后执行

作用:一般是用来做内存清除的

5.3 @Pointcut注解

  • 当有很多的切入点表达式的时候,就比较难管理,这时就可以使用过@Pointcut注解
  • 使用场景:用很多通知 都需要使用同一个切入点表达式。
  • 属性: value 切入点表达式
  • 位置:在自定义方法上面
  • 特点:
    • 当使用了Pointcut定义在一个方法的上面,此时这个方法的名称就是切入点表达式的别名。
    • 其他的通知中,value的属性就可以使用这个方法的名称来代替切入点表达式了

未命名图片.png

5.4 代理的使用方法

  1. 如果目标类有接口,框架使用jdk动态代理
  2. 如果目标类没有接口,默认使用的cglib动态代理
  3. 有接口也可以强制使用cglib动态代理 ```xml cglib cglib 2.2.2

```