简介

代理模式属于行为型模式的一种, 控制对其他对象的访问, 起到中介作用.

代理模式核心角色: 真实角色,代理角色;

按实现方式不同分为静态代理和动态代理两种;

意图

控制对其它对象的访问。

类图

实现

JDK自带了Proxy的实现, 下面我们先使用JDK的API来演示代理如何使用, 随后再探究Proxy的实现原理,并自己来实现Proxy.

JDK代理类的使用: (InvocationHandler,Proxy)

    使用JDK实现的代理代码如下, 先定义业务接口`Car`,然后实现该接口`QQCar`,实现类即为真实角色. 继续定义代理类`Agent`,代理类需要实现接口`InvocationHandler`的`invoke()`方法, 最后是调用;
注意代理类使用了`Proxy.newProxyInstance()`方法动态生成代理对象, 在稍后手写代码时我们将参考本方法定义我们自己的实现方式.
/**
* 汽车接口,定义业务规范
*/
public interface Car {
void sale();
}
/**
* 接口Car的具体实现,是代理模式中的真实角色
*/
public class QQCar implements Car {
public void sale() {
System.out.println("卖了一辆qq");
}
}
/**
* 经纪公司,或者经销商
*/
public class Agent implements InvocationHandler { private Car car ; /**
* 返回代理对象,接收被代理对象
* @param car
* @return
* @throws Exception
*/
public Object getInstance(Car car) throws Exception {
this.car=car;
Class clazz = car.getClass();
// 看下代理前后的具体类型
System.out.println("代理前对象的类型"+car.getClass().getName());
Object obj = Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
// 看下代理前后的具体类型
System.out.println("代理后对象类型变为"+obj.getClass().getName());
return obj;
} public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Agent find some costumers");
//this.car.sale();
method.invoke(this.car, args);
System.out.println("Agent saled the car");
return null;
}
}
try {
Car car = (Car) new Agent().getInstance(new QQCar());
car.sale();
}catch (Exception e){
e.printStackTrace();
}

总结JDK原理如下:

  1. 获取真实角色对象的引用并获取其接口
  2. Proxy生成一个代理类,并实现接口的方法
  3. 获取被代理对象的引用
  4. 动态生成代理类的class字节码
  5. 编译加载

手写实现Proxy

要自己实现JDK的代理模式,我们首先要搞清楚JDK的Proxy是如何实现的, 通过调试及查看源码可以知道JDK生成了一个$Proxy0的类型,我们也将该类型名称打印到了控制台. 如果能动态生成,编译并将这个类加载到内存, 我们就可以自己实现Proxy了.

  1. 首先拿到$Proxy0的代码,供我们后面生成代理类的源码时参考
//生产接口Car对应的代理类class文件并保存到文件
byte[] data = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{Car.class});
FileOutputStream outputStream = new FileOutputStream("E:\\design-patterns\\src\\main\\java\\com\\xlx\\pattern\\proxy\\jdk\\$Proxy0.class");
outputStream.write(data);
outputStream.close();

