代理模式

在深入学习动态代理之前,需要先掌握代理模式。只有深刻理解了代理模式的应用,才能充分理解Java动态代理带来的便利。

在生活中存在许多使用“代理模式”的场景,比如:村里的张三今年已经30岁了,但是还没结婚,可把他老妈给愁坏了,于是就拜托村东头的王媒婆给儿子找个媳妇。

在这里,要娶媳妇的人是张三,但是他不能直接跑到女方家把人家闺女直接带回来,需要中间人王媒婆上门说媒,在这里王媒婆就是一个代理。

另外,我们上大学的时候都知道,学校的机房都是通过一个代理服务器上网的,因为只有一个外网IP,不允许每一台局域网的机器都直连外网。

再者,我们通常为了保护应用程序不受外网攻击,通常将nginx部署在应用前端,作为反向代理服务器。

总之,我们总是会出于某些目的,或者因为某些限制而不得不使用代理模式。

OK,我们通过一个更加常见的例子来说明如何在通过抽象在Java世界使用代理模式。

我们都知道,iPhone已经成为当今非常流行的手机品牌,前些年甚至有人为了买iPhone手机而卖肾,可想其品牌早已深入人心。

最新发售的iPhoneX也是火爆得一塌糊涂,于是小明同学按耐不住激动的心情也想买一台来使用。

但是最先并不在大陆发售,但是小明又身在大陆不便于出国,那可怎么办呢?在电子商务早已风靡的今天,显然这个问题太容易解决了。

于是小明在某电商网站通过海外代购的方式就可以买到心仪的机器了。在这里,小明买手机这个事情就委托给了电商平台的某个商家,小明不用直接去购买,而是通过商家这个“代理”去买手机,最后再通过邮寄的方式把货物交给小明。

那么,如何用程序来实现小明使用海外代购这个方式购买到手机呢?

在此之前,我们需要明确一点,计算机程序的实现必然是对现实生活的一种抽象。所以,我们第一步先抽象出一个叫做“人”的类。

/**
* 定义"人"类
* @desc org.chench.test.java.Person
* @date 2017年11月30日
* @version 1.0.0
*/
public class Person {
/**
* 姓名
*/
private String name = ""; /**
* 年龄
*/
private int age = 0; public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
}
}

我们看到,在“人”这个类中定义基本的属性,如:姓名,年龄。

同时,我们还需要抽象出一个“购买者”接口,声明购买指定物品的方法,如下所示:

/**
* 定义"购买者"接口,声明购买指定物品的方法.
* @desc org.chench.test.java.Buyer
* @date 2017年11月30日
* @version 1.0.0
*/
public interface Buyer {
/**
* 有人想要买iphonex
*/
public void buyIphoneX();
}

显然,做了这么多准备,目的就是要为小明买iPhoneX,所以“人”类还必须实现“购买者”接口,即最终的“人”类应该是这样子的:

/**
* 定义"人"类
* @desc org.chench.test.java.Person
* @date 2017年11月30日
* @version 1.0.0
*/
public class Person implements Buyer {
/**
* 姓名
*/
private String name = ""; /**
* 年龄
*/
private int age = 0; public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} @Override
public void buyIphoneX() {
System.out.println(getName() + " will buy iPhoneX");
}
}

OK,到这里我们应该可以实例化一个叫“小明”的对象去购买iPhoneX了:

/**
* @desc org.chench.test.java.BuySomething
* @date 2017年11月30日
* @version 1.0.0
*/
public class BuySomething {
public static void main(String[] args) {
Person person = new Person();
person.setName("小明");
person.buyIphoneX();
}
}

运行时输出:小明 will buy iPhoneX

但是问题来了,因为iPhoneX还未在大陆上市,同时小明又不能出国,他是没法直接买到iPhoneX了。

于是小明找了一家叫做“代购小王子”的商家,替他去海外买手机,然后再邮寄给小明。

/**
* @desc org.chench.test.java.BuyerProxyPrinceling
* @date 2017年11月30日
* @version 1.0.0
*/
public class BuyerProxyPrinceling implements Buyer {
private String name = "";
private Person realBuyer = null; public BuyerProxyPrinceling(String name, Person realBuyer) {
this.name = name;
this.realBuyer = realBuyer;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} @Override
public void buyIphoneX() {
System.out.println(getName() + " buy iPhoneX");
realBuyer.receiveIphoneX("iPhoneX");
}
}

