• java基础-静态代理与动态代理


    一、代理模式的原理

    1.1、代理模式是如何实现的

    代理模式是常用的设计模式之一,它一般分为三个角色:

    1. 抽象角色:指代理角色和真实角色对外提供的公共方法,一般为一个接口
    2. 真实角色:需要实现抽象角色接口,定义了真正所要实现的业务逻辑,以便代理角色调用。
    3. 代理角色:需要实现抽象角色接口,是真实角色的代理,通过真实角色的业务逻辑来实现抽象方法,并可以附加自己的操作,将统一的流程控制都放到代理角色中处理。

    三个角色的关系图如下:

     1.2、使用代理模式的目的

    1. 通过引入代理对象来间接访问目标对象,可对调用者和目标对象进行解耦,保护目标对象。
    2. 可用过代理对象对目标对象的实现进行扩展和增强。

    二、代理模式分类

    2.1、静态代理

    简单的demo:

    这里我们构建一个在京东(代理对象)买实体厂家(目标对象)空调的场景。

    1. //抽象角色(空调厂家)
    2. public interface ACFactory {
    3. void deliverGoods();
    4. }
    5. //真实角色
    6. public class Geli implements ACFactory{
    7. @Override
    8. public void deliverGoods() {
    9. System.out.println("格力空调....发货了");
    10. }
    11. }
    12. //真实角色
    13. public class Haier implements ACFactory{
    14. @Override
    15. public void deliverGoods() {
    16. System.out.println("海尔空调....发货了");
    17. }
    18. }
    19. //代理角色
    20. public class JD implements Factory{
    21. private Factory factory;
    22. public JD(Factory factory) {
    23. this.factory = factory;
    24. }
    25. private void sailBefore(){
    26. System.out.println("售前服务");
    27. }
    28. private void sailAfter(){
    29. System.out.println("售后服务");
    30. }
    31. //对真实角色的行为(方法)进行增强
    32. @Override
    33. public void deliverGoods() {
    34. sailBefore();
    35. factory.deliverGoods();
    36. sailAfter();
    37. }
    38. }
    39. //调用者
    40. public class ShoppingTest {
    41. public static void main(String[] args) {
    42. Factory haier = new Haier();
    43. JD jd = new JD(haier);
    44. jd.deliverGoods();
    45. }
    46. }

    如果此时我感觉格力和海尔的空调太贵了不适合我,我想买个奥克斯的(新需求),此时我们能快速的进行扩展。

    1. public class Aux implements ACFactory{
    2. @Override
    3. public void deliverGoods() {
    4. System.out.println("奥克斯空调....发货了");
    5. }
    6. }
    7. //调用者
    8. public class ShoppingTest {
    9. public static void main(String[] args) {
    10. Factory aux = new Aux();
    11. JD jd = new JD(aux);
    12. jd.deliverGoods();
    13. }
    14. }

    再说一个真实场景帮助大家理解,当我们进行Android开发时会用到网络请求框架,比如最早使用的是Volley框架,之后我想换成okhttp框架,如果之前是强关联(即在业务代码中直接引用Volley),想要做框架替换无疑是痛苦的,如果在设计之初,我们写了一个代理层则可以快速进行框架转换,哪怕将来要求替换为Retrofit也会毫不费力,代理模式可以增强我们代码的扩展性。

    静态代理的缺点:

    1. 静态代理会导致类和接口泛滥,难以管理
    2. 如需对接口进行改动,那么所有实现类都要改动

    正因静态代理的缺点,由此引出动态代理。

    2.2、动态代理

    首先需要说明的是静态代理和动态代理的思想和原理是一模一样的,只是实现代理类的方式不同。

    静态代理:是手动创建代理类并实现接口(.java文件),这个文件是实实在在存储在磁盘上的文件,然后经过编译生成字节码文件(.class文件),最后通过类加载器加载到内存中。

    动态代理:是在运行时动态生成字节流,其内容是和实现了接口的代理类的字节码文件是基本一致的,只不过它直接存储在内存中,帮我们省去了手动实现接口这一过程。

     

    (可类比于Android中xml中的控件和java中直接new的控件的关系)。

    下面先看一下动态代理的使用方式:

    1. public class ShoppingTest {
    2. public static void main(String[] args) {
    3. //静态代理的使用方式
    4. // Factory haier = new Haier();
    5. // JD jd = new JD(haier);
    6. // jd.deliverGoods();
    7. //动态代理的使用方式,与静态代理对应着看有助于理解
    8. //步骤一 创建目标对象
    9. Haier haier = new Haier();
    10. // o即静态代理中的JD代理类
    11. //步骤二,创建代理类对象
    12. Object o = Proxy.newProxyInstance(ShoppingTest.class.getClassLoader(), //类加载器,用于加载生成的字节码(是一个字节数组)
    13. new Class[]{Factory.class}, //要实现的接口
    14. new InvocationHandler() { //回调,会在生成的字节码中实现的接口方法中进行调用
    15. @Override
    16. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    17. Object invoke= null;
    18. if (method.getName().equals("deliverGoods")){
    19. sailBefore();
    20. //调用目标对象(真实角色)的方法
    21. //步骤四 调用目标对象的方法(真正的逻辑代码)
    22. invoke = method.invoke(haier, args);
    23. sailAfter();
    24. }
    25. return invoke;
    26. }
    27. });
    28. //相当于静态代理中jd.deliverGoods()
    29. //步骤三 调用代理类接口方法
    30. Factory factory = (Factory) o;
    31. factory.deliverGoods();
    32. }
    33. private static void sailBefore() {
    34. System.out.println("售前服务");
    35. }
    36. private static void sailAfter() {
    37. System.out.println("售后服务");
    38. }
    39. }

    看完动态代理的使用,我相信大家还是会有很多疑惑的,最大的疑惑肯定是代理类调用实现的接口方法到底是怎么回调到InvocationHandler的invoke()的呢?那接下来就来看下源码,相信所有的疑惑都会烟消云散。

    1. Proxy.java
    2. public static Object newProxyInstance(ClassLoader loader,
    3. Class[] interfaces,
    4. InvocationHandler h)
    5. throws IllegalArgumentException
    6. {
    7. //检查h不为空
    8. Objects.requireNonNull(h);
    9. //拷贝接口类数组
    10. final Class[] intfs = interfaces.clone();
    11. ...
    12. // 标记1:寻找或生成代理类.
    13. Class cl = getProxyClass0(loader, intfs);
    14. ...
    15. try {
    16. ...
    17. //获取代理类的构造方法
    18. final Constructor cons = cl.getConstructor(constructorParams);
    19. final InvocationHandler ih = h;
    20. ...
    21. //通过反射创建代理类对象,并将传入的InvocationHandler对象传递给代理类
    22. return cons.newInstance(new Object[]{h});
    23. } catch (IllegalAccessException|InstantiationException e) {
    24. throw new InternalError(e.toString(), e);
    25. } catch (InvocationTargetException e) {
    26. Throwable t = e.getCause();
    27. if (t instanceof RuntimeException) {
    28. throw (RuntimeException) t;
    29. } else {
    30. throw new InternalError(t.toString(), t);
    31. }
    32. } catch (NoSuchMethodException e) {
    33. throw new InternalError(e.toString(), e);
    34. }
    35. }

    这个方法的源码很好理解,重点是标记一处的代理类是如何生成的,继续跟入最终来到Proxy.ProxyClassFactory的apply()方法:

    1. public Class apply(ClassLoader var1, Class[] var2) {
    2. ...
    3. //上面的逻辑就是做了一些字符上的拼接,这里得到的var22就是代理类的内容了
    4. byte[] var22 = ProxyGenerator.generateProxyClass(var23, var2, var17);
    5. try {
    6. //这里根据上面的字节数组创建一个class对象
    7. return Proxy.defineClass0(var1, var23, var22, 0, var22.length);
    8. } catch (ClassFormatError var14) {
    9. throw new IllegalArgumentException(var14.toString());
    10. }
    11. }
    12. }

    那如果我们将byte[]输出到一个class文件,我们就能清楚生成的代理类到底是什么样子的了:

    1. private static void generateProxy(){
    2. String name = Factory.class.getName()+"$Proxy0";
    3. byte[] bytes = ProxyGenerator.generateProxyClass(name, new Class[]{Factory.class});
    4. try {
    5. FileOutputStream fos = new FileOutputStream(name+".class");
    6. fos.write(bytes);
    7. fos.close();
    8. } catch (Exception e) {
    9. e.printStackTrace();
    10. }
    11. }

    运行此方法,我们就可以拿到生成的动态代理类了,内容如下:

    1. //这里实现了Factory接口
    2. public final class Factory$Proxy0 extends Proxy implements Factory {
    3. private static Method m1;
    4. private static Method m3;
    5. private static Method m2;
    6. private static Method m0;
    7. //这个InvocationHandler就是我们使用动态代理时传入的回调对象
    8. public Factory$Proxy0(InvocationHandler var1) throws {
    9. super(var1);
    10. }
    11. //重写了Object类中的equals()方法。
    12. public final boolean equals(Object var1) throws {
    13. try {
    14. return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
    15. } catch (RuntimeException | Error var3) {
    16. throw var3;
    17. } catch (Throwable var4) {
    18. throw new UndeclaredThrowableException(var4);
    19. }
    20. }
    21. //重写了Factory接口中的方法
    22. public final void deliverGoods() throws {
    23. try {
    24. //super.h就是上面构造函数中的InvocationHandler对象,这里就调用了invoke()方法进行回调
    25. super.h.invoke(this, m3, (Object[])null);
    26. } catch (RuntimeException | Error var2) {
    27. throw var2;
    28. } catch (Throwable var3) {
    29. throw new UndeclaredThrowableException(var3);
    30. }
    31. }
    32. //重写了Object类中的toString()方法。
    33. public final String toString() throws {
    34. try {
    35. return (String)super.h.invoke(this, m2, (Object[])null);
    36. } catch (RuntimeException | Error var2) {
    37. throw var2;
    38. } catch (Throwable var3) {
    39. throw new UndeclaredThrowableException(var3);
    40. }
    41. }
    42. //重写了Object类中的hashCode()方法。
    43. public final int hashCode() throws {
    44. try {
    45. return (Integer)super.h.invoke(this, m0, (Object[])null);
    46. } catch (RuntimeException | Error var2) {
    47. throw var2;
    48. } catch (Throwable var3) {
    49. throw new UndeclaredThrowableException(var3);
    50. }
    51. }
    52. //静态块初始化所有要重写的方法的对象
    53. static {
    54. try {
    55. m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
    56. m3 = Class.forName("com.example.view.test4.Factory").getMethod("deliverGoods");
    57. m2 = Class.forName("java.lang.Object").getMethod("toString");
    58. m0 = Class.forName("java.lang.Object").getMethod("hashCode");
    59. } catch (NoSuchMethodException var2) {
    60. throw new NoSuchMethodError(var2.getMessage());
    61. } catch (ClassNotFoundException var3) {
    62. throw new NoClassDefFoundError(var3.getMessage());
    63. }
    64. }
    65. }

    同理如果我们传入多个接口,生成的代理类(只有一个类)会将他们全部实现,到此动态代理的原理就非常清晰了,很多优秀框架都用到了动态代理如Retrofit。

  • 相关阅读:
    SpringMVC学习笔记(二)
    在PHP8中统计数组元素个数-PHP8知识详解
    【Flink实战】Flink自定义的Source 数据源案例-并行度调整结合WebUI
    # CommonJS模块 和 ECMAScript模块
    【一天一个设计模式】—— 单例模式
    Kafka概论
    中小微企业中的营业额与收入评估的风险模型预测
    从零到一建设数据中台 - 数据可视化
    Excel·VBA数组排列函数
    Elasticsearch8.4.3单机版安装
  • 原文地址:https://blog.csdn.net/qq_27246079/article/details/126346973