代理模式的理解

    首先代理二字的含义,程序中代理与字面意思的代理并无区别。比如现实生活中办理车辆审车,我们经常会听说花钱找代理(又称黄牛)办手续,即办手续这个事,不是我们亲自执行,而是通过代理(即黄牛)去车管所办理。再比如联系明星商业出演,那明星一般也不会直接和商家对线,而是通过明星的经纪人协商时间地点出场费等。
    换到程序中代理二字也是上述含义:即调用一个对象时,不是直接与此对象示例交互,而是通过对象的代理来完成交互。这就是代理最基本的含义。
    那增加这层代理的好处也可类比现实生活进行理解:
  • 直观的,可以避免调用方和被调用的对象产生直接的联系。也就是程序设计中耦合度问题。
  • 其次,通过代理可以实现目标对象本身所不具备的行为功能。比如明星只负责演出,而经纪人则负责安排档期、经费、缴税等工作。
基于上述的现实案例做铺垫,我们梳理并识别代理模式涉及的几个组成部分:
  • 目标对象:车主、明星
  • 代理人:黄牛、经纪人
  • 行为:办审核手续、唱歌跳舞
  • 提出方、调用方:警察、商人

说回到程序中,代理主要有以下两种形式:

代理的形式之一:静态代理

这里采用明星商演的场景来演示:
public interface IStar {
void SingAsong(String songName);
}
public class Star implements IStar {
private String _name;
public Star(String name) {
this._name = name;
}
@Override
public void SingAsong(String songName) {
System.out.println("i am " + _name + ",I'm singing....");
}
}
public class StaticStarProxy implements IStar {
Star zhangsanStar = new Star("zhangsan");
@Override
public void SingAsong(String songName) {
this.PlanSingDate();
zhangsanStar.SingAsong(songName);
this.PayingTax();
}
private String PlanSingDate() {
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
calendar.add(Calendar.MONTH, 2);
Date singDate = calendar.getTime();
System.out.println("sing date =" + singDate);
return singDate.toString();
}
private void PayingTax() {
System.out.println("payint tex....");
}
}

public class Main {
public static void main(String[] args) { System.out.println("starting...."); //直接调用,直接与明星对线
IStar zhangsanStar = new Star("zhangsan");
zhangsanStar.SingAsong("molihua");
System.out.println();
System.out.println("stoping");
}
上述示例代码中,Star就是明星本人,StarProxy就是代理人,IStar就是行为,Main就是调用方。
程序中静态一般是代表稳定不变的、相对固定的描述。静态代理也就是说代理是相对不变化的,代理程序(StarProxy)怎么写的一般就不会变化。
很显然,明星不止一个,并且明星的行为也不仅仅是唱歌。采用静态代理就会产生几个问题:
  • 如果采用多个明星对应多个代理类的话,那么就会产生大量的代理类出现。【虽然在现实生活中是合理的,但是程序中一般追求精简】
  • 如果采用多个明星对应一个代理类,那么这个代理类的实现就会很复杂,且代理类变更频繁。
  • 还有一个问题,如果明星增加技能(比如跳舞),那么代理类就需要同步的变更。
于是,程序中提出了动态代理来解决这些问题。

代理的形式之二:动态代理

所谓动态代理,直观理解就是代理是动态生成的,而不需要提前编写好。

public class DnyamicStarProxy implements InvocationHandler {
IStar _targetStar; public DnyamicStarProxy(IStar star) {
this._targetStar = star;
} @Override
public Object invoke(Object proxyInstance, Method method, Object[] objects) throws Throwable {
this.PlanSingDate();
Object invokeResult = method.invoke(_targetStar, objects);
this.PayingTax();
return invokeResult;
} private String PlanSingDate() {
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
calendar.add(Calendar.MONTH, 2);
Date singDate = calendar.getTime();
System.out.println("sign date =" + singDate);
return singDate.toString();
} private void PayingTax() {
System.out.println("the agent is paying tex....");
}
}

public class Main {
public static void main(String[] args) { System.out.println("starting...."); //直接调用,直接与明星对线
IStar zhangsanStar = new Star("zhangsan");
zhangsanStar.SingAsong("molihua");
System.out.println(); //代理模式,通过经纪人对线
IStar zhangsanProxy = (IStar) new StaticStarProxy();
zhangsanProxy.SingAsong("molihua");
System.out.println(); //代理模式,动态代理调用
System.out.println("邀请李四演出:");
IStar lisiStar = new Star("lisi");
IStar lisiStarProxy = (IStar) Proxy.newProxyInstance(IStar.class.getClassLoader()
, new Class[]{IStar.class}
, new DnyamicStarProxy(lisiStar));
lisiStarProxy.SingAsong("molihua");
System.out.println(); System.out.println("邀请王五演出:");
IStar wangwuStar = new Star("wangwu");
IStar wangwuStarProxy = (IStar) Proxy.newProxyInstance(IStar.class.getClassLoader()
, new Class[]{IStar.class}
, new DnyamicStarProxy(wangwuStar));
wangwuStarProxy.SingAsong("molihua");
System.out.println(); System.out.println("stoping");
}
输出结果:
starting....
i am zhangsan,I'm singing.... sing date =Wed Mar 16 18:14:17 CST 2022
i am zhangsan,I'm singing....
payint tex.... 邀请李四演出:
sign date =Wed Mar 16 18:14:17 CST 2022
i am lisi,I'm singing....
the agent is paying tex.... 邀请王五演出:
sign date =Wed Mar 16 18:14:17 CST 2022
i am wangwu,I'm singing....
the agent is paying tex.... stoping 进程已结束,退出代码为 0
 
首先对代码中陌生的类和方法做一个说明:
InvocationHandler类的Object invoke(Object o, Method method, Object[] objects)
  • 入参o代表:动态生成的代理类对象
  • 入参method代表;调用方调用的哪个方法,演示代码中即代表SingAsong方法
  • 入参objects代表:调用方法传入的参数,即实参
 
Proxy类的static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
  • 参数loader:代表java反射中的类加载器
  • 参数interfaces:代表依赖的接口
  • 参数h:代表动态代理类的的对象,即DnyamicStarProxy
 
站在调用方角度来说,可以这么理解:调用SingAsong时不是直接new一个Star实例就调用了,而是通过Proxy.newProxyInstance动态的创建一个代理类的对象,然后通过此代理类的对象来调用。
 
通过演示代码,可以看出以下特征:
  • 只有一个DnyamicStarProxy类,而省掉了具体的各个代理人的类;
  • 对商人(调用方)来说,只需要提前知道想要邀请的明星(李四、王五),然后通过特定的代码就生成了明星经纪人对象(lisiStarProxy、wangwuStarProxy)
  • 得到经纪人对象后,就可以通过经纪人对象调用执行具体的行为(唱歌)。
同时,我们梳理分析看看动态代理方式的各个组成部分的依赖情况:
  • 目标对象:Star,没有额外的变化
  • 代理人:省掉了具体的xxx代理人,转为DnyamicStarProxy类,从而依赖InvocationHandler类。
  • 行为:IStar,没有额外的变化
  • 调用方:依赖IStar接口、特定的Star类对象、Proxy类、DnyamicStarProxy类
 
现在,我们考虑一个问题:演示代码中是如何调用了Star类的Sing方法?
根据程序执行输出,可以判断是在DnyamicStarProxy.Invoke中调用的。于是可以看到反射技术的应用,即method.invoke()处利用反射真正执行了Star的SingAsong()方法。
 

动态代理的一些常见思考题:

Java常用的动态代理技术有哪些?

本次演示程序虽然用到反射技术来实现动态代理,但也有其它技术来实现比如CGLIB等

演示代码中,调用方还是new了具体的明星类(lisiStar、wangwutar),这就产生了严重的依赖。有什么办法可以解除这个依赖?

DnyamicStarProxy.Invoke中通过反射执行了目标对象的方法(Star.SingAsong),此时执行的代码是本地的代码。如果这里通过socket将方法名、入参发送到另一台server上执行,然后把结果在再通过网络返回,这就是RPC框架中常用的动态代理了。

虽然是动态代理,但根据前面的铺垫,按说也是会生成代理类的吧,那动态生成的代理长什么样?

    确实是运行期间动态生成了代理类,如下所示:
注意动态代理类的SingAsong方法;
查看代码


//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
// import Stars.IStar;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException; public final class DynamicGeneratedStarProxyClass extends Proxy implements IStar {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0; public DynamicGeneratedStarProxyClass(InvocationHandler var1) throws {
super(var1);
} public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
} public final void SingAsong(String var1) throws {
try {
super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
} public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
} public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
} static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("Stars.IStar").getMethod("SingAsong", Class.forName("java.lang.String"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
 

动态代理及java演示的更多相关文章

  1. 动态代理:JDK原生动态代理(Java Proxy)和CGLIB动态代理原理+附静态态代理

    本文只是对原文的梳理总结,以及自行理解.自己总结的比较简单,而且不深入,不如直接看原文.不过自己梳理一遍更有助于理解. 详细可参考原文:http://www.cnblogs.com/Carpenter ...

  2. 实现动态代理(Java和spring)

    一.Java实现动态代理 1.创建接口 package com.oyy.mw.biz.i; public interface Cal { public int add(int num1,int num ...

  3. 设计模式之动态代理(Java的JDK动态代理实现)

    先来看一下思维导图: 对于JDK的动态代理,孔浩老师说学习的方法是把它记下来. 先写一个主题接口类,表示要完成的一个主题. package com.liwei.dynaproxy; /** * 要代理 ...

  4. Java JDK 动态代理使用及实现原理分析

    转载:http://blog.csdn.net/jiankunking   一.什么是代理? 代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问.代理类负责为委托类预处理 ...

  5. Java动态代理深入解析

    要想了解Java动态代理,首先要了解什么叫做代理,熟悉设计模式的朋友一定知道在Gof总结的23种设计模式中,有一种叫做代理(Proxy)的对象结构型模式,动态代理中的代理,指的就是这种设计模式. 在我 ...

  6. Java编程的逻辑 (86) - 动态代理

    ​本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...

  7. java动态代理_aop2

      一.什么是代理? 代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问.代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理. 代理模 ...

  8. Java基础加强-(注解,动态代理,类加载器,servlet3.0新特性)

    1.   Annotation注解 1.1.  Annotation概述 Annotation是JDK 5.0以后提供对元数据的支持,可以在编译.加载和运行时被读取,并执行相应的处理.所谓Annota ...

  9. Java动态代理——框架中的应用场景和基本原理

    前言 之前已经用了5篇文章完整解释了java动态代理的原理,本文将会为这个系列补上最后一块拼图,展示java动态代理的使用方式和应用场景 主要分为以下4个部分 1.为什么要使用java动态代理 2.如 ...

随机推荐

  1. [BUUCTF]PWN——others_shellcode

    others_shellcode 附件 解题步骤: 例行检查,32位程序,开启了NX(堆栈不可执行)和PIE(地址随机化)双重保护 试运行了一下,发现直接就能执行shell的命令 远程连接运行一下,直 ...

  2. CF1490D Permutation Transformation 题解

    Content 给定一个排列 \(a\),按照以下方法构造一棵树: 选择当前排列中的最大数作为根的编号. 最大数左边的所有数按照上述方法建左子树,若没有数则该节点没有左儿子. 最大数右边的所有数按照上 ...

  3. Linux 磁盘分区和挂载

    目录 Linux 磁盘分区和挂载 windows 下的分区 磁盘管理 相关命令 分区及挂载实现步骤 添加硬盘 分区步骤 步骤 挂载步骤 卸载分区步骤 补充: Linux 磁盘分区和挂载 windows ...

  4. xcode导出手机的崩溃日志

    1.第一步将手机连接xcode 点击window 2.选择 Device and Simulators 3.选择View Device Logs 4.导出文件 右键鼠标选择Export Logs 5. ...

  5. 惊!世界上竟然有O(N)时间复杂度的排序算法!计数排序!

    啥?你以为排序算法的时间复杂度最快也只能O(N*log(N))了? O(N)时间复杂度的排序算法听说过没有?计数排序!!它是世界上最快最简单的算法!!! 计数排序算法操作起来只有三步,看完秒懂! 根据 ...

  6. 【LeetCode】101. Symmetric Tree 对称二叉树(Java & Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 DFS BFS 日期 [LeetCode] 题目地址 ...

  7. 【LeetCode】374. Guess Number Higher or Lower 解题报告(Java & Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 日期 题目地址:https://leetcode.c ...

  8. 【LeetCode】858. Mirror Reflection 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 日期 题目地址:https://leetcode.c ...

  9. 【LeetCode】841. Keys and Rooms 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 DFS BFS 日期 题目地址:https://le ...

  10. golang 数组的一些自问自答

    所有代码基于Go-1.17.一些研究Go数组的自问自答,可以考虑作为面试题. 问题:静态存储区是什么?和堆/栈有什么区别? 回答: 可以参考下列图 堆上存放new产生的大块内存 栈上存放的是程序运行的 ...