## 定义
什么是代理模式?
代理模式,也叫委托模式,其定义是给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。它包含了三个角色:
Subject:抽象主题角色。可以是抽象类也可以是接口,是一个最普通的业务类型定义。
RealSubject:具体主题角色,也就是被代理的对象,是业务逻辑的具体执行者。
Proxy:代理主题角色。负责读具体主题角色的引用,通过真实角色的业务逻辑方法来实现抽象方法,并在前后可以附加自己的操作。
用类图来表示的话大概如下:

用举一个球员的例子,一般来说,球员最主要的工作就是参赛,其他的场外工作可以交给他的经纪人去做,例如谈球队,签代言等等,而负责这些场外工作的经纪人就相当于 Proxy ,而负责核心业务的球员就是 RealSubject 。
这就是代理模式的设计思路。
代理模式分为静态代理和动态代理,静态代理是我们自己创建一个代理类,而动态代理是程序自动帮我们生成一个代理类,可以在程序运行时再生成对象,下面分别对它们做介绍。
## 静态代理
还是用上面球员比赛的例子,在静态代理模式中,我们要先创建一个抽象主题角色 SuperStart:
```java
/**
* 球员业务
*/
interface SuperStart {
// 参赛
void play();
}
```
```java
/**
* 球员,也就是具体的主题角色 Subject
*/
class Player implements SuperStart {
public void play() {
System.out.println("球员:出场~~~");
System.out.println("球员:结束~~~");
}
}
```
接下来就是创建具体的主题角色和代理主题角色,分别实现这个接口;代理类本身并不负责核心业务的执行流程,比赛这事还得 SuperStar 自己来。所以在代理类中需要将真实对象引入,下面是具体的代码实现:
```java
/**
* 代理对象
*/
class Agent implements SuperStart {
/**
* 接收真实的明星对象
*/
private SuperStart star;
/**
* 通过构造方法传进来真实的明星对象
*
* @param star star
*/
public Agent(SuperStart star) {
this.star = star;
}
public void play() {
System.out.println("经纪人:签合同");
star.play();
System.out.println("经纪人:收钱了");
}
}
public class Main {
public static void main(String[] args) {
SuperStart start = new Agent(new Player());
start.play();
}
}
```
静态代理的问题在于一个代理对象能服务的业务对象是写死的,例如上面静态代理,只能为一个球员对象 Player 提供服务,当需要代理其他类型对象(比如演员 Actor)时,要么为 Actor 服务另外实现一个静态代理,要么使用动态代理实现一个超级 Agent。
## 动态代理
在 Java 中,动态代理可以使用 JDK 反射来实现,也可以用框架来实现(例如 CGLib ),以 JDK 反射为例,其核心使用
Proxy.newProxyInstance 这个方法,该方法包含了三个参数:
- **`ClassLoader loader`**:指定当前目标对象使用的类加载器,获取加载器的方法是固定的;
- **`Class<?>[] interfaces`**:指定目标对象实现的接口的类型,使用泛型方式确认类型;
- **`InvocationHandler`**:指定动态处理器,执行目标对象的方法时会触发事件处理器的方法。
InvocationHandler 定义如下
```java
interface InvocationHandler {
/**
* 代理方法调用,代理主题类里面执行的方法最终都是此方法
* @param proxy 要代理的对象
* @param method 要执行的方法接口名称
* @param args 传递的参数
* @return 某一个方法的返回值
* @throws Throwable 方法调用时出现的错误继续向上抛出
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
```
JDK 反射实现的 SuperAgent 如下:
```java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
class SuperAgent {
private Object target;
public Object bind(Object target) {
this.target = target;
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(proxy, method, args) -> {
System.out.println("经纪:签合同");
Object object = method.invoke(this.target, args);
System.out.println("经纪:收钱了");
return object;
}
);
}
}
public class Main {
public static void main(String[] args) {
SuperStart proxy = (SuperStart) new SuperAgent().bind(new Player());
proxy.play();
}
}
```
相对于静态代理,JDK 动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。但它同样有缺陷,就是动态代理的实现类需要类实现接口来完成代理的业务,也就是说它始终无法摆脱仅支持 interface 代理的桎梏,这是设计上的缺陷。而这时 CGLIB 动态代理就派上用场了。
## CGLIB 动态代理
CGLib 采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。下面我们写一个关于CGLib的动态代理类,值得说下的是,CGLib所在的依赖包不是JDK本身就有的,所以我们需要额外引入,如果是用maven来管理的话,就可以直接引入如下的依赖:
```xml
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.3</version>
</dependency>
</dependencies>
```
使用 CGLib 需要实现 MethodInterceptor 接口,并重写 intercept 方法,在该方法中对原始要执行的方法前后做增强处理。该类的代理对象可以使用代码中的字节码增强器来获取。具体的代码如下:
```java
public class CglibProxy implements MethodInterceptor {
/**
* 维护目标对象
*/
private Object target;
public Object getProxyInstance(final Object target) {
this.target = target;
// Enhancer 类是 CGLIB 中的一个字节码增强器,它可以方便的对你想要处理的类进行扩展
Enhancer enhancer = new Enhancer();
// 将被代理的对象设置成父类
enhancer.setSuperclass(this.target.getClass());
// 回调方法,设置拦截器
enhancer.setCallback(this);
// 动态创建一个代理类
return enhancer.create();
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("经纪:签合同");
Object result = methodProxy.invoke(o, objects);
System.out.println("经纪:收钱了");
return result;
}
}
```
场景测试类:
```java
public class Main {
public static void main(String[] args) {
SuperStart player = new Player();
// 创建动态代理对象实例
SuperStart proxy = (SuperStart) new CglibProxy().getProxyInstance(player);
proxy.play();
}
}
```
可以看出,测试类的逻辑和JDK动态代理差不多,其实套路都是一样的,其实技术实现不同。
总结一下 CGLIB 代理模式: CGLIB 创建的动态代理对象比 JDK 创建的动态代理对象的性能更高,但是 CGLIB 创建代理对象时所花费的时间却比 JDK 多得多。所以对于单例的对象,因为无需频繁创建对象,用 CGLIB 合适,反之使用 JDK 方式要更为合适一些。同时由于 CGLib 由于是采用动态创建子类的方法,对于 final 修饰的方法无法进行代理。
## 扩展知识
这里扩展一个知识点,那就是Spring AOP的底层实现,为什么在这里提及呢?因为Spring AOP的底层实现就是基于代理模式,而 JDK 动态代理和 CGLIB 动态代理均是实现 Spring AOP 的基础。我们可以看下 AOP 的部分底层源码:
```java
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
// 判断目标类是否是接口或者目标类是否Proxy类型,若是则使用JDK动态代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// 使用CGLIB的方式创建代理对象
return new ObjenesisCglibAopProxy(config);
}
else {
// 上面条件都不满足就使用JDK的提供的代理方式生成代理对象
return new JdkDynamicAopProxy(config);
}
}
}
```
源码的判断逻辑并不难,主要是根据目标类是否是接口或者Proxy类型来判断使用哪种代理模式创建代理对象,使用的代理模式正是 JDK 动态代理和 CGLIB 动态代理技术。