首先,“代购小王子”要购买iPhoneX,同样需要实现“购买者”接口。

其次,为了在购买到手机之后把机器寄给小明,在“人”类中还需要定义一个接收iPhoneX的方法,否则,代购者购买到手机之后没法给小明啊。

在“人”类中添加如下方法:

public void receiveIphoneX(String iPhoneX) {
System.out.println(getName() + " received " + iPhoneX);
}

准备完毕,海外代购找好了,小明需要找“代购小王子”买iphonex啦__

/**
* @desc org.chench.test.java.BuySomething
* @date 2017年11月30日
* @version 1.0.0
*/
public class BuySomething {
public static void main(String[] args) {
Person person = new Person();
person.setName("小明");
// 小明无法直接购买手机
// person.buyIphoneX();
BuyerProxyPrinceling buyerProxy = new BuyerProxyPrinceling("代购小王子", person);
// 小明通过代理购买手机
buyerProxy.buyIphoneX();
}
}

运行输出:

代购小王子 buy iPhoneX
小明 received iPhoneX

小明终于通过海外代购的方式买到了心仪的手机了,这下该好好出去炫耀了吧~

在这里,我们在软件实现中把小明称为目标对象,代购商家称为代理对象,通过代理对象完成目标对象不能完成的动作。

从这个很简单的例子可以看到,为了通过代理实现某个操作,必须先定义出业务接口,然后目标对象类和代理对象类都必须实现该业务接口。

同时,代理对象必须要持有目标对象的引用,便于代理对象执行某个操作之后与目标对象进行联系。

虽然达到了目的,但是实现起来却很繁琐。具体到编程实现,需要进行太多的硬编码,这对于我们一直追求简单灵活的目标来说实在是太不友好了。

实际上,实现这种代理模式的方式叫做静态代理。

Java自1.3版本就提出了对动态代理的支持,这个动态代理又能带来什么便利呢?相比起静态代理,动态代理有什么优势呢?使用动态代理又能实现什么高级的操作呢?

动态代理

动态代理类概述

关于动态代理类的官方定义是:

A dynamic proxy class is a class that implements a list of interfaces specified at runtime such that a method invocation
through one of the interfaces on an instance of the class will be encoded and dispatched to another object through
a uniform interface.

翻译过来的意思是:

动态代理类是一个实现在运行时指定接口列表的类,使得这些接口实现类的实例中的某个方法调用将被编码并通过统一的接口分派到另一个对象。

这个定义非常的生硬,但是从中可以得出这么几点信息:

(1)动态代理类需要实现一些指定接口,且是在运行时指定的,也就是说,动态代理类不需要在编码时明确实现这些接口。

(2)实现这些接口的类的实例的方法调用将会被编码,并且会通过一个统一的接口分派到另一个对象(其实就是被分派到代理对象)。

那么我们就需要进一步确定:

(1)这个动态代理类需要在运行时指定哪些接口?

(2)这些接口的实现类实例的方法调用将会被谁编码?会被编码为什么?

(3)分派这些接口的实现类实例的方法调用的“统一的接口”是什么接口?分派到的对象是什么?

(4)使用这个动态代理类到底能做什么?

带着这些问题,继续看官方对于动态代理类功能的描述:

  • 可以使用动态代理类为接口列表创建类型安全的代理对象,而不需要预先生成代理类,例如使用编译时工具。
  • 动态代理类的实例的方法调用被调度到该实例的调用处理程序中的单个方法,并且会被编码为一个标识方法被调用的java.lang.reflect.Method对象和一个包含方法参数的Object类型的数组。
  • 动态代理类对于需要呈现接口API的对象提供类型安全的调用分派的应用程序或库很有用。例如,应用程序可以使用动态代理类来创建一个对象,该对象实现多个任意事件侦听器接口(扩展java.util.EventListener--),以统一的方式处理各种不同类型的事件(例如通过记录所有这样的事件到一个文件)。

既然动态代理类这么牛叉,那么如何创建和使用它呢?

动态代理类实践

还是之前小明海外代购iPhoneX的例子,编写动态代理类。

