代理模式是一种比较好理解的设计模式。简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。
代理模式的主要作用是扩展目标对象的功能,比如说在目标对象的某个方法执行前后你可以增加一些自定义的操作。
举个例子:你找了小红来帮你问话,小红就可以看作是代理你的代理对象,代理的行为(方法)是问话。
![[图片上传失败...(image-e75331-1650446995215)]](https://1000bd.com/contentImg/2022/08/16/183611523.png)
代理模式有静态代理和动态代理两种实现方式,我们 先来看一下静态代理模式的实现。
静态代理中,我们对目标对象的每个方法的增强都是手动完成的(后面会具体演示代码),非常不灵活(比如接口一旦新增加方法,目标对象和代理对象都要进行修改)且麻烦(需要对每个目标类都单独写一个代理类)。 实际应用场景非常非常少,日常开发几乎看不到使用静态代理的场景。
上面我们是从实现和应用角度来说的静态代理,从 JVM 层面来说, 静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。
下面通过代码展示!
1、定义发送短信的接口
- public interface SmsService {
- String send(String message);
- }
-
2、实现发送短信的接口
- public class SmsServiceImpl implements SmsService {
- public String send(String message) {
- System.out.println("send message:" + message);
- return message;
- }
- }
-
3、创建代理类并同样实现发送短信的接口
- public class SmsProxy implements SmsService {
-
- private final SmsService smsService;
-
- public SmsProxy(SmsService smsService) {
- this.smsService = smsService;
- }
-
- @Override
- public String send(String message) {
- //调用方法之前,我们可以添加自己的操作
- System.out.println("before method send()");
- smsService.send(message);
- //调用方法之后,我们同样可以添加自己的操作
- System.out.println("after method send()");
- return null;
- }
- }
-
4、实际使用
- public class Main {
- public static void main(String[] args) {
- SmsService smsService = new SmsServiceImpl();
- SmsProxy smsProxy = new SmsProxy(smsService);
- smsProxy.send("java");
- }
- }
-
运行上述代码之后,控制台打印出:
before method send()
send message:java
after method send()
可以输出结果看出,我们已经增加了 SmsServiceImpl 的send()方法。
- package com.company.proxy.v07;
-
-
- import java.util.Random;
-
- /**
- * 问题:我想记录坦克的移动时间
- * 最简单的办法:修改代码,记录时间
- * 问题2:如果无法改变方法源码呢?
- * 用继承?
- * v05:使用代理
- * v06:代理有各种类型
- * 问题:如何实现代理的各种组合?继承?Decorator?
- * v07:代理的对象改成Movable类型-越来越像decorator了
- *
- */
- public class Tank implements Movable {
-
- /**
- * 模拟坦克移动了一段儿时间
- */
- @Override
- public void move() {
- System.out.println("Tank moving claclacla...");
- try {
- Thread.sleep(new Random().nextInt(10000));
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
-
- public static void main(String[] args) {
-
- Tank t = new Tank();
- TankTimeProxy ttp = new TankTimeProxy(t);
- TankLogProxy tlp = new TankLogProxy(ttp);
- tlp.move();
- }
- }
-
- class TankTimeProxy implements Movable {
- Movable m;
-
- public TankTimeProxy(Movable m) {
- this.m = m;
- }
-
- @Override
- public void move() {
- long start = System.currentTimeMillis();
- m.move();
- long end = System.currentTimeMillis();
- System.out.println(end - start);
- }
- }
-
- class TankLogProxy implements Movable {
- Movable m;
-
- public TankLogProxy(Movable m) {
- this.m = m;
- }
-
- @Override
- public void move() {
- System.out.println("start moving...");
- m.move();
- long end = System.currentTimeMillis();
- System.out.println("stopped!");
- }
- }
-
- interface Movable {
- void move();
- }
运行结果

相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,我们可以直接代理实现类( CGLIB 动态代理机制)。
从 JVM 角度来说,动态代理是在运行时动态生成类字节码
说到动态代理,Spring AOP、RPC 框架应该是两个不得不提的,它们的实现都依赖了动态代理。
动态代理在我们日常开发中使用的相对较少,但是在框架中的几乎是必用的一门技术。学会了动态代理之后,对于我们理解和学习各种框架的原理也非常有帮助。
就 Java 来说,动态代理的实现方式有很多种,比如 JDK 动态代理、CGLIB 动态代理等等。
guide-rpc-framework
使用的是 JDK 动态代理,我们先来看看 JDK 动态代理的使用。
另外,虽然 guide-rpc-framework
没有用到 CGLIB 动态代理 ,我们这里还是简单介绍一下其使用以及和JDK 动态代理的对比。
在 Java 动态代理机制中 InvocationHandler 接口和 Proxy 类是核心。
Proxy 类中使用频率最高的方法是:newProxyInstance() ,这个方法主要用来生成一个代理对象。
- public static Object newProxyInstance(ClassLoader loader,
- Class>[] interfaces,
- InvocationHandler h)
- throws IllegalArgumentException
- {
- ......
- }
-
这个方法一共有 3 个参数:
InvocationHandler 接口的对象;要实现动态代理的话,还必须需要实现InvocationHandler 来自定义处理逻辑。 当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现InvocationHandler 接口类的 invoke 方法来调用。
- public interface InvocationHandler {
-
- /**
- * 当你使用代理对象调用方法的时候实际会调用到这个方法
- */
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable;
- }
-
invoke() 方法有下面三个参数:
也就是说:你通过Proxy 类的 newProxyInstance() 创建的代理对象在调用方法的时候,实际会调用到实现InvocationHandler 接口的类的 invoke()方法。 你可以在 invoke() 方法中自定义处理逻辑,比如在方法执行前后做什么事情。
这样说可能会有点空洞和难以理解,我上个例子,大家感受一下吧!
1、定义发送短信的接口
- public interface SmsService {
- String send(String message);
- }
-
2、实现发送短信的接口
- public class SmsServiceImpl implements SmsService {
- public String send(String message) {
- System.out.println("send message:" + message);
- return message;
- }
- }
-
3、定义一个 JDK 动态代理类
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.InvocationTargetException;
- import java.lang.reflect.Method;
-
- /**
- * @author shuang.kou
- * @createTime 2020年05月11日 11:23:00
- */
- public class DebugInvocationHandler implements InvocationHandler {
- /**
- * 代理类中的真实对象
- */
- private final Object target;
-
- public DebugInvocationHandler(Object target) {
- this.target = target;
- }
-
- public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
- //调用方法之前,我们可以添加自己的操作
- System.out.println("before method " + method.getName());
- Object result = method.invoke(target, args);
- //调用方法之后,我们同样可以添加自己的操作
- System.out.println("after method " + method.getName());
- return result;
- }
- }
-
invoke() 方法: 当我们的动态代理对象调用原生方法的时候,最终实际上调用到的是 invoke() 方法,然后 invoke() 方法代替我们去调用了被代理对象的原生方法。
4、获取代理对象的工厂类
- public class JdkProxyFactory {
- public static Object getProxy(Object target) {
- return Proxy.newProxyInstance(
- target.getClass().getClassLoader(), // 目标类的类加载
- target.getClass().getInterfaces(), // 代理需要实现的接口,可指定多个
- new DebugInvocationHandler(target) // 代理对象对应的自定义 InvocationHandler
- );
- }
- }
-
getProxy() :主要通过Proxy.newProxyInstance()方法获取某个类的代理对象
5、实际使用
- SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
- smsService.send("java");
-
运行上述代码之后,控制台打印出:
before method send
send message:java
after method send
代码展示
- package com.company.proxy.v08;
-
-
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.lang.reflect.Proxy;
- import java.util.Random;
-
- /**
- * 问题:我想记录坦克的移动时间
- * 最简单的办法:修改代码,记录时间
- * 问题2:如果无法改变方法源码呢?
- * 用继承?
- * v05:使用代理
- * v06:代理有各种类型
- * 问题:如何实现代理的各种组合?继承?Decorator?
- * v07:代理的对象改成Movable类型-越来越像decorator了
- * v08:如果有stop方法需要代理...
- * 如果想让LogProxy可以重用,不仅可以代理Tank,还可以代理任何其他可以代理的类型 Object
- * (毕竟日志记录,时间计算是很多方法都需要的东西),这时该怎么做呢?
- * 分离代理行为与被代理对象
- * 使用jdk的动态代理
- */
- public class Tank implements Movable {
-
- /**
- * 模拟坦克移动了一段儿时间
- */
- @Override
- public void move() {
- System.out.println("Tank moving claclacla...");
- try {
- Thread.sleep(new Random().nextInt(10000));
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
-
- public static void main(String[] args) {
- Tank tank = new Tank();
-
- //reflection 通过二进制字节码分析类的属性和方法
-
- Movable m = (Movable)Proxy.newProxyInstance(Tank.class.getClassLoader(),
- new Class[]{Movable.class}, //tank.class.getInterfaces()
- new LogHander(tank)
- );
-
- m.move();
- }
- }
-
- class LogHander implements InvocationHandler {
-
- Tank tank;
-
- public LogHander(Tank tank) {
- this.tank = tank;
- }
- //getClass.getMethods[]
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- System.out.println("method " + method.getName() + " start..");
- Object o = method.invoke(tank, args);
- System.out.println("method " + method.getName() + " end!");
- return o;
- }
- }
-
-
- interface Movable {
- void move();
- }
运行结果:

JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。
为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免。
CGLIB
(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。很多知名的开源框架都使用到了CGLIB, 例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。
在 CGLIB 动态代理机制中 MethodInterceptor 接口和 Enhancer 类是核心。
你需要自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法。
- public interface MethodInterceptor
- extends Callback{
- // 拦截被代理类中的方法
- public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
- MethodProxy proxy) throws Throwable;
- }
-
你可以通过 Enhancer类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 MethodInterceptor 中的 intercept 方法。
MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;Enhancer 类的 create()创建代理类;不同于 JDK 动态代理不需要额外的依赖。CGLIB
(Code Generation Library) 实际是属于一个开源项目,如果你要使用它的话,需要手动添加相关依赖。
- <dependency>
- <groupId>cglibgroupId>
- <artifactId>cglibartifactId>
- <version>3.3.0version>
- dependency>
-
1、实现一个使用阿里云发送短信的类
- package github.javaguide.dynamicProxy.cglibDynamicProxy;
-
- public class AliSmsService {
- public String send(String message) {
- System.out.println("send message:" + message);
- return message;
- }
- }
-
2、自定义 (方法拦截器)
- import net.sf.cglib.proxy.MethodInterceptor;
- import net.sf.cglib.proxy.MethodProxy;
-
- import java.lang.reflect.Method;
-
- /**
- * 自定义MethodInterceptor
- */
- public class DebugMethodInterceptor implements MethodInterceptor {
-
- /**
- * @param o 代理对象(增强的对象)
- * @param method 被拦截的方法(需要增强的方法)
- * @param args 方法入参
- * @param methodProxy 用于调用原始方法
- */
- @Override
- public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
- //调用方法之前,我们可以添加自己的操作
- System.out.println("before method " + method.getName());
- Object object = methodProxy.invokeSuper(o, args);
- //调用方法之后,我们同样可以添加自己的操作
- System.out.println("after method " + method.getName());
- return object;
- }
-
- }
-
3、获取代理类
- import net.sf.cglib.proxy.Enhancer;
-
- public class CglibProxyFactory {
-
- public static Object getProxy(Class> clazz) {
- // 创建动态代理增强类
- Enhancer enhancer = new Enhancer();
- // 设置类加载器
- enhancer.setClassLoader(clazz.getClassLoader());
- // 设置被代理类
- enhancer.setSuperclass(clazz);
- // 设置方法拦截器
- enhancer.setCallback(new DebugMethodInterceptor());
- // 创建代理类
- return enhancer.create();
- }
- }
-
4、实际使用
- AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
- aliSmsService.send("java");
-
运行上述代码之后,控制台打印出:
before method send
send message:java
after method send
这篇文章中主要介绍了代理模式的两种实现:静态代理以及动态代理。涵盖了静态代理和动态代理实战、静态代理和动态代理的区别、JDK 动态代理和 Cglib 动态代理区别等内容。