一、介绍
何谓代理?
代理这个词最早出现在代理商这个行业,所谓代理商,简而言之,其实就是帮助企业或者老板打理生意,自己本身不做生产任何商品。
举个例子,我们去火车站买票的时候,人少老板一个人还忙的过来,但是人一多的话,就会非常拥挤,于是就有了各种代售点,我们可以从代售点买车票,从而加快老板的卖票速度。
代售点的出现,可以说,很直观的帮助老板提升了用户购票体验。
站在软件设计的角度,其实效果也是一样的,采用代理模式的编程,能显著的增强原有的功能和简化方法调用方式。
在介绍动态代理之前,我们先来聊解静态代理。
二、静态代理
下面,我们以两数相加
为例,实现过程如下!
- 接口类
public interface Calculator { /** * 计算两个数之和 * @param num1 * @param num2 * @return */ Integer add(Integer num1, Integer num2); }
- 目标对象
public class CalculatorImpl implements Calculator { @Override public Integer add(Integer num1, Integer num2) { Integer result = num1 + num2; return result; } }
- 代理对象
public class CalculatorProxyImpl implements Calculator { private Calculator calculator; @Override public Integer add(Integer num1, Integer num2) { //方法调用前,可以添加其他功能.... Integer result = calculator.add(num1, num2); //方法调用后,可以添加其他功能.... return result; } public CalculatorProxyImpl(Calculator calculator) { this.calculator = calculator; } }
- 测试类
public class CalculatorProxyClient { public static void main(String[] args) { //目标对象 Calculator target = new CalculatorImpl(); //代理对象 Calculator proxy = new CalculatorProxyImpl(target); Integer result = proxy.add(1,2); System.out.println("相加结果:" + result); } }
- 输出结果
相加结果:3
通过这种代理方式,最大的优点就是:可以在不修改目标对象的前提下,扩展目标对象的功能。
但也有缺点:需要代理对象和目标对象实现一样的接口,因此,当目标对象扩展新的功能时,代理对象也要跟着一起扩展,不易维护!
三、动态代理
动态代理,其实本质也是为了解决上面当目标对象扩展新功能时,代理对象也需要跟着一起扩展的痛点问题而生。
那它是怎么解决的呢?
以 JDK 为例,当需要给某个目标对象添加代理处理的时候,JDK 会在内存中动态的构建代理对象,从而实现对目标对象的代理功能。
下面,我们还是以两数相加
为例,介绍具体的玩法!
3.1、JDK 中生成代理对象的玩法
- 创建接口
public interface JdkCalculator { /** * 计算两个数之和 * @param num1 * @param num2 * @return */ Integer add(Integer num1, Integer num2); }
- 目标对象
public class JdkCalculatorImpl implements JdkCalculator { @Override public Integer add(Integer num1, Integer num2) { Integer result = num1 + num2; return result; } }
- 动态代理对象
public class JdkProxyFactory { /** * 维护一个目标对象 */ private Object target; public JdkProxyFactory(Object target) { this.target = target; } public Object getProxyInstance(){ Object proxyClassObj = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler(){ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("方法调用前,可以添加其他功能...."); // 执行目标对象方法 Object returnValue = method.invoke(target, args); System.out.println("方法调用后,可以添加其他功能...."); return returnValue; } }); return proxyClassObj; } }
- 测试类
public class TestJdkProxy { public static void main(String[] args) { //目标对象 JdkCalculator target = new JdkCalculatorImpl(); System.out.println(target.getClass()); //代理对象 JdkCalculator proxyClassObj = (JdkCalculator) new JdkProxyFactory(target).getProxyInstance(); System.out.println(proxyClassObj.getClass()); //执行代理方法 Integer result = proxyClassObj.add(1,2); System.out.println("相加结果:" + result); } }
- 输出结果
class com.example.java.proxy.jdk1.JdkCalculatorImpl class com.sun.proxy.$Proxy0 方法调用前,可以添加其他功能.... 方法调用后,可以添加其他功能.... 相加结果:3
采用 JDK 技术动态创建interface
实例的步骤如下:
1. 首先定义一个 InvocationHandler 实例,它负责实现接口的方法调用 2. 通过 Proxy.newProxyInstance() 创建 interface 实例,它需要 3 个参数: (1)使用的 ClassLoader,通常就是接口类的 ClassLoader (2)需要实现的接口数组,至少需要传入一个接口进去; (3)用来处理接口方法调用的 InvocationHandler 实例。 3. 将返回的 Object 强制转型为接口
动态代理实际上是 JVM 在运行期动态创建class
字节码并加载的过程,它并没有什么黑魔法技术,把上面的动态代理改写为静态实现类大概长这样:
public class JdkCalculatorDynamicProxy implements JdkCalculator { private InvocationHandler handler; public JdkCalculatorDynamicProxy(InvocationHandler handler) { this.handler = handler; } public void add(Integer num1, Integer num2) { handler.invoke( this, JdkCalculator.class.getMethod("add", Integer.class, Integer.class), new Object[] { num1, num2 }); } }
本质就是 JVM 帮我们自动编写了一个上述类(不需要源码,可以直接生成字节码)。