/**
* "海外代购"动态代理类
* @desc org.chench.test.java.BuyerDynimicProxy
* @date 2017年12月1日
*/
public class BuyerDynimicProxy implements InvocationHandler {
private String name = "海外代购商家";
private Object realBuyer = null; private BuyerDynimicProxy(Object obj) {
this.realBuyer = obj;
} public static Object newInstance(Object obj) {
// 返回对象的动态代理对象,被代理对象的方法被调用时会被分派到动态代理对象的invoke方法
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(),
new BuyerDynimicProxy(obj));
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 当小明要买手机时,会触发该动态代理去购买,买到了再邮寄给小明
Object result = method.invoke(realBuyer, args);
this.doBuyIphoneX();
if(realBuyer instanceof Person) {
Person person = (Person)realBuyer;
// 把手机邮寄给小明
person.receiveIphoneX("iPhoneX");
}
return result;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} /**
* 代理对象去买iphonex
*/
private void doBuyIphoneX() {
System.out.println(getName() + " buy iPhoneX");
}
}

通过动态代理对象代购手机:

/**
* @desc org.chench.test.java.BuySomething
* @date 2017年11月30日
* @version 1.0.0
*/
public class BuySomething {
public static void main(String[] args) {
Person person = new Person();
person.setName("小明"); // 通过动态代理给小明代购手机
Buyer buyerDynimicProxy = (Buyer) BuyerDynimicProxy.newInstance(person);
buyerDynimicProxy.buyIphoneX();
}
}

输出结果:

小明 will buy iPhoneX
海外代购商家 buy iPhoneX
小明 received iPhoneX

总结

以小明代购手机这个使用了代理模式的例子我们可以看到,使用动态代理比使用静态代理带来了更多的灵活性和解耦,主要体现在:

动态代理类不需要在编码中明确实现业务接口(BuyerDynimicProxy并未实现Buyer接口),而是在运行时动态指定。
这样一个动态代理类就可以很方便地实现同时代理多种业务(比如BuyerDynimicProxy除了可以代购手机,还可以代理购房),而静态代理需要明确实现多个代理接口。

实际上,所谓的动态代理就是Java提供了一种机制,允许开发者在编程中灵活方便的实现代理模式的应用,而不需要实现大量重复的耦合性较大的硬编码。

动态代理是Spring AOP框架的基石,正是因为Java动态代理的出现,使得在Java中面向切面编程成为可能。

虽然具备了灵活性,但是动态代理同样需要遵循一定的约定:动态代理类必须实现java.lang.reflect.InvocationHandler接口,用于目标对象在执行业务接口方法时通知到代理对象。 动态代理与目标对象的关系可以使用下图形象表示:



如图所示,当动态代理对象调用它所实现的业务接口方法时,会触发InvocationHandler接口的invoke()方法,可以在这里获得对目标对象的引用,从而执行一些拦截处理。

另外,动态代理仅仅是Java为应用编程提供的一种灵活使用代理模式的手段,但不是必须的,如果处于某些考虑使用静态代理同样可以达到目的。

特别注意: 生成的动态代理对象只能代理目标对象类实现的业务接口方法,不能代理目标对象类中自己定义的方法。

/**
* @desc org.chench.test.java.BuySomething
* @date 2017年11月30日
* @version 1.0.0
*/
public class BuySomething {
public static void main(String[] args) {
Person person = new Person();
person.setName("小明"); // 通过动态代理给小明代购手机
//Buyer buyerDynimicProxy = (Buyer) BuyerDynimicProxy.newInstance(person);
//buyerDynimicProxy.buyIphoneX(); // 动态代理只能代理目标对象类所实现的业务接口方法,如下代码在运行时报错
Person p = (Person) BuyerDynimicProxy.newInstance(person);
p.doSomething();
}
}

【参考】

http://blog.csdn.net/carson_ho/article/details/54910472 代理模式(Proxy Pattern)- 最易懂的设计模式解析

