1. AOP
- AOP (Aspect Orient Programming) 面向切面编程是从动态角度考虑程序的运行过程
- AOP底层,就是采用动态代理模式实现的,采用了两种代理,JDK的动态代理和CGLib的动态代理
- 实际上AOP就是动态代理的一种规范化,因为动态代理种类繁多,掌握较难,所以就规范了一套统一的方式,这就是AOP
AOP把动态代理的实现步骤,方式都定义好了,让开发人员用一种统一的方式,去用动态代理。
1.1 动态代理实现方式
JDK:
jdk动态代理,要求jdk中有Proxy,Method,InvocationHandler创建代理对象
- jdk代理要求目标类必须实现接口
CGLib:
- 可以在目标类源代码不改变的情况下去增加功能
- 减少重复代码
- 专注业务逻辑代码
-
1.3 理解AOP面向切面编程
AOP Aspect:切面 给你目标类增加的功能就是切面,就比如要添加的日志 他就属于切面
切面的特点:一般都是非业务方法,可以独立使用
AOP Orient:面向
AOP Programming:编程 需要在分析项目功能时,找出切面。(并不是所有功能都能当切面)
- 合理的安排切面的执行时间(在目标方法前面,还是在目标方法后面)
- 合理的安排切面执行的位置,在哪个类,在哪个方法增加增强功能
1.4 什么时候使用AOP技术
- 当你要给一个项目存在的类修改功能,但是原有的类的功能不完善,并且没有源代码,就可以使用aop增加功能
- 给项目中的多个类,增加一个相同的功能,使用aop
-
1.5 面向切面编程的术语
Aspect:切面,表示增强的功能,就是一堆代码,完成某一个功能,非业务功能,
常见的切面功能:日志,事务,统计信息,参数检查,权限验证
- JoinPoint:连接点 连接业务方法和切面的位置,其实就是类中的业务方法
- Pointcut:切入点 指多个连接点方法的集合
- 目标对象:给哪个类的方法添加功能,这个类就是目标对象
- Advice:通知, 通知表示切面功能执行的时间,(在方法之前还是在方法之后)
1.6 一个切面有三个关键的要素:
- 切面的功能代码,Aspect 切面要干什么
- 切面的执行位置,Pointcut 也就是在那个方法里加功能
-
2. Aspect/切入点表达式
2.1 AOP
2.2 AOP的技术实现框架:
Spring:
spring在内部中实现了AOP规范,能做AOP的工作
spring主要在事务处理时使用AOP
我们项目开发中很少使用spring的AOP实现,因为springAOP比较笨重。
AspectJ:
一个开源的专门做AOP的框架,是业内最专业的AOP框架,又精又厉害
spring中已经集成了AspectJ的框架,所以通过spring就可以直接使用AspectJ的功能了2.3 Aspect实现AOP有两种方式
使用xml配置文件:配置全局事务
- 使用注解,我们在项目中要做AOP功能,一般都是用注解,aspectJ有5个注解【表示切面的执行时间】
2.4 AspectJ框架的使用
切面的执行时间,这个执行时间在规范中,他叫做通知/增强 Advice
在AspectJ中使用注解来表示,当然也可以使用xml文件中的标签
**@Before**
**@AfterReturn**``**ing**
**@Around**
**@AfterThrowing**
**@After**
2.5 切入点表达式
表示切面执行的位置使用的是 切入点表达式
execution:关键字execution(modifiers-pattern? ret-type-pattren
declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)
小括号开始 小括号结束
第一个参数:方法的访问修饰符【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(给一些已经存在的类和方法,增加额外的功能【前提是不改变原来类的代码】)
先创建一个maven功能
- 加入spring依赖
- 加入aspectJ依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
创建目标类:必须有接口和他的实现类(底层目前是JDK动态代理)
给类中的方法增加功能
//目标类
@Component
public class SomeServiceImpl implements SomeService{
@Override
public String doSome(String s) {
//给doSome方法增加功能,在方法执行之前 输出方法的执行时间
System.out.println("SomeServiceImpl的doSome()方法执行了");
System.out.println(s);
return s;
}
}
- 创建切面类:就是一个普通类
- 在类的上面添加注解
**@Aspect**
- 在类中定义方法,方法就是切面要执行的功能代码
- 在方法的上面加入aspectJ中的通知注解,例如
**@Before**
- 指定切入点表达式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()) {
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(“后置通知执行”); } } ```
创建spring的配置文件,在文件中声明对象,把对象交给容器统一管理(IOC管理)
- 声明对象可以使用xml或者注解的方式
- 声明目标对象(使用注解也可以)
- 声明切面类对象(使用注解也可以)
- 声明AspectJ框架中的自动代理生成器标签(自动代理生成器:完成代理对象自动创建功能)
<aop:aspectj-autoproxy/>
<!--声明自动代理生成器,使用aspectJ框架内部的功能,创建目标对象的代理对象
创建代理对象是在内存中实现的,修改目标对象的内存中的结构。创建为代理对象
所以目标对象就是被修改后的代理对象
aop:aspectj-autoproxy:会把spring容器中所有的目标对象一次行都生成代理对象
-->
创建测试类,从spring容器中获取目标对象(实际就是代理对象)
- 通过代理执行方法,实现aop的功能增强
这个返回的就不是哪个类的对象了,而是他的接口的实现类
@org.junit.Test
public void test(){
String path = "application-context.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(path);
//从容器中获取目标对象,获取的目标对象不再是原来的类型 而是它的接口类型
//实际上是 获取的就是代理对象
String applicationName = ac.getApplicationName();
System.out.println(applicationName);
SomeService someServiceImpl = (SomeService) ac.getBean("someService");
//通过代理对象执行方法,完成在调用方法时增强功能
someServiceImpl.doSome("我是你爸爸");
}
4. 前置/后置/环绕通知注解
表示切面的执行时间使用的是通知注解
4.1 前置通知注解 @Before
- 在切面类的切面方法上使用
**@Before**
表示在目标方法执行前执行(表示执行的时间,是在前还是在后) - 属性:
**value**
是切入点表达式,表示切面的功能执行的位置 - 位置:在方法的上面添加注解
@Before
特点:
1.在目标方法之前先执行<br /> 2.不会改变目标方法的执行结果<br /> 3.不会影响目标方法的执行
前置通知方法的定义要求
1.公共方法
2.方法没有返回值
3.方法名称自定义
4.方法可以有参数,也可以没有参数
如果有参数,参数不是自定义的,有几个参数类型可以使用…
4.2 后置通知 @AfterReturning
@AfterReturning
属性:
value
切入点表达式Returning
自定义变量,表示目标方法的返回值
自定义的变量名必须和通知方法的形参名一样
使用位置:在方法定义的上面
特点:
- 在目标方法执行之后执行
- 能够获取到目标方法的返回值,根据返回值做不同的处理功能
Object res = 调用目标类的目标方法返回值
- 可以修改返回值
- 如果是引用传递 那么可以修改值,也就是说传递的是一个对象的话 返回值是可以在通知方法中修改的
因为传递的是内存中的同一个对象,通知方法中修改的也是同一个对象
- 如果是值传递,也就是说传递的是字符串或者是数字的话 那么返回值是不能在通知方法里修改的
后置通知方法
- 公共方法
- 方法没有返回值
- 方法名称自定义
- 方法有参数的 推荐使用Object ```java /保证returning的值和方法的形参名一致就行/ @AfterReturning(value = “execution( (..))”, returning=”res”) public void myAfterReturning(Object res){ 一致 //res就是目标方法的返回值 System.out.println(“后置通知执行”); }
后置通知的执行 res相当于: Object res = doOther(); // 目标方法 myAfterReturning(res); // 在把目标方法的返回值 传递给通知方法
<a name="P6TSE"></a>
### ![未命名图片.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)
<a name="CCZv6"></a>
### @After和@AfterReturning的区别
@After通知是最终通知 也就是说就算抛出了异常 @After通知也是可以执行的,有点向finally<br />但是@AfterReturning不是 一但抛出异常@AfterReturning不执行
<a name="5mGvc"></a>
## 4.3 指定通知方法的形参
> (被通知注解修饰的方法叫通知方法)
**[所有的通知方法都能使用 必须是形参的第一位]**<br />JoinPoint:代表的是业务方法 也就是`@Before("execution(public void ``**doSome**``(..))")`里的doSome方法<br />要加入切面功能的业务方法
作用是:在通知方法中获取方法执行时的信息,例如**方法名称,方法的实际参数**<br />**如果你的切面功能中需要用到方法的信息,就加入JoinPoint**<br />**使用要求**:<br />JionPoint的值是框架自动赋予的,必须是第一个位置的参数<br />
- 获取方法的形参 ** **`** joinPoint.getArgs()**` //返回一个String数组 ,遍历即可
- 获取方法的定义`joinPoint.getSignature() `
<a name="wCef0"></a>
# 5. 环绕/异常/最终通知注解
<a name="nUnck"></a>
## 5.1 `@Around`环绕通知
:::info
经常用来做 事务 在目标方法之前开启事务,目标方法之后结束事务
:::
**环绕通知方法的定义和格式**
1. public方法
1. 必须有返回值,推荐使用Object
1. 方法名称自定义
1. 方法有参数,固定参数 ProceedingJoinPoint
```java
@Around("execution(public void doSome())")
public Object myAspect(ProceedingJoinPoint pjp) throws Throwable {
return null;
}
@Around
- 属性:value 切入点表达式
- 位置:在方法的定义上面
- 特点:
- 功能最强的通知
- 可以在目标方法的前/后执行。
- 在
**pjp.proceed();**
方法上面的代码就是前,下面就是后
- 在
- 控制目标方法是否被调用执行
- 通过 调不调用
**pjp.proceed();**
方法来控制目标方法是否被调用 - 也可以 通过条件判断 如果xxx就执行 如果xxx不执行
ProceedingJoinPoint
是接口 继承JoinPoint所以可以通过 他的getArgs()
获取到参数然后进行判断
- 通过 调不调用
- 修改原来的目标方法的执行结果,影响最后的调用结果
- 最后的返回结果是环绕通知的return 决定的
- 跟JDK的动态代理一样
参数ProceedingJoinPoint :
- 用来执行目标方法的
//O``bject obj = ``pjp.proceed();
执行目标方法 接收返回值
返回值:
- 就是目标方法的执行结果,可以被修改
- [在测试类中写的 调用
doOther()
其实内部调用的是myAspect()
方法,这就是动态代理] - 调用目标方法 == 调用
myAround()
在**pjp.proceed()**
上面的代码 是方法执行之前执行,下面是执行之后执行
5.2 @AfterThrowing
异常通知(了解即可)
在程序抛出异常是被触发调用
异常通知方法定义:
- public
- 没有返回值
- 方法名字自定义
- 方法可以有参数,如果有就是
JoinPoint
/Exception
public void myAfterThrowing(Exception e){
}
属性:@AfterThrowing
- value 切入点表达式
- throwing 自定义的变量,表示目标方法抛出的异常对象
- throwing=“xx” xx必须和 Exception xx 一致
特点:
- 在目标方法抛出异常时执行的
- 可以做异常的监控程序,监控目标方法执行时是不是有异常,如果有异常可以发送邮件或者短信
执行原理就是:在底层其实是有一个try{}catch(){}的 如果遇到了异常就进入到异常 然后调用你的异常通知方法
5.3 @After
最终通知
最终通知方法定义
public
- 没有返回值
- 名称自定义
- 没参数,如果有JoinPoint
属性:value 切入点表达式
使用位置:方法的上面
特点:
- 总是会执行(就算抛出了异常也是会执行的) 类似于finally
- 在目标方法之后执行
作用:一般是用来做内存清除的
5.3 @Pointcut
注解
- 当有很多的切入点表达式的时候,就比较难管理,这时就可以使用过
@Pointcut
注解 - 使用场景:用很多通知 都需要使用同一个切入点表达式。
- 属性: value 切入点表达式
- 位置:在自定义方法上面
- 特点:
- 当使用了Pointcut定义在一个方法的上面,此时这个方法的名称就是切入点表达式的别名。
- 其他的通知中,value的属性就可以使用这个方法的名称来代替切入点表达式了
5.4 代理的使用方法
- 如果目标类有接口,框架使用jdk动态代理
- 如果目标类没有接口,默认使用的cglib动态代理
- 有接口也可以强制使用cglib动态代理
```xml
cglib cglib 2.2.2
```