java | 什么是动态代理?

代理模式在 Java 领域很多地方都有应用,它分为静态代理和动态代理,其中 Spring AOP 就是动态代理的典型例子。动态代理又分为接口代理和 cglib (子类代理),结合我的理解写了几个 demo 分享给你们,这是昨晚修仙到 3 点写出来的文章,不点在看,我觉得说不过去了。

代理模式在我们日常中很常见,生活处处有代理:

看张学友的演唱会很难抢票,可以找黄牛排队买

嫌出去吃饭麻烦,可以叫外卖

无论是黄牛、外卖骑手都得帮我们干活。但是他们不能一手包办(比如黄牛不能帮我吃饭),他们只能做我们不能或者不想做的事。

找黄牛可以帮我排队买上张学友的演唱会门票

外卖骑手可以帮我把饭送到楼下

所以,你看。代理模式其实就是当前对象不愿意做的事情,委托给别的对象做。

静态代理
我还是以找黄牛帮我排队买张学友的演唱会门票的例子,写个 demo 说明。现在有一个 Human 接口,无论是我还是黄牛都实现了这个接口。

public interface Human {void eat();void sleep();void lookConcert();}

例如,我这个类,我会吃饭和睡觉,如以下类:

public class Me implements Human{

@Override
public void eat() {System.out.println("eat emat ....");
}@Override
public void sleep() {System.out.println("Go to bed at one o'clock in the morning");
}@Override
public void lookConcert() {System.out.println("Listen to Jacky Cheung's Concert");
}

}
有黄牛类,例如:

public class Me implements Human{

@Override
public void eat() {
}@Override
public void sleep() {
}@Override
public void lookConcert() {
}

}
现在我和黄牛都已经准备好了,怎么把这二者关联起来呢?我们要明确的是黄牛是要帮我买票的,买票必然就需要帮我排队,于是有以下黄牛类:注意这里我们不关心,黄牛的其他行为,我们只关心他能不能排队买票。

public class HuangNiu implements Human{

private Me me;public HuangNiu() {me = new Me();
}@Override
public void eat() {
}@Override
public void sleep() {
}@Override
public void lookConcert() {// 添加排队买票方法this.lineUp();me.lookConcert();
}public void lineUp() {System.out.println("line up");}

}
最终的 main 方法调用如下:

public class Client {

public static void main(String[] args) {Human human = new HuangNiu();human.lookConcert();}

}
结果如下:

静态代理结果
静态代理结果
由此可见,黄牛就只是做了我们不愿意做的事(排队买票),实际看演唱会的人还是我。客户端也并不关心代理类代理了哪个类,因为代码控制了客户端对委托类的访问。客户端代码表现为 Human human = new HuangNiu();

由于代理类实现了抽象角色的接口,导致代理类无法通用。比如,我的狗病了,想去看医生,但是排队挂号很麻烦,我也想有个黄牛帮我的排队挂号看病,但是黄牛它不懂这只狗的特性(黄牛跟狗不是同一类型,黄牛属于 Human 但狗属于 Animal 类)但排队挂号和排队买票相对于黄牛来说它两就是一件事,这个方法是不变的,现场排队。那我们能不能找一个代理说既可以帮人排队买票也可以帮狗排队挂号呢?

答案肯定是可以的,可以用动态代理。

基于接口的动态代理
如静态代理的内容所描述的,静态代理受限于接口的实现。动态代理就是通过使用反射,动态地获取抽象接口的类型,从而获取相关特性进行代理。因动态代理能够为所有的委托方进行代理,因此给代理类起个通用点的名字 HuangNiuHandle。先看黄牛类可以变成什么样?

public class HuangNiuHandle implements InvocationHandler {

private Object proxyTarget;public Object getProxyInstance(Object target) {this.proxyTarget = target;return Proxy.newProxyInstance(proxyTarget.getClass().getClassLoader(), proxyTarget.getClass().getInterfaces(), this);
}@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object methodObject = null;System.out.println("line up");methodObject = method.invoke(proxyTarget, args);System.out.println("go home and sleep");return methodObject;
}

}
这个时候的客户端代码就变成这样了

public class Client {

public static void main(String[] args) {HuangNiuHandle huangNiuHandle = new HuangNiuHandle();Human human = (Human) huangNiuHandle.getProxyInstance(new Me());human.eat();human.run();human.lookConcert();System.out.println("------------------");Animal animal = (Animal) huangNiuHandle.getProxyInstance(new Dog());animal.eat();animal.run();animal.seeADoctor();
}

}
使用动态代理有三个要点,

必须实现 InvocationHandler 接口,表明该类是一个动态代理执行类。

InvocationHandler 接口内有一实现方法如下: public Object invoke(Object proxy, Method method, Object[] args) 。使用时需要重写这个方法

获取代理类,需要使用 Proxy.newProxyInstance 这个方法去获取Proxy对象(Proxy 类类型的实例)。

注意到 Proxy.newProxyInstance 这个方法,它需要传入 3 个参数。解析如下:

