## 代理模式
### 为什么要有代理模式
在 Web 三层架构开发中,Service 层负责业务逻辑的处理,是最为重要的核心层,Service 层的主要代码由**核心功能**和**额外功能**组成。
* **核心功能**:主要是业务逻辑运算和 DAO 层调用
* **额外功能**:不属于业务,代码量较小,例如事务管理、日志、性能监控等
思考:额外功能写在 Service 层好不好?
从 Service 的调用者(Controller)角度看,Service 层应该具备额外功能,但是从 Service
层内部或者设计者角度看,Service 层不应该书写额外功能的代码,由此代理模式应运而生
### 基本概念
编写一个代理类,通过代理类为原始类增加额外的功能,有利于原始类最核心的功能的维护
* 原始类:也称目标类,是指负责核心业务逻辑运算的类,同时负责调用 DAO 层
* 原始方法:也称目标方法,是原始类中的方法
* 额外功能:即一些附加功能,应该由代理类完成,如事务、日志、性能
代理类的核心要素就是 **原始类** + **额外功能** + **与原始类实现相同的接口**
## 静态代理
静态代理:为每一个需要被代理的原始类都编写一个代理类
### 编码
定义 `UserService` 接口
```java
package cool.yzt.service;
import cool.yzt.entity.User;
public interface UserService {
public void save(User user);
}
```
定义原始类 `UserServiceImpl`,完成核心功能
```java
package cool.yzt.service;
import cool.yzt.dao.UserDAO;
import cool.yzt.dao.UserDAOImpl;
import cool.yzt.entity.User;
public class UserServiceImpl implements UserService{
private UserDAO userDAO = new UserDAOImpl();
// 核心功能
public void save(User user) {
//业务逻辑
System.out.println("UserServiceImpl.save() run");
// 调用 DAO
userDAO.save(user);
}
}
```
定义代理类
```java
package cool.yzt.proxy;
import cool.yzt.entity.User;
import cool.yzt.service.UserService;
import cool.yzt.service.UserServiceImpl;
// 与原始类实现相同的接口
public class UserServiceProxy implements UserService {
// 原始类的对象
UserService userService = new UserServiceImpl();
public void save(User user) {
// 附加额外功能
System.out.println("-----log-----");
userService.save(user);
}
}
```
测试
```java
UserService userService = new UserServiceProxy();
userService.save(new User());
```
打印结果
```
-----log-----
UserServiceImpl.save() run
UserDAOImpl.save() run
```
静态代理虽然可以完成增强原始类的功能,但是存在两个问题
1. 代理类和原始类的对象都是在编译期间确定下来,不利于后续的额外功能扩展,维护性差
2. 每一个代理类只能为一个接口服务,程序开发中必然产生过多的代理
动态代理
## Spring 动态代理
### 基本概念
代理类并不是在编译期就确定下来,而是在运行时,根据需要代理的原始类的类型动态的确定,并创建相应的代理类返回给调用者,为原始类增加额外功能
### 开发过程
#### 创建原始对象
```java
// 原始类
public class UserServiceImpl implements UserService{
private UserDAO userDAO = new UserDAOImpl();
// 核心功能
public void save(User user) {
//业务逻辑
System.out.println("UserServiceImpl.save() run");
// 调用 DAO
userDAO.save(user);
}
public User findById(int id) {
System.out.println("UserServiceImpl.findById() run");
return null;
}
}
```
在 Spring 配置文件中注册
```xml
```
#### 实现 MethodBeforeAdvice 接口
额外功能写在该接口实现类的 before() 方法中,则会在原始类的方法执行之前执行额外功能
```java
package cool.yzt.dynamic;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class Before implements MethodBeforeAdvice {
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("----- log:method before advice -----");
}
}
```
在 Spring 配置文件中注册
```xml
```
#### 定义切入点
切入点:CutPoint,即额外功能加入的位置,由程序员决定额外功能加给哪个方法,首先简单测试将额外功能加给指定原始类的所有方法
在配置文件中加入如下标签,`execution(* * (..))` 就表示额外功能加给所有方法
```xml
```
#### 整合额外功能与切入点
即告知定义额外功能的类 `Before` 作用在那里,在 `` 中加入子标签 ``
```xml
```
#### 获取代理类并调用
```java
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = ctx.getBean("userService",UserService.class);
userService.save(new User());
```
打印结果,可以看到,额外功能已经成功加入到原始方法之前
```
----- log:method before advice -----
UserServiceImpl.save() run
UserDAOImpl.save() run
```
**注意**
虽然传入的是原始类的 `id` 值和原始类的 `class`,但是,通过 Spring 工厂获得的是**代理对象**而不是原始对象
### Spring 动态代理细节分析
Spring 框架在运行时,通过动态字节码技术,在 JVM 内部创建动态代理类,运行在 JVM 内部,程序结束后,和 JVM 一起消失
动态字节码技术就是通过第三方动态字节码框架(ASM,Javassist,Cglib),在 JVM 中创建对应类的字节码,而无需加载字节码文件,进而可以创建对象,JVM 运行结束时,动态字节码跟着消失
也就是说,即使没有定义类文件,动态代理类也可以创建,简化了类文件的管理工作,额外功能的可维护性大大增强,无需为每一个原始类编写代理类
## 参考
[孙哥说Spring5](https://www.bilibili.com/video/BV185411477k)

Spring | 静态代理和动态代理