动态代理

动态代理源于设计模式中的代理模式,代理模式的主要作用就是使代理对象完成用户的请求,屏蔽用户对真实对象的访问。通过代理对象去访问目标对象来控制原对象的访问。

代理模式的最典型的应用就是 Spring AOP。

静态代理

代理模式的实现有两种,静态代理动态代理,静态代理的代理类是需要程序员去写的,而动态代理的代理类是自动生成的。

静态代理需要持有被代理对象的引用,通过这个引用去调用被代理对象的方法。

我们来看一个静态代理的实例:

首先定义一个接口,代理对象和被代理对象都需要实现这个接口。

public interface IService{
void sayHello();
}

被代理的对象:

public class RealClass implements IService{
@Override
public void sayHello(){
System.out.println("hello world.....");
} public void doService(){
System.out.println("doing service.....");
}
}

真正的代理类需要做的事情:

public class ProxyClass implements IService{
//被代理对象的引用
private RealClass realClass; public ProxyClass(RealClass realClass){
this.realClass = realClass;
} @Override
public void sayHello(){
System.out.println("i am proxy, prepare for saying hello...");
realClass.sayHello();
System.out.println("i am proxy, saying hello ending...");
} public void doService(){
System.out.println("i am proxy, prepare for saying hello...");
realClass.doService();
System.out.println("i am proxy, saying hello ending...");
}
}

一般来说,代理类会选择直接继承被代理类所有的接口和父类以便于实现所有被代理类的方法。

到这里其实静态代理就讲完了,也没有什么难点。但是动态代理不同于静态代理的点在于,代理类不用我们自己写。

JDK 动态代理

动态代理区别于静态代理的一点是,动态代理的代理类由虚拟机在运行时动态创建并于虚拟机卸载时清除。

我们还用上面的 RealClass 类,看看 JDK 的动态代理是如何实现的。

首先定义一个 Handler 类,它继承自 InvocationHandler。

public class MyHandler implements InvocationHandler{
//同样需要传入被代理类的引用,这里我们直接使用 Object 类型
private Object realObject; public MyHandler(Object realObj){
this.realObject = realObj;
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Exception{
System.out.println("proxy begainning ... ");
// 通过反射的方式,调用被代理对象的方法
Object result = method.invoke(realObj,args);
System.out.println("proxy ending....");
return result;
}
}

代理类的生成以及调用过程:

public static void main(String[] args){
RealClass rc = new RealClass();
MyHandler handler = new MyHandler(rc);
Object obj = Proxy.newProxyInstance(rc.getClass().getClassLoader(),new Class[]{IService.class},handler);
}
  1. 首先我们定义处理类,它继承了 InvocationHandler 并实现了 invoke 方法。这里面还是需要传入被代理对象的引用。
  2. InvocationHandler 的实现类里面通过反射来调用被代理兑现的方法,还可以加上一些新的功能。
  3. 通过 JDK 提供的 Proxy 类的 newProxyInstance 方法来构建代理类,里面需要传入三个参数,类加载器,被代理类的所有接口,第三个是我们自己定义的 InvocationHandler 的实现类。

我们通过反编译来看看这个代理类的 Class 到底有什么不同:

  1. 生成的代理类的名字是很随意的,一个程序中如果有多个代理类药生成,【$PRroxy + 数字】就是它们的类名。
  2. 这个代理类继承了 Proxy 类并且实现了 IService 接口(之前如果指定多个,这里就会继承多个)。
  3. 代理类的构造器需要传入 InvocationHandler 类型的参数。并且将这个参数传递给了父类。这也是为什么所有代理类都必须使用 Proxy 作为父类的一个原因。

我们继续看代理类下面的内容:

  1. 包裹在 static 里面的静态代码块很重要,通过反射获取了四个 Method。其中有三个是 Object 类的常用方法,也就是说代理类还会代理被代理对象从 Object 继承来的方法,还有一个是被代理类的接口的方法。

