定义

代理模式又叫委托模式,是为某个对象提供一个代理对象,并且由代理对象控制对原对象的访问。代理模式通俗来讲就是我们生活中常见的中介。
代理模式可以提供非常好的访问控制,应用比较广泛。
举个例子来说明:假如说我现在想买一辆二手车,虽然我可以自己去找车源,做质量检测等一系列的车辆过户流程,但是这确实太浪费我得时间和精力了。我只是想买一辆车而已为什么我还要额外做这么多事呢?于是我就通过中介公司来买车,他们来给我找车源,帮我办理车辆过户流程,我只是负责选择自己喜欢的车,然后付钱就可以了。

角色

抽象主题角色:可以是抽象类,也可以是接口。抽象主题是一个普通的业务类型,无特殊要求。

具体主题角色:也叫做被委托角色或被代理角色,是业务逻辑的具体执行者。

代理主题角色:也叫做委托类或代理类。它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并且在具体主题角色处理完毕前后做预处理和善后处理工作。

静态代理

静态代理中,我们对目标对象的每个方法的增强都是手动完成的(后面会具体演示代码),非常不灵活(比如接口一旦新增加方法,目标对象和代理对象都要进行修改)且麻烦(需要对每个目标类都单独写一个代理类)。 实际应用场景非常非常少,日常开发几乎看不到使用静态代理的场景。

静态代理实现步骤:

  1. 定义一个接口及其实现类;
  2. 创建一个代理类同样实现这个接口
  3. 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。

下面我写一个结婚的案例方便理解静态代理

定义一个结婚的接口

1
2
3
4
5
6
7
8
9
/**
* @Author: ZVerify
* @Description: TODO
* @DateTime: 2022/8/30 22:49
**/
public interface MarryService {

void marry();
}

具体对象去实现接口

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* @Author: ZVerify
* @Description: TODO
* @DateTime: 2022/8/30 22:50
**/
public class Zverify implements MarryService{


@Override
public void marry() {
System.out.println("抽空结个婚");
}
}

代理类同样去实现接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* @Author: ZVerify
* @Description: TODO
* @DateTime: 2022/8/30 22:53
**/
public class ZverifyMarryProxy implements MarryService{

private MarryService marryService;

public ZverifyMarryProxy(MarryService marryService) {
this.marryService = marryService;
}

@Override
public void marry() {

System.out.println("我是婚庆主持我来主持");

marryService.marry();

System.out.println("我是打扫卫生的,婚礼结束我来打扫");
}
}

测试

image-20220830225814257

动态代理

相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,我们可以直接代理实现类( CGLIB 动态代理机制)。

从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。

说到动态代理,Spring AOP、RPC 框架应该是两个不得不提的,它们的实现都依赖了动态代理。

动态代理在我们日常开发中使用的相对较少,但是在框架中的几乎是必用的一门技术。学会了动态代理之后,对于我们理解和学习各种框架的原理也非常有帮助。

就 Java 来说,动态代理的实现方式有很多种,比如 JDK 动态代理、CGLIB 动态代理等等。

JDK动态代理

在Java动态代理的机制中InvocationHandler接口和Proxy类是核心
Proxy 类中使用频率最高的方法是:newProxyInstance() ,这个方法主要用来生成一个代理对象。
这个方法一共有三个参数:

  1. loader:类加载器用于加载代理对象
  2. interfaces:被代理类实现的一些接口
  3. h:实现了InvocationHandler接口的invoke方法来调用

所以我们第三个参数需要一个自定义的类去实现InvocationHandler接口并实现invoke方法这里因为这个接口就一个方法我们直接使用lambda表达式写一下就不去创建类了

invoke() 方法有下面三个参数:

proxy :动态生成的代理类
method : 与代理类对象调用的方法相对应
args : 当前 method 方法的参数
也就是说:你通过Proxy 类的 newProxyInstance() 创建的代理对象在调用方法的时候,实际会调用到实现InvocationHandler 接口的类的 invoke()方法。 你可以在 invoke() 方法中自定义处理逻辑,比如在方法执行前后做什么事情。