// 第一个参数,是类的加载器
// 第二个参数是委托类的接口类型,证代理类返回的是同一个实现接口下的类型,保持代理类与抽象角色行为的一致
// 第三个参数就是代理类本身,即告诉代理类,代理类遇到某个委托类的方法时该调用哪个类下的invoke方法
Proxy.newProxyInstance(Class loader, Class<?>[] interfaces, InvocationHandler h)
再来看看 invoke 方法,用户调用代理对象的什么方法,实质上都是在调用处理器的 invoke 方法,通过该方法调用目标方法,它也有三个参数:

// 第一个参数为 Proxy 类类型实例,如匿名的 $proxy 实例
// 第二个参数为委托类的方法对象
// 第三个参数为委托类的方法参数
// 返回类型为委托类某个方法的执行结果
public Object invoke(Object proxy, Method method, Object[] args)
调用该代理类之后的输出结果:

动态代理
动态代理
由结果可知,黄牛不仅帮了(代理)我排队买票,还帮了(代理)我的狗排队挂号。所以,你看静态代理需要自己写代理类(代理类需要实现与目标对象相同的接口),还需要一一实现接口方法,但动态代理不需要。

注意,我们并不是所有的方法都需要黄牛这个代理去排队。我们知道只有我看演唱会和我的狗去看医生时,才需要黄牛,如果要实现我们想要的方法上面添加特定的代理,可以通过 invoke 方法里面的方法反射获取 method 对象方法名称即可实现,所以动态代理类可以变成这样:

public class HuangNiuHandle implements InvocationHandler {

private Object proxyTarget;public Object getProxyInstance(Object target) {this.proxyTarget = target;return Proxy.newProxyInstance(proxyTarget.getClass().getClassLoader(), proxyTarget.getClass().getInterfaces(), this);
}@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object methodObject = null;if ("lookConcert".equals(method.getName()) ||"seeADoctor".equals(method.getName())) {System.out.println("line up");// 调用目标方法methodObject = method.invoke(proxyTarget, args);} else {// 不使用第一个proxy参数作为参数,否则会造成死循环methodObject = method.invoke(proxyTarget, args);}return methodObject;
}

}
结果如下:可以看到我们只在特定方法求助了黄牛

动态代理
动态代理
由此可见,动态代理一般应用在记录日志等横向业务。

值得注意的是:

基于接口类的动态代理模式,必须具备抽象角色、委托类、代理三个基本角色。委托类和代理类必须由抽象角色衍生出来,否则无法使用该模式。

动态代理模式最后返回的是具有抽象角色(顶层接口)的对象。在委托类内被 private 或者 protected 关键修饰的方法将不会予以调用,即使允许调用。也无法在客户端使用代理类转换成子类接口,对方法进行调用。也就是说上述的动态代理返回的是委托类(Me)或 (Dog)的就接口对象 (Human)或 (Animal)。

在 invoke 方法内为什么不使用第一个参数进行执行回调。在客户端使用 getProxyInstance(new Child( ))时,JDK 会返回一个 proxy 的实例,实例内有InvokecationHandler 对象及动态继承下来的目标 。客户端调用了目标方法,有如下操作:首先 JDK 先查找 proxy 实例内的 handler 对象 然后执行 handler 内的 invoke 方法。

根据 public Object invoke 这个方法第一个参数 proxy 就是对应着 proxy 实例。如果在 invoke 内使用 method.invoke(proxy,args) ,会出现这样一条方法链,目标方法→invoke→目标方法→invoke…,最终导致堆栈溢出。

基于子类的动态代理
为了省事,我这里并没有继承父类,但在实际开发中是需要继承父类才比较方便扩展的。与基于接口实现类不同的是:

CGLib (基于子类的动态代理)使用的是方法拦截器 MethodInterceptor ,需要导入 cglib.jar 和 asm.jar 包

基于子类的动态代理,返回的是子类对象

方法拦截器对 protected 修饰的方法可以进行调用

代码如下:

public class Me {

public void eat() {System.out.println("eat meat ....");
}public void run() {System.out.println("I run with two legs");
}public void lookConcert() {System.out.println("Listen to Jacky Cheung's Concert");
}protected void sleep() {System.out.println("Go to bed at one o'clock in the morning");
}

}
Dog 类

public class Dog {

public void eat() {System.out.println("eat Dog food ....");
}public void run() {System.out.println("Dog running with four legs");
}public void seeADoctor() {System.out.println("The dog go to the hospital");
}

}
黄牛代理类,注意 invoke() 这里多了一个参数 methodProxy ,它的作用是用于执行目标(委托类)的方法,至于为什么用 methodProxy ,官方的解释是速度快且在intercep t内调用委托类方法时不用保存委托对象引用。