  1. 最后一部分我们看到的是,虚拟机根据静态初始化代码块所反射出来的所有代理方法,为他们生成代理的方法。
  2. 调用时需要从父类 Proxy 中取出构造实例化时存储的处理器类,并调用它的 invoke 方法。
  3. 第一个参数是当前代理类的实例(事实证明这个参数并没有什么用),第二个参数是 Method 方法实例,第三个参数是方法的形式参数集合,如果没有就是 null。

总结

  1. 一个处理器类的定义是必不可少的,它的内部必须关联一个真实对象,即被代理类实例。
  2. 我们从外部调用代理类的任意一个方法,从反编译的源码我们知道,代理类方法会转而去调用处理器的 invoke 方法并传入方法签名和参数。

缺陷

我们需要注意到,虚拟机生成的代理类为了共用 Proxy 类中的 InvocationHandler 字段来存储自己的处理器类实例而继承了 Proxy 类。

这里有个问题,说明代理类不能在继承其他类了。那么被代理类父类的方法自然就无法获取了,即代理类无法代理被代理类中父类的任何方法,只能代理接口中的方法,这也是我们常说的 JDK 动态代理必须实现接口的原因。

RealClass 自己的方法 doService 也没有被代理,只有接口中的方法被代理了。所以说,JDK 的动态代理机制是单一的,它只能代理被代理类的接口集合中的方法。

类中的非接口方法和父类中的方法是无法被代理的。

不友好的返回值。

public static void main(String[] args){
RealClass rc = new RealClass();
MyHandler handler = new MyHandler(rc);
Object obj = Proxy.newProxyInstance(rc.getClass().getClassLoader(),new Class[]{IService.class},handler);
}

newProxyInstance 返回的是代理类 【Proxy0】的一个实例,但是它是以 Object 类型进行返回的,而你又不能把它强转成 【Proxy0】类型。因为编译期是不存在这个【Proxy0】类型的,所以一般只会强转为该代理类实现的接口之一。

IService obj = (IService)Proxy.newProxyInstance(
rc.getClass().getClassLoader(),
new Class[]{IService.class},
hanlder);
obj.sayHello();

现在问题又来了,加入我们被代理类实现了多个接口,那么你该强转为哪个接口类型哪?可能需要你多次进行转换,这样的设计相当不友好。

下一篇我们将介绍一个广为各类框架使用的 CGLIB 动态代理库,它的底层基于字节码操作框架 ASM,不再依赖继承来实现,完美的解决了 JDK 的单一代理的不足。

动态代理之 JDK 动态代理的更多相关文章

  1. 静态代理与JDK动态代理

    demo地址: https://github.com/ZbLeaning/leaning 代理: 为其他对象提供一种代理以控制对这个对象的访问.分为静态代理和动态代理.代理模式的目的就是为真实业务对象 ...

  2. Java之代理(jdk静态代理,jdk动态代理,cglib动态代理,aop,aspectj)

    一.概念 代理是什么呢?举个例子,一个公司是卖摄像头的,但公司不直接跟用户打交道,而是通过代理商跟用户打交道.如果:公司接口中有一个卖产品的方法,那么公司需要实现这个方法,而代理商也必须实现这个方法. ...

  3. java的静态代理、jdk动态代理和cglib动态代理

    Java的代理就是客户端不再直接和委托类打交道,而是通过一个中间层来访问,这个中间层就是代理.使用代理有两个好处,一是可以隐藏委托类的实现:二是可以实现客户与委托类之间的解耦,在不修改委托类代码的情况 ...

  4. 从静态代理,jdk动态代理到cglib动态代理-一文搞懂代理模式

    从代理模式到动态代理 代理模式是一种理论上非常简单,但是各种地方的实现往往却非常复杂.本文将从代理模式的基本概念出发,探讨代理模式在java领域的应用与实现.读完本文你将get到以下几点: 为什么需要 ...

  5. Java代理(静态代理、JDK动态代理、CGLIB动态代理)

    Java中代理有静态代理和动态代理.静态代理的代理关系在编译时就确定了,而动态代理的代理关系是在运行期确定的.静态代理实现简单,适合于代理类较少且确定的情况,而动态代理则给我们提供了更大的灵活性. J ...