JDK 动态代理类使用步骤
定义一个接口及其实现类;
自定义 InvocationHandler 并重写invoke方法,在 invoke 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;
通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象;

抽象接口

1
2
3
4
public interface MarryService {

void marry();
}

具体被代理类

1
2
3
4
5
6
7
8
9
10
public class Zverify implements MarryService{


@Override
public void marry() {

System.out.println("抽空结个婚");
}

}

产生代理对象的工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ProxyFactory {

public static MarryService getMarryProxy(MarryService marryService){

return (MarryService) Proxy.newProxyInstance(marryService.getClass().getClassLoader(), marryService.getClass().getInterfaces(),
(proxy, method, args)->{

System.out.println("我帮你主持婚礼");
Object invoke = method.invoke(marryService, args);
System.out.println("我帮你打扫卫生");
return invoke;
});
}
}

测试

1
2
3
4
5
6
7
8
9
public class ProxyTest {

public static void main(String[] args) {

MarryService marryProxy = ProxyFactory.getMarryProxy(new Zverify());

marryProxy.marry();
}
}

执行结果如下
image-20220831092221066

CGLIB动态代理

介绍
JDK动态代理有一个最致命的问题就是他只能代理实现了接口的类
为了解决这个问题,我们可以使用CGLIB动态代理机制来避免

CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。很多知名的开源框架都使用到了CGLIB, 例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。

在 CGLIB 动态代理机制中 MethodInterceptor 接口和 Enhancer 类是核心。

你需要自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法。

1
2
3
4
5
6
7
public interface MethodInterceptor
extends Callback{
// 拦截被代理类中的方法,参数基本和之前的一样见名知意
public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,MethodProxy proxy) throws Throwable;
}


你可以通过 Enhancer类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 MethodInterceptor 中的 intercept 方法

CGLIB 动态代理类使用步骤

  1. 定义一个类;
  2. 自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;
  3. 通过 Enhancer 类的 create()创建代理类;

不同于 JDK 动态代理不需要额外的依赖。CGLIB(Code Generation Library) 实际是属于一个开源项目,如果你要使用它的话,需要手动添加相关依赖。

1
2
3
4
5
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>

创建一个能够结婚的类

1
2
3
4
5
6
7
8
9
10
11
12
/**
* @Author: ZVerify
* @Description: TODO
* @DateTime: 2022/8/31 10:20
**/
public class MarryService {

public void marry(){

System.out.println("抽空结个婚");
}
}

这次我们就不用lambda了,分开写一下,自定义一个拦截器实现MethodInterceptor接口重写intercept

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* @Author: ZVerify
* @Description: TODO
* @DateTime: 2022/8/31 10:25
**/
public class MarryInterceptor implements MethodInterceptor {

/**
* @param o 代理对象(增强的对象)
* @param method 被拦截的方法(需要增强的方法)
* @param objects 方法入参
* @param methodProxy 用于调用原始方法
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

System.out.println("我帮助你主持婚礼");
Object invokeSuper = methodProxy.invokeSuper(o, objects);

System.out.println("我帮你打扫卫生");

return invokeSuper;
}
}

获取代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* @Author: ZVerify
* @Description: TODO
* @DateTime: 2022/8/31 10:34
**/
public class CglibProxyFactory {

public static <A> A getProxy(Class<A> clazz){

Enhancer enhancer = new Enhancer();

enhancer.setClassLoader(clazz.getClassLoader());

enhancer.setSuperclass(clazz);

enhancer.setCallback(new MarryInterceptor());

return (A) enhancer.create();

}
}

测试
image-20220831105319785

JDK 动态代理和 CGLIB 动态代理对比

JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。

静态代理和动态代理的对比

灵活性 :动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的!
JVM 层面 :静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。