public class HuangNiuHandle implements MethodInterceptor {

private Object proxyTarget;public Object getProxyInstance(Object target) {this.proxyTarget = target;return Enhancer.create(target.getClass(), target.getClass().getInterfaces(), this);
}@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {Object methodObject = null;if ("lookConcert".equals(method.getName()) ||"seeADoctor".equals(method.getName())) {System.out.println("line up");// 调用目标方法methodObject = methodProxy.invokeSuper(proxy, args);} else {methodObject = method.invoke(proxyTarget, args);}return methodObject;
}

}
client 类

public class Client {

public static void main(String[] args) {HuangNiuHandle huangNiuHandle = new HuangNiuHandle();Me me = (Me) huangNiuHandle.getProxyInstance(new Me());me.eat();me.run();me.sleep();me.lookConcert();System.out.println("------------------");Dog dog = (Dog) huangNiuHandle.getProxyInstance(new Dog());dog.eat();dog.run();dog.seeADoctor();
}

}
结果:

基于子类的动态代理
基于子类的动态代理
注意到 Me 类中被 protected 修饰的方法 sleep 仍然可以被客户端调用。这在基于接口的动态代理中是不被允许的。

静态代理与动态代理的区别
静态代理需要自己写代理类并一一实现目标方法,且代理类必须实现与目标对象相同的接口。

动态代理不需要自己实现代理类,它是利用 JDKAPI,动态地在内存中构建代理对象(需要我们传入被代理类),并且默认实现所有目标方法。

java | 什么是动态代理?相关推荐

  1. 吃透Java中的动态代理

    动态代理在Java中是很重要的一部分,在很多框架中都会用到,如Spring中的AOP.Hadoop中的RPC等.为此在这把我对Java中的动态代理的理解分享给大家,同时写了一个模拟AOP编程的实例.( ...

  2. Java基础:动态代理

    系列阅读 Java基础:类加载器 Java基础:反射 Java基础:注解 Java基础:动态代理 概述 在运行时,动态创建一组指定的接口的实现类对象(代理对象)! 代理是实现AOP(面向切面编程)的核 ...

  3. Java se之动态代理

    转载自 Java se之动态代理 jdk动态代理: jdk动态代理是 需要提供一个实现了InvocationHandler接口的处理类: 通过Proxy的newProxyInstance()方法,参数 ...

  4. 学习spring必须java基础知识-动态代理

    2019独角兽企业重金招聘Python工程师标准>>> Spring AOP使用动态代理技术在运行期织入增强的代码,为了揭示Spring AOP底层的工作机理,有必要对涉及到的Jav ...

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

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

  6. 码农翻身——Java帝国之动态代理

    已经快三更天了, Java帝国的国王还在看着IO大臣的奏章发呆,他有点想不明白, 帝国已经给臣民了提供了这么多的东西,他们为什么还不满意呢? 集合.IO.反射.网络.线程.泛型.JDBC ...... ...

  7. Java设计模式-----Cglib动态代理(Cglib Proxy)

    接上文:4.2Java设计模式-----JDK动态代理(Dynamic Proxy) Cglib动态代理 百度百科:Cglib是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java ...

  8. 仿照java的jdk动态代理实现go语言动态代理

    仿照java的jdk动态代理实现go语言动态代理 通过学习java的jdk动态代理和Cglib动态代理,仿照jdk动态代理用go实现了一个简单的动态代理 结构型模式 代理模式 代理模式中分为静态代理和 ...

  9. java Proxy.newProxyInstance 动态代理 简介

    利用Java的反射技术(Java Reflection),在运行时创建一个实现某些给定接口的新类(也称"动态代理类")及其实例(对象),代理的是接口(Interfaces),不是类 ...

最新文章

  1. 平述factory reset ——从main system到重引导流程
  2. boost::container_hash模块实现哈希图
  3. 2020\Simulation_1\6.递增三元组
  4. LRU缓存 数据结构设计(C++)
  5. Linux无法登陆,var目录权限修改导致SSH失败
  6. centos 时区正确,时间不对
  7. 【零基础学Java】—List集合(三十九)
  8. 樊登高效休息法心得400字_超级干货!让你快速恢复精力的高效休息法!!
  9. H3C PPP 基本配置
  10. 日期插件datepicker的使用
  11. C++ UPD广播异步发包工具
  12. H5加壳APP发布Android、IOS应用(证书响应文件制作)
  13. SSL认证之相关证书的生成
  14. Hello MySQL(四)——MySQL数据库创建实例
  15. Chrome快捷键整理
  16. 一周学python系列(7)——面向对象
  17. 三极管工作原理--我见过最通俗讲法
  18. Python_Appium爬取wx朋友圈
  19. 用JavaScript实现简单的星座查询
  20. 【知识图谱】知识图谱构建技术一览

热门文章

  1. Oracle 修改字符集(AL32UTF8 转换成UTF8字符集)
  2. PhotoShop的字体安装及制作文字特效
  3. 针对常见的四种短路故障(单相接地短路,两相相间短路,两相接地短路,三相短路),可采取三种方法进行计算
  4. RedHat Linux 9.0的安装+下载+入门指南(图文并茂)
  5. 云展网教程 | PDF上传后部分页面内容不显示/文字图片错位/PDF转换很慢或者失败
  6. Broadcast详解
  7. #Geek Talk# 007 何以解忧:唯有沟通!
  8. 搭建小说系统源码,如何实现读书的分页功能
  9. “/”与“\”以及“//”与“\\”之间的区别 (转)
  10. 2021林伟华中学高考成绩查询,2021年汕尾高考状元名单公布,汕尾高考状元学校资料及最高分...