细说java系统之动态代理的更多相关文章

  1. Java 反射 设计模式 动态代理机制详解 [ 转载 ]

    Java 反射 设计模式 动态代理机制详解 [ 转载 ] @author 亦山 原文链接:http://blog.csdn.net/luanlouis/article/details/24589193 ...

  2. 杨晓峰-Java核心技术-6 动态代理 反射 MD

    目录 第6讲 | 动态代理是基于什么原理? 典型回答 考点分析 知识扩展 反射机制及其演进 动态代理 精选留言 Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAnd ...

  3. 深度剖析java中JDK动态代理机制

    https://www.jb51.net/article/110342.htm 本篇文章主要介绍了深度剖析java中JDK动态代理机制 ,动态代理避免了开发人员编写各个繁锁的静态代理类,只需简单地指定 ...

  4. 一文读懂Java中的动态代理

    从代理模式说起 回顾前文: 设计模式系列之代理模式(Proxy Pattern) 要读懂动态代理,应从代理模式说起.而实现代理模式,常见有下面两种实现: (1) 代理类关联目标对象,实现目标对象实现的 ...

  5. 使用Java中的动态代理实现数据库连接池

    2002 年 12 月 05 日 作者通过使用JAVA中的动态代理实现数据库连接池,使使用者可以以普通的jdbc连接的使用习惯来使用连接池. 数据库连接池在编写应用服务是经常需要用到的模块,太过频繁的 ...

  6. java 笔记(3) —— 动态代理,静态代理,cglib代理

    0.代理模式 代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口. 代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类,以及事后处理消息等. 代理类与委托类之间通常会存 ...

  7. java中的动态代理机制

    java中的动态代理机制 在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface).另一个则是 Proxy(Class),这一个类和接口是实现 ...

  8. Java反射和动态代理

    Java反射 反射机制 RTTI 编译器在编译时打开和检查*.class文件 反射机制 运行时打开和检查*.class文件 Java反射常见的方法 java反射的应用 setAccessible(bo ...

  9. Java学习笔记--动态代理

    动态代理 1.JDK动态代理 JDK1.3之后,Java提供了动态代理的技术,允许开发者在运行期创建接口的代理实例.JDK的动态代理主要涉及到java.lang.reflect包中的两个类:Proxy ...

随机推荐

  1. shell 中的 ${} 、## 、%% 使用范例

    日常使用范例见如下例子: 利用 ${ } 还可针对不同的变数状态赋值 (沒设定.空值.非空值): ${file-my.file.txt} :假如 $file 沒有设定,則使用 my.file.txt ...

  2. Android应用开发资源

    Android应用设计和开发人员现在可以参考由Android用户体验(UX)团队官方发布的Android设计指南.该指南提供了开发者应该遵循的基本原则,并列出了很多细节指导,涉及设备与显示.主题.触控 ...

  3. pandas isin

    在已知id索引的情况下,如何获取所需要的行呢?已经不止一次遇到这样的情况,经历过重重筛选,所得到的最终结果是一串满足所有条件的id列表. pandas 的isin 能很好的解决这个问题, import ...

  4. Codeforces Round #533 (Div. 2) C.思维dp D. 多源BFS

    题目链接:https://codeforces.com/contest/1105 C. Ayoub and Lost Array 题目大意:一个长度为n的数组,数组的元素都在[L,R]之间,并且数组全 ...

  5. zabbix自动发现

    zabbix3.4 Discovery自动发现教程 Zabbix 创建发现规则创建发现规则配置 ---- 自动发现 ---- 创建发现规则 看一个例子 这样发现规则就没有问题了,下面让主机自动加入到某 ...

  6. JavaScript(JS)之Javascript对象BOM,History,Location,Function...(二)

    https://www.cnblogs.com/haiyan123/p/7594046.html 在JavaScript中除了null和undefined以外其他的数据类型都被定义成了对象,也可以用创 ...

  7. Linux安装 火速入门

    一.基本简介 Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和UNIX的多用户.多任务.支持多线程和多CPU的操作系统. Linux能运行主要的UNIX工具软件.应用程序 ...

  8. Unity 着色器基础知识

    一.着色器基础知识 着色器通过代码模拟物体表面发生的事情,其实就是GPU中运行的一段代码. 着色器的类型: 顶点着色器.片元着色器.无光照着色器.表面着色器.图像特效着色器.计算着色器. 坐标空间: ...

  9. discuz 3.1论坛快照被百度劫持解决方案

    最近很郁闷,遇到一个很棘手的问题.我们公司有个论坛在百度查看快照信息的时候全部都是博彩信息,但是打开却无博彩信息显示.在快照中查看是这样的 百度快照查看图: 经过思考,怀疑是网站中有网页被改动了,在某 ...

  10. Luogu P4248 [AHOI2013]差异

    题目链接 \(Click\) \(Here\) 神仙题.或者可能我太菜了没见过后缀数组的骚操作,然后就被秀了一脸\(hhhhh\) \[\sum\limits_{1<=i < j < ...