生成的$Proxy0.class文件反编译后的代码如下, 可以看到其实现了Car接口.

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
// import com.xlx.pattern.proxy.jdk.Car;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException; public final class $Proxy0 extends Proxy implements Car {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0; public $Proxy0(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 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 void sale() throws {
try {
super.h.invoke(this, m3, (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"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.xlx.pattern.proxy.jdk.Car").getMethod("sale");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
  1. 参考JDK的实现,我们分别定义MyClassLoader,MyInvocationHandler,MyProxy,分别对应ClassLoader,InvocationHandler,Proxy;

    其中接口MyInvocationHandler代码如下:
/**
* 代理类需要实现该接口
*/
public interface MyInvocationHandler {
Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
  1. 定义代理类MyAgent对应我们使用JDK时定义的Agent, 但getInstance()方法实现全部改为2中自定义的类,实现2中定义的接口
/**
* 代理类
*/
public class MyAgent implements MyInvocationHandler { private Car car; public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Agent find some costumers");
//this.car.sale();
method.invoke(this.car,args);
System.out.println("Agent saled the car");
return null;
} public Object getInstance(Car car) throws Exception {
this.car=car;
Class clazz = car.getClass();
Object obj = MyProxy.newProxyInstance(new MyClassLoader(),clazz.getInterfaces(),this);
return obj;
}
}
  1. 实现MyProxy中生成代理对象的方法newProxyInstance()

    具体又分为以下几步:

     1. 定义动态代理类的源码
    2. 保存源码文件到磁盘
    3. 编译源码文件为.class文件
    4. 加载.class字节码到内存 (具体实现见5.实现MyClassLoader))
    5. 返回代理对象
/**
* 生成代理对象的代码, Proxy的具体原理在这里体现
*/
public class MyProxy { private static final String ln = "\r\n"; public static Object newProxyInstance(MyClassLoader loader, Class<?>[] interfaces, MyInvocationHandler h) {
File f = null;
try {
// 第一步: 生成源代码
String src = generateSrc(interfaces[0]); // 第二步: 保存生成的源码文件
String filePath = MyProxy.class.getResource("").getPath();
f = new File(filePath + "/$Proxy0.java");
FileWriter writer = new FileWriter(f);
writer.write(src);
writer.flush();
writer.close(); // 第三步: 编译生成.class文件
JavaCompiler compliler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager manager = compliler.getStandardFileManager(null, null, null);
Iterable iterable = manager.getJavaFileObjects(f);
JavaCompiler.CompilationTask task = compliler.getTask(null, manager, null, null, null, iterable);
((JavaCompiler.CompilationTask) task).call();
manager.close(); // 第四步: 加载class字节码到内存(MyClassLoader类实现)
Class proxyClass = loader.findClass("$Proxy0");
// 第五步: 返回代理对象
Constructor constructor = proxyClass.getConstructor(MyInvocationHandler.class);
return constructor.newInstance(h); } catch (Exception e) {
e.printStackTrace();
} finally {
if (null != f) {
f.delete();
}
}
return null;
} /**
* 生成源码的方法
*
* @param interfaces 为了演示,按一个接口处理
* @return
*/
private static String generateSrc(Class<?> interfaces) {
StringBuffer src = new StringBuffer();
src.append("package com.xlx.pattern.proxy.my;" + ln);
src.append("import java.lang.reflect.Method;" + ln);
src.append("public class $Proxy0 extends MyProxy implements " + interfaces.getName() + "{" + ln);
src.append("MyInvocationHandler h;" + ln); src.append("public $Proxy0(MyInvocationHandler h){" + ln);
src.append("this.h=h;" + ln);
src.append("}" + ln); // 循环定义方法,与被代理类的方法同名
for (Method m : interfaces.getMethods()) {
src.append("public " + m.getReturnType().getName() + " " + m.getName() + "(){" + ln); src.append("try{" + ln);
src.append("Method m =" + interfaces.getName() + ".class.getMethod(\"" + m.getName() + "\",new Class[]{});" + ln);
src.append("this.h.invoke(this,m,null);" + ln);
src.append("}catch(Throwable e){e.printStackTrace();}" + ln);
src.append("}" + ln);
} src.append("}" + ln);
return src.toString();
}
}
  1. 实现MyClassLoaderfindClass()方法,最终由父类ClassLoader.defineClass()方法加载,完成最后拼图
/**
* 代码生成,编译,重新加载到内存
* 类加载器, 使用ClassLoader
*/
public class MyClassLoader extends ClassLoader{ File basePath ; public MyClassLoader(){
String basePath = MyClassLoader.class.getResource("").getPath();
this.basePath = new File(basePath) ;
} @Override
public Class<?> findClass(String name) throws ClassNotFoundException{ String className = MyClassLoader.class.getPackage().getName()+"."+name; if (null!=basePath){
File classFile = new File(basePath,name.replaceAll("\\.","/")+".class");
if (classFile.exists()){
FileInputStream in = null;
ByteArrayOutputStream out= null;
try {
in = new FileInputStream(classFile);
out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len=in.read(buffer))!=-1){
out.write(buffer,0,len);
}
return defineClass(className,out.toByteArray(),0,out.size());
}catch (Exception e){
e.printStackTrace();
}finally {
classFile.delete();
if (null!=in){
try{
in.close();
}catch (Exception e){
e.printStackTrace();
}
}
if (null!=out){
try{
out.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
} return null;
}
}

总结

上面我仿照JDK自带API用自己的代码实现了Proxy, 这样写了一次之后对Proxy的实现原理加深了很多.Proxy作为一种重要的模式已经大量用在了目前流行的很多框架上, 理解了原理就更有信心去学习框架以及框架的实现思想了.

优点: 1. 职责清晰,真实角色专注实现业务逻辑,代理角色去完成具体的事务,代码结构简洁清晰;2. 可扩展

补充

上面研究了JDK动态代理的实现, 首先定义了接口,然后用一个类实现这个接口,这个实现类就是要代理的具体对象;

cglib库也实现了Proxy模式,与JDK不同的是, cglib不需要定义接口, 而是通过生成被代理类的子类来实现代理模式.这使得代理模式的使用更加简单. 一般类型均可以作为被代理类型.

大致的实现如下(原理跟jdk实现差不多,只不过cglib使用的是类继承实现):

/**
* 演示 cglib 代理方式
*/
public class CGAgent implements MethodInterceptor { public Object getInstance(Class clazz) throws Exception{
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
} public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("代理开始了....");
methodProxy.invokeSuper(o,objects);
System.out.println("代理结束了....");
return null;
}
}

设计模式三: 代理模式(Proxy) -- JDK的实现方式的更多相关文章

  1. 乐在其中设计模式(C#) - 代理模式(Proxy Pattern)

    原文:乐在其中设计模式(C#) - 代理模式(Proxy Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 代理模式(Proxy Pattern) 作者:webabcd 介绍 为 ...

  2. 大熊君说说JS与设计模式之------代理模式Proxy

    一,总体概要 1,笔者浅谈 当我们浏览网页时,网页中的图片有时不会立即展示出来,这就是通过虚拟代理来替代了真实的图片,而代理存储了真实图片的路径和尺寸,这就是代理方式的一种. 代理模式是比较有用途的一 ...

  3. 二十四种设计模式:代理模式(Proxy Pattern)

    代理模式(Proxy Pattern) 介绍为其他对象提供一个代理以控制对这个对象的访问. 示例有一个Message实体类,某对象对它的操作有Insert()和Get()方法,用一个代理来控制对这个对 ...

  4. 设计模式(三)—代理模式

    目录: 一.概述 二.静态代理 三.动态代理 四.静态代理和动态代理的区别 一.概述      代理模式就是多一个代理类出来,替原对象进行一些操作,比如我们在租房子的时候回去找中介,为什么呢?因为你对 ...

  5. Java基础-设计模式之-代理模式Proxy

    代理模式是对象的结构模式.代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用. 代理模式是常用的Java 设计模式,它的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理 ...

  6. [设计模式] 12 代理模式 proxy

    在GOF的<设计模式:可复用面向对象软件的基础>一书中对代理模式是这样说的:为其他对象提供一种代理以控制对这个对象的访问.结合上面的游戏代理的例子和下面的图,我们来进行分析一下.以前你是这 ...

  7. c#设计模式之代理模式(Proxy Pattern)

    引言 代理这个词语,大家在现实世界已经频繁的接触过,例如火车站代理售票点,因为这些代理售票点的存在,我们不必要去火车站的售票处就可以查询或者取到火车票.代理点本身是没有能力生产车票的,我们在代理处享受 ...

  8. 【设计模式】代理模式-Proxy

    转载:https://www.cnblogs.com/yangchongxing/p/7654725.html 代理模式定义如下: Provide a surrogate or placeholder ...

  9. 设计模式 笔记 代理模式 Proxy

    //---------------------------15/04/21---------------------------- //Proxy 代理模式-----对象结构型模式 /* 1:意图: ...

随机推荐

  1. Luogu5058 [ZJOI2004]嗅探器

    $Luogu5058 [ZJOI2004]嗅探器 给定一张 \(n\) 个点, \(m\) 条边的无向图,和两点 \(s,\ t\) ,求 \(s\to t\) 编号最小的必经点(排除 \(s,\ t ...

  2. 6-STM32物联网开发WIFI(ESP8266)+GPRS(Air202)系统方案升级篇-优化升级(安装Apache (Web服务器)软件,测试HTTP)

    为了和SDK升级保持协议一致,花了两天时间实现了用LUA开发,MQTT+HTTP方式实现远程升级 安装Apache主要是为了实现通过HTTP下载资源 升级介绍: 0,用户点击检查更新时,APP首先通过 ...

  3. MySQL 8.0.x for Windows 解压缩版配置安装

    一.官网下载MySQL8.0.16 直达官网下载Community版:https://dev.mysql.com/downloads/mysql/ 然后拉倒下方点击对应版本位数下载 二.创建my.in ...

  4. Linux scp sudo

    command line - scp to remote server with sudo - Super Userhttps://superuser.com/questions/138893/scp ...

  5. NOIP2015普及组复赛A 推销员

    题目链接:https://ac.nowcoder.com/acm/contest/243/A 题目大意: 略 分析: 方法就是把疲劳值从小到大排个序,然后从尾部开始一个一个取,当选到第i(i > ...

  6. Python中的 一些常用技巧函数[.join()]

    1.str.join(item)字符串操作函数,参数item可以是字符串.元组.字典,示例 ','.join('abc') [','.join('abc')] 输出: 'a,b,c'['a', 'b' ...

  7. Platform.Uno介绍

    编者语:Xamarin国内很多人说缺乏可用的实例,我在写书过程中在完善一些常用场景的例子,希望帮到大家.Build 2018结束一周了,善友问我要不要谈谈Xamarin的一些变化,但碍于时间有限一直没 ...

  8. Javascript初识之流程控制、函数和内置对象

    一.JS流程控制 1. 1.if else var age = 19; if (age > 18){ console.log("成年了"); }else { console. ...

  9. Codeforces1102F Elongated Matrix 【状压DP】

    题目分析: 这题瞎搞一个哈密尔顿路,对于起点不同的分开跑就可以过了. $O(n^3*2^n)$ #include<bits/stdc++.h> using namespace std; ; ...

  10. Word自定义多级列表样式

    Word自定义多级列表样式: 1. 2. 3.取个名字 在这里鼠标移上时显示 : 4. 5. 定义完成,即可使用: