网站首页 > java教程 正文
前言 : 最近正在学习一些设计模式 ,正好看到代理的这一块,写这篇文章的目的就是让自己这个菜鸟加深点印象,如有出错的地方还请海涵 !
代理模式的概念 :
为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用
看着概念,大家可能不太清楚,那么我们一步一步的通过举例来说明,
具体的我们分为 3 个步骤来 引出 我们的终极BOSS,JDK动态代理 Proxy
- 1. 无代理
- 2. 静态代理
- 3. 动态代理
我们 以人类需要吃饭的例子来说明吧
一 、无代理
//定义人这个接口
public interface Person {
//定义人这个吃饭的行为
void eat();
}
// 这个人接口的实现类
public class PersonImpl implements Person{
@Override
public void eat() {
//这里我们吃点面
System.out.println(" ======吃面====== ");
}
}
当我们需要在吃面之前后做一些事,比如,选择餐厅 ,霸王餐之类的 ... ,那么我们正常的会这样做
@Override
public void eat() {
before();
//这里我们吃点面
System.out.println(" ======吃面====== ");
after();
}
public void before(){
//这里吃之前做的事
System.out.println("=======选择目标餐厅======");
}
public void after(){
//这里吃之后做的事
System.out.println("======假装上厕所从后门溜走=======");
}
这样写是不是很简单, 大多时候我们就是这样写的,但是这样写就使得这块没有扩展性和灵活性了,
因为,这里是吃面,那当我们吃大米饭的时候也需要这样做的时候,是不是又要写一个实现类来实现Person接口,然后在里面写上
before() , eat() , after()
甚至,当我们,eat() 之后 突然良心发现,想给钱了(目前是吃霸王餐),那还是不是又在里面加代码,
public void after(){
//这里吃之后做的事
System.out.println("======良心发现->付账=======");
}
然后没处都修改过去 ... ,然而, 当这些代码都在别人的 原来的代码上改的, 甚至在jar里面的呢,
是不是 头都大了
PS : 头都大了(TDDL),貌似是阿里开发的一个 支持数据库读写分离分表分库等 强大功能的 集成组件
那么 我们来看看静态代理模式是怎么实现的吧
二、静态代理
public class PersonProxy implements Person {
Person person ;
public PersonProxy() {
person = new PersonImpl() ;
}
@Override
public void eat() {
before();
person.eat();
after();
}
public void before(){
//这里吃之前做的事
System.out.println("=======选择目标餐厅======");
}
public void after(){
//这里吃之后做的事
System.out.println("======假装上厕所从后门溜走=======");
}
}
// 这个人接口的实现类
public class PersonImpl implements Person{
@Override
public void eat() {
//这里我们吃点面
System.out.println(" ======吃面====== ");
}
}
通过代码可以看出,
- 我们新建了一个PersonProxy 类,
- 实现了和 PersonImpl 同一个的 接口 Person
- 我们在 他的内部定义了 Person这个属性, 却在构造函数中 将他指向了PersonImpl ;
- 重写了 父类的 eat方法,在这个eat() 方法中调用 具体实现类的
- 并且在这个方法中的前后调用 before(), after()
下面我来看一下这样写是怎么调用的
public static void main(String[] args) {
Person PersonProxy = new PersonProxy();
PersonProxy.eat();
}
这里 我们还是持有 Person 对象的应用,只是实例化的过程变了,执行这个过程,会为我们自动加上前后的是;试想一下,我们这里的PersonProxy 对象中的 构造函数中的实例化类型是动态传入的, 是不是能解决,去吃大米饭的问题了,并且做一下和吃面前后一样要做的是 我们再来看看,怎么实现
首先定义吃大米饭的实现类 PersonAImpl(注意这里名称多了一个A)
class PersonAImpl implements Person{
@Override
public void eat() {
System.out.println(" ======吃大米饭====== ");
}
}
看我门稍稍修改之后的类 PersonProxy 类
public class PersonProxy implements Person {
Person person ;
public PersonProxy(Person person) {
this.person = person ;
}
@Override
public void eat() {
before();
person.eat();
after();
}
public void before(){
//这里吃之前做的事
System.out.println("=======选择目标餐厅======");
}
public void after(){
//这里吃之后做的事
System.out.println("======假装上厕所从后门溜走=======");
}
public static void main(String[] args) {
Person PersonProxy = new PersonProxy(new PersonAImpl());
PersonProxy.eat();
}
}
注意上面的构造函数,仅仅改动那一块地方,需要代理的对象右开发者手动传入,
再看看我们 在调用处的函数 :
public static void main(String[] args) {
Person PersonProxy1 = new PersonProxy(new PersonImpl());
PersonProxy1.eat();
System.out.println("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
System.out.println("+++++++++++++++我是可爱的分割线+++++++++++++++++++++++++++++++++");
System.out.println("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
Person PersonProxy2 = new PersonProxy(new PersonAImpl());
PersonProxy2.eat();
}
再看看输出结果 :
=======选择目标餐厅======
======吃面======
======假装上厕所从后门溜走=======
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+++++++++++++++我是可爱的分割线+++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
=======选择目标餐厅======
======吃大米饭======
======假装上厕所从后门溜走=======
总结 一下吧:
我们这样写是不是 不需要在意你吃什么了,
就算你要 吃翔,也可以先悄悄的选择场所(PS:听说现在某些旅游景点的厕所很高打上 ...,有大屏电视还是有精美的装修,还有 ...各种其他的,就像个休息室一样的。真是贫穷限制了我的想像 。),然后悄悄的溜走了;
当然 .. ... .. ^_* 。
优点 :
1. 是我们不需要去改eat() 函数里面的 各种源代码,做各种判断;
2. 要是还有别的吃法,需要这样的流程,我们只需要写个实现Person 这个接口的实现类就好了
3. 如果以后这里改为想付账了,我只需要改个PersonProxy 这个代理类就OK;
缺点:
当我们的吃饭不是吃饭这个动作的时候, 并且我也要做那ea() 前后 的那些事,
比如去上班,别问我问什么上班前需要找餐厅 。。。
那么我们的实现方式有可能是 :
- 新写一个接口, 工作的的接口, 并且写上这个工作的实现类, 然后在写一个代理类,说白了就是复制上面的一套,并把,eat() 改变为 work() , 然后就 over ;
- 在原来的接口接触上增加work() 方法,并在代理中重写work() 并且在里面写上before() ,after()
大家来看看会存在什么样的问题:
- 两者都存在大量的重复代码 , 当然第二种会优于第一种,
- 第二种的,当新加的和原来的 的业务都完全没关系时,能定义中同一个就接口中吗?
下面我们来看看第三种解决方案、
三、动态代理
其实我们在上面的 第二种方案中已经用到了一点动态的感觉了,比如,那个对象是有 具体传入的对象类型来控制的,充分的利用了java的多态,
那么我们这里说的动态,是java 在运行是产生一个新的class , 和上面的多态完全是不同的;
下面我们来看看 具体的实现代码吧:
原来的类:
// 接口
//定义人这个接口
public interface Person {
//定义人这个吃饭的行为
void eat();
}
// 这个人接口的实现类
public class PersonImpl implements Person{
@Override
public void eat() {
//这里我们吃点面
System.out.println(" ======吃面====== ");
}
}
class PersonAImpl implements Person{
@Override
public void eat() {
System.out.println(" ======吃大米饭====== ");
}
}
新的:
package noproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class PersonProxyHandle implements InvocationHandler {
private Object obj ; // 接收 任何 的对象,没有任何的限制
//绑定委托对象(即真正的目标对象) 并返回代理对象
public Object bind(Object tar) {
this.obj = tar;
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res = null ;
before(); // 之前
res = method.invoke(obj, args);
after(); // 之后
return res;
}
public void before(){
//这里吃之前做的事
System.out.println("=======选择目标餐厅======");
}
public void after(){
//这里吃之后做的事
System.out.println("======假装上厕所从后门溜走=======");
}
}
代码是不是很简单 ,就短短的几行,
我们先来看看调用处是怎么写的 :
public static void main(String[] args) {
PersonProxyHandle pph1 = new PersonProxyHandle();
Person person1 = (Person) pph1.bind(new PersonImpl());
person1.eat();
System.out.println("<<<<<<<<<<<<<<<<<<<<<我是华丽的分割线<<<<<<<<<<<<<<<<<<<<<<<<<<");
PersonProxyHandle pph2 = new PersonProxyHandle();
Person person2 = (Person) pph2.bind(new PersonAImpl());
person2.eat();
}
再看看输出结果
=======选择目标餐厅======
======吃面======
======假装上厕所从后门溜走=======
<<<<<<<<<<<<<<<<<<<<<我是华丽的分割线<<<<<<<<<<<<<<<<<<<<<<<<<<
=======选择目标餐厅======
======吃大米饭======
======假装上厕所从后门溜走=======
再翻看前面静态代理的结果,是不是一模一样,
我们 再来看看 扩展性 :
扩展(一):
新建一个接口 AAA,再一个类 AAAImpl, 记住这类 和前面的业务逻辑完全不一样 ;可以自己天马行空的发挥想象
public interface AAA {
void doing();
}
public class AAAImpl implements AAA{
@Override
public void doing() {
System.out.println("我这里可能需要做些什么,比如泡妞,玩游戏打球");
}
}
我们再看看调用处是怎么写的:
public static void main(String[] args) {
PersonProxyHandle pph2 = new PersonProxyHandle();
AAA aaa = (AAA) pph2.bind(new AAAImpl());
aaa.doing();
}
输出结果 :
=======选择目标餐厅======
我这里可能需要做些什么,比如泡妞,玩游戏打球
======假装上厕所从后门溜走=======
扩展(二):
我们在原来的接走新增方法:
AAA 接口中新增方法 sleep()
public interface AAA {
void doing();
void sleep();
}
然后在AAAImpl 中重写它
public class AAAImpl implements AAA{
@Override
public void doing() {
System.out.println("我这里可能需要做些什么,比如泡妞,玩游戏打球");
}
@Override
public void sleep() {
System.out.println("我们玩累了,需要休息了, 我要取宾馆睡觉或者回家睡觉");
}
}
我们再来看调用及输出:
//这里是调用的代码
public static void main(String[] args) {
PersonProxyHandle pph2 = new PersonProxyHandle();
AAA aaa = (AAA) pph2.bind(new AAAImpl());
aaa.sleep();;
}
//以下是输出的内容 :
=======选择目标餐厅======
我们玩累了,需要休息了, 我要取宾馆睡觉或者回家睡觉
======假装上厕所从后门溜走=======
看到没有,我们这里只改变了 调用函数的地方是不是很神奇;
我们不需要在自己写代理类(原 PersonProxy 类)以及不需要自己在每个原代理类中的方法中调用 before() 和after()
现在我们来看看 具体是怎么实现的:
核心类 PersonProxyHandle , 中核心的方法
private Object obj ; // 接收 任何 的对象,没有任何的限制
//绑定委托对象(即真正的目标对象) 并返回代理对象
public Object bind(Object tar) {
this.obj = tar;
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res = null ;
before(); // 之前
res = method.invoke(obj, args);
after(); // 之后
return res;
}
里面唯一个 属性, Object obj ;
这里属性代表这这里可以接受任何的对象,并且帮他产生一个代理的对象(就像原来的PersonProxy)
就是bind 返回的返回值,这个返回值不是一个AAAImpl 类的具体实例,而是一个和AAAImpl一样实现了AAA的一个类,就像我们在静态代理中自己手动实现的那个类(PersonProxy), 所以这里也还是用了 AAA aaa 这个来接收;我们这里可以过开发工具打断点看出,
看下面的图,很重要 ,敲黑板 !!!
aaa 变量的应用是 $Proxy0 ; 这个类继承了 Proxy 实现了 AAA 接口 ;
并且里面 有 重写 eq , hashCode ,toString 以及 AAA 的方法(sleep() )
看下这个 $Proxy0 这个反编译后的大致代码, 主要就是 sleep() ;
private static Method m3;
... // eq hashCode toString
public final String sleep()
{
return (Object)this.h.invoke(this, m3, null);
}
static
{
... // eq , hashCode ,toString
m3 = Class.forName("AAA").getMethod("sleep", new Class[0]);
...
}
最后 我们再看 aaa.sleep() 的调用
aaa.sleep() 实际就是 $Proxy0.sleep()
即
return (Object)this.h.invoke(this, m3,null);
从上面的截图中可以看出 h 就是 PersonProxyHandle
invoke(this.m3,null) 就是 下面的这段代码(上面已经贴过)
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res = null ;
before(); // 之前
res = method.invoke(obj, args);
after(); // 之后
return res;
}
中间在利用反射 调用具体的方法
下面的就不用说了吧 . . .
号外 : Proxy.newInstance( .... ), 这个方法就是返回指定的 代理类,里面的具体的单个参数就是给java虚拟机在程序运行时 生成代理类使用的;
优点 我就不多说了 ,扩展性 ,灵活性是不是提升了好几呗,
说下缺点 :
每一个都要定义一个接口 :这就是 java jdk 自带的动态代理模式的一个限制;
后面再研究下 cglib 。
听说 Spring AOP 就是用这个JDK Proxy实现的,不过我没有看过源码,只看过一些资料。。。
参考文章 :
- http://www.daidingkang.cc/2017/07/18/java-reflection-annotations/
- http://blog.csdn.net/u013476542/article/details/53228552?locationNum=6&fps=1
- http://blog.csdn.net/jianghuxiaoxiami/article/details/3403924
- http://www.jb51.net/article/125621.htm
?
猜你喜欢
- 2024-10-24 Java动态代理与静态代理以及它能为我们做什么
- 2024-10-24 Java设计模式:代理模式 vs. 装饰模式
- 2024-10-24 设计模式篇——代理模式详解(面试再问你代理模式,这么回答他)
- 2024-10-24 动态代理大揭秘,带你彻底弄清楚动态代理
- 2024-10-24 面试:Java的代理模式动态代理和静态代理区别,aop用的什么代理
- 2024-10-24 Java设计模式之代理模式(java代理类是什么)
- 2024-10-24 JAVA设计模式——代理模式(java编写代理服务)
- 2024-10-24 23种java设计模式之:门面模式、享元模式、代理模式
- 2024-10-24 Java 17中的动态代理:实现灵活的代理模式
- 2024-10-24 代理模式(剪映怎么打开代理模式)
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)