  6. 代理模式之静态代理,JDK动态代理和cglib动态代理

    代理模式,顾名思义,就是通过代理去完成某些功能.比如,你需要购买火车票,不想跑那么远到火车站售票窗口买,可以去附近的火车票代售点买,或者到携程等第三方网站买.这个时候,我们就把火车站叫做目标对象或者委 ...

  7. 代理模式详解:静态代理、JDK动态代理与Cglib动态代理

    代理模式简介分类 概念 ​ 代理,是为了在不修改目标对象的基础上,增强目标方法的业务逻辑. ​ 客户类需要执行的是目标对象的目标方法,但是真正执行的是代理对象的代理方法,客户类对目标对象的访问是通过代 ...

  8. 静态代理和利用反射形成的动态代理(JDK动态代理)

    代理模式 代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问.在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用. 静态代理 1.新建 ...

  9. 何为代理?jdk动态代理与cglib代理、spring Aop代理原理浅析

    原创声明:本博客来源为本人原创作品,绝非他处摘取,转摘请联系博主 代理(proxy)的定义:为某对象提供代理服务,拥有操作代理对象的功能,在某些情况下,当客户不想或者不能直接引用另一个对象,而代理对象 ...

  10. 动态代理:JDK动态代理和CGLIB代理的区别

    代理模式:代理类和被代理类实现共同的接口(或继承),代理类中存有指向被代理类的索引,实际执行时通过调用代理类的方法.实际执行的是被代理类的方法. 而AOP,是通过动态代理实现的. 一.简单来说: JD ...

随机推荐

  1. H3C V.35接口线缆

  2. HMM(隐马尔科夫)用于中文分词

    隐马尔可夫模型(Hidden Markov Model,HMM)是用来描述一个含有隐含未知参数的马尔可夫过程. 本文阅读了2篇blog,理解其中的意思,附上自己的代码,共同学习. 一.理解隐马尔科夫 ...

  3. jq添加插入删除元素

    https://www.cnblogs.com/sandraryan/ append() - 在被选元素的结尾插入内容 <body> <div class="wrap&qu ...

  4. Codeforces Round #189 (Div. 1 + Div. 2)

    A. Magic Numbers 不能出现连续的3个4,以及1.4以外的数字. B. Ping-Pong (Easy Version) 暴力. C. Malek Dance Club 考虑\(x\)二 ...

  5. 下推栈实现(c++编程思想 p136)

    1 头文件Stack.h #ifndef STACK_H #define STACK_H struct Stack { struct Link { void* data; Link* next; vo ...

  6. linux预备知识

    我们正在接近去看一些实际的模块代码. 但是首先, 我们需要看一些需要出现在你的模块 源码文件中的东西. 内核是一个独特的环境, 它将它的要求强加于要和它接口的代码上. 大部分内核代码包含了许多数量的头 ...

  7. P1043 查找小于x的最大元素

    题目描述 现在告诉你一个长度为 \(n\) 的有序数组 \(a_1, a_2, ..., a_n\) ,以及 \(q\) 次询问,每次询问会给你一个数 \(x\) ,对于每次询问,你需要输出数组 \( ...

  8. P1035 台阶问题二

    题目描述 有 \(N\) 级的台阶,你一开始在底部,每次可以向上迈最多 \(K\) 级台阶(最少 \(1\) 级),问到达第 \(N\) 级台阶有多少种不同方式. 输入格式 两个正整数 \(N, K( ...

  9. linux获知当前时间

    内核代码能一直获取一个当前时间的表示, 通过查看 jifies 的值. 常常地, 这个值只代 表从最后一次启动以来的时间, 这个事实对驱动来说无关, 因为它的生命周期受限于系统 的 uptime. 如 ...

  10. 基于Springboot+Junit+Mockito做单元测试

    前言 前面的两篇文章讨论过< 为什么要写单元测试,何时写,写多细 >和<单元测试规范>,这篇文章介绍如何使用Springboot+Junit+Mockito做单元测试,案例选取 ...