## 额外功能
### MethodBeforeAdvice
`MethodBeforeAdvice` 接口:定义额外功能,运行在原始方法执行之前,额外功能操作都写在该接口的 `before()` 方法中,例如
```java
public class Before implements MethodBeforeAdvice {
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("----- log:method before advice -----");
}
}
```
`before()` 方法的三个参数
**`Method`**:额外功能所增加给的那个**原始方法**,例如 `UserDAOImpl` 的 `save()` 方法
**`Object[]`**:**原始方法的参数**
**`Object`**:额外功能所增加给的那个**原始类对象**,例如 `UserDAOImpl` 的对象
在实战中,三个参数根据需要进行使用,不一定都会用到
### MethodInterceptor
`MethodIntercepter` 接口:定义额外功能,可以根据需要运行在原始方法执行之前、之后或前+后,额外功能操作都写在该接口的 `invoke()` 方法中,例如
```java
package cool.yzt.dynamic;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class Around implements MethodInterceptor {
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("---- log:原始方法运行之前 -----");
Object ret = methodInvocation.proceed();
System.out.println("---- log:原始方法运行之后 -----");
return ret;
}
}
```
定义切入点
```xml
```
测试
```java
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = ctx.getBean("userService",UserService.class);
userService.save(new User());
```
打印结果
```
---- log:原始方法运行之前 -----
UserServiceImpl.save() run
UserDAOImpl.save() run
---- log:原始方法运行之后 -----
```
可以看到,额外功能成功的运行在原始方法执行之前和之后,在实战中,事务功能是需要在原始方法之前和之后添加的
**MethodInvocation**
`invoke()` 的参数,`MethodInvocation` 可以看做是额外功能所加给的那个原始方法的再一次封装,原始方法 `MethodInvocation` 必须手动调用 `proceed()` 才可以执行,其返回值就是原始方法的返回值
**原始方法抛出异常**
```java
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
Object ret = null;
try {
ret = methodInvocation.proceed();
System.out.println("---- log:原始方法运行成功 -----");
} catch (Throwable throwable) {
System.out.println("---- log:原始方法抛出异常 -----");
throwable.printStackTrace();
}
return ret;
}
```
**MethodInterceptor 影响原始方法的返回值**
* 原始方法 `MethonInvocation` 的返回值直接作为 `invoke()` 方法的返回值返回,则 `MenthonInterceptor` 不会影响原始方法的返回值
* 若要影响原始方法的返回值,不直接返回原始方法的返回值而是做一些额外加工即可
## 切入点
切入点决定了额外功能的加入位置,Spring 配置文件中,使用标签 ` ` 定义切入点,其中
1. `id`:切入点的唯一标识
2. `execution`:切入点函数
3. `* * (..)`:切入点表达式,`* * (..)` 匹配了所有方法
### 切入点表达式
#### 1. 方法切入点
首先,看一个方法的定义,可以看做由三个部分组成,A:修饰符合返回值,B:方法名,C:参数列表
```java
public User findById(int id);
// A B C
```
方法切入点表达式也分为三个部分,用于定位一个需要额外功能的方法,例如 `* * (..)`,第一个 `*` 表示任意的方法修饰符与返回值,第二个 `*` 表示任意的方法名, `(..)` 表示任意的参数列表
**实例**
再定义几个 `UserServiceImpl` 和 `UserDAOImpl` 的方法
```java
public void save(User user);
public User findById(int id);
public List findAll();
public void login(String username,String password);
public void login(User user);
```
定义切入点
```xml
```
**注意**
1. `java.lang` 包下的类可以只写类名,例如 `String`,非 `java.lang` 包下的类必须写全类名
2. 精准限定方法:`包名.类名.方法名`
3. 方法参数列表的表达式中,`..` 可以与具体参数类型连用,例如 `login(String,..)`
#### 2. 类切入点
类切入点选择整个类作为额外功能的加入位置,该类下的所有方法都会加上额外功能
* 类中所有方法加入额外功能
```
* cool.yzt.service.UserServiceImpl.* (..)
```
* 简写:忽略包名,类只存在一级包,即 `cool.UserServiceImpl`
```
* *.UserServiceImpl.* (..)
```
* 简写:忽略包名,类存在多级包,即 `cool.yzt.service.UserServiceImpl`
```
* *..UserServiceImpl.* (..)
```
#### 3. 包切入点
包切入点选择整个包作为额外功能的加入位置,该包下所有类的所有方法都会加上额外功能
* 当前包,不包括其子包
```
# service 包下的所有类的所有方法
* cool.yzt.service.*.* (..)
```
* 当前包及其所有子包
```
# cool.yzt包下及其所有子包下的所有类的所有方法
* cool.yzt..*.* (..)
```
### 切入点函数
切入点函数用于执行切入点表达式
#### 1. execution
`execution` 是最终要的切入点函数,功能最全,可以执行方法切入点表达式、类切入点表达式、包切入点表达式,其他的切入点函数都是 `execution` 的简化书写,功能上一致
#### 2. args
`args` 函数主要用于方法参数的匹配,例如,选择参数为 `String,String` 的方法作为切入点,切入点函数和表达式可以有如下写法
```xml
execution(* * (String,String))
args(String,String)
```
#### 3. within
`within` 函数主要用于类、包切入点的匹配,例如,选择 `UserServiceImpl` 类作为切入点,可以有如下写法
```xml
execution(* *..UserServiceImpl.* (..))
within(*..UserServiceImpl)
```
再如,选择 `service` 包作为切入点, 可以有如下写法
```xml
execution(* cool.yzt.service..*.* (..))
within(cool.yzt.service..*.*)
```
#### 4. @annotation
`@annotation` 主要用于匹配具有特殊注解(例如自定义注解)的方法
自定义注解
```java
package cool.yzt.annotaion;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
}
```
在想要加入额外功能的方法上加上自定义的 `Log` 注解,然后配置切入点,切入点表达式语法为 `@annotation(注解的全类名)`
```xml
```
#### 5. 切入点函数的逻辑运算
切入点函数的逻辑运算可以整合多个切入点函数一起配合工作,进而完成更为复杂的需求
1. `and`
需求:方法名为 `login` 且参数为 `String,String` 的方法作为切入点
```xml
execution(* login(String,String))
execution(* login(..)) and args(String,String)
```
注意:`and` 操作不可以用于连接同种类型的切入点函数
2. `or`
需求:方法名为 `login` 或者方法名为 `save` 的方法作为切入点
```xml
execution(* login(..)) or execution(* save(..))
```
## 参考
[孙哥说Spring5](https://www.bilibili.com/video/BV185411477k)

Spring | 动态代理详解