JAVA动态代理

Java JDK动态代理Proxy类的原理是什么? - 知乎(重排版)

本文转载自知乎问题Java JDK动态代理Proxy类的原理是什么?下用户@胖君回答

  1. 什么是代理?

先从代理开始讲。
代理这种设计模式其实很好理解,基本就是最简单的一个 “组合”。比如说下面这个例子,我们有 A 这个类,本来可以直接调用 A 类的 foo() 方法。但代理模式就非要把 A 类当成 B 类的一个成员字段放在 B 类里面。然后因为 A 类和 B 类都实现了 Interface 这个接口,所以 B 类里也有 foo()方法。而且 B 类里的 foo()方法就是傻瓜式的调用 A 类的 foo()方法。

interface Interface{public void foo();}
/**委托类*/
class A implements Interface{
    public void foo(){System.out.println("Method a of class A!");}
}
/**代理类*/
class B implements Interface{
    public A a=new A();
    public void foo(){a.foo();}
}
/**用户*/
class Consumer{
    public static void consum(Interface i){
        i.foo();
    }
}
/**测试*/
public class TestProxy{
    public static void main(String[] args){
        Interface i=new B();
        Consumer.consum(i);
    }
}
  1. 代理有什么好处?

乍一看,代理方法完全是多此一举,B 类的行为和 A 类完全一样,没有任何意义。但实际上,B 类的 foo() 方法在直接调用 A 类 foo() 方法之前和之后,可以做很多事情。举个例子,如果在 B 类里加个静态计数字段 count,然后每次调用 foo() 方法之后都计一下数,就可以监控 A 类 foo() 方法被调用的次数。

class B implements Interface{
    public static long count=0;
    public A a=new A();
    public void foo(){a.foo();count++;}
}

所以代理类里能非常好地控制,辅助被代理类,甚至是增加额外的功能。而且一般来说代理类 B 和被代理 A 都会实现同样的接口,这样对用户端(就是上面例子里的 Consumer 类)的代码没有任何影响,耦合很低。

  1. 什么是动态代理?

上面例子里在 A 类外面套一个 B 类好像很简单,但实际到了工程级别的代码,需要代理的就不止一个两个了。每个代理类都手动写会累死,而且很枯燥,是没有技术含量的重复。所以这个时候 Java 的反射功能就立功了。代理类 B 是可以完全用反射动态生成的。
怎么动态生成 B 类呢?下面有个例子,通过反射动态加载 B 类,然后调用 B 类的 foo() 方法。

public class TestDynamicProxy{
    public static void main(String[] args){
        try{
            Class<?> refB=B.class;
            Method refFoo=refB.getDeclaredMethod("foo");
            Object refObj=refB.newInstance();
            refFoo.invoke(refObj);
        }catch(Exception e){
            System.out.println(e);
        }
    }
}
  1. B.class 获得了 B 类的 Class 对象。
  2. Class#getDeclaredMethod() 方法根据方法的名称"foo"获得了 foo() 方法的 Method 对象。
  3. 最后调用这个 Method 对象的 invoke() 来执行这个方法。

但这个是动态生成吗?明显不是!上面这个方法只是在 B 类已经写好了的情况下动态调用 B 类。其实并没有动态生成 B 类,根本不能叫动态生成。真的要完全凭空用反射 “写” 一个 B 类的字节码文件出来然后加载它,其实要复杂地多,这就是为什么需要 Proxy 工具来替我们做的原因。

4.Proxy 类怎么实现动态代理?

Proxy 类里能替我们生成一个代理类对象的,就是 newProxyInstance() 方法。现在回过头看它的三个参数,

newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
  1. 第一个 ClassLoader 是为了生成 B 类的 Class 对象。作用是根据一组类的字节码 byte[] 直接生成这个类的 Class 对象。
  2. 第二个参数是由委托类实现的接口的 Class 对象数组。主要是包含了最重要的代理类需要实现的接口方法的信息。
  3. 最后一个最重要的就是一个实现了 InvocationHandler 接口的对象。InvocationHandler 接口在 java.lang.reflect 包里。最主要的就是定义了 invoke( ) 方法。它就是假设在已经动态生成了最后的 proxy 代理对象,以及所有接口定义的方法 Method 对象以及方法的参数的情况下,定义我们要怎么调用这些方法的地方。

这三个参数的分工用大白话讲就是:第一参数 ClassLoader 和第二参数接口的 Class 对象是用来动态生成委托类的包括类名,方法名,继承关系在内的一个空壳。用 B 类的例子演示就像下面这样,

class $ProxyN implements Interface{
    public void foo(){
        //do something...
    }
}

只有接口定义的方法名,没有实际操作。实际的操作是交给第三个参数 InvocationHandler 的 invoke() 方法来执行。

所以最主要的业务逻辑应该是在第三个参数 InvocationHandler 的 invoke() 方法里定义。下面代码,是根据之前 A 类 B 类的例子用 Proxy 类实现动态代理的 Demo。代码里原先的 B 类被擦掉了,完全由 Proxy 类动态生成。

interface Interface{public void foo();}

class A implements Interface{
    public void foo(){System.out.println("Method a of class A!");}
}

/*    //这是Proxy要动态生成的B类。
class B implements Interface{
    public void foo(){a.foo();}
    public A a=new A();
}
 */

class Consumer{
    public static void consum(Interface i){
        i.foo();
    }
}

class DynamicProxyHandler implements InvocationHandler {
    private Object proxied;
    public DynamicProxyHandler(Object proxied) {
        this.proxied = proxied;
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try{
            return method.invoke(proxied, args);
        }catch(Exception e){
            System.out.println(e);
            return null;
        }
    }
}

public class TestDynamicProxy{
    public static void main(String[] args){
        A a=new A();
    //直接把A类对象a当参数传进去,就动态产生一个代理类对象
        Interface proxy = (Interface)Proxy.newProxyInstance(Interface.class.getClassLoader(), new Class<?>[]{Interface.class }, new DynamicProxyHandler(a));
        Consumer.consum(proxy);
    }
}

在实现了 InvocationHandler 接口的 DynamicProxyHandler 类里有一个被代理类的对象 proxied 作为成员字段。在获得了参数传进来的代理类对象和 Method 对象之后,直接用 Method#invoke(Object o) 方法,调用了代理类对象的方法。第一个参数 ClassLoader 直接用的是 Interface 接口的类加载器 (Interface.class.getClassLoader())。第二参数就是 Interface 接口的 Class 对象。

然后,剩下的事就交给 Proxy 来完成。关键的难点在于怎么根据给定的 ClassLoader 和接口的方法信息动态生成一个所谓 “空壳”。其实 newProxyInstance() 方法隐藏了非常多其他的复杂性,比如说访问权限检查,包路径设置,安全检查等等琐碎的事,但这里先不说。只说核心步骤。

下面截取 newProxyInstance() 方法源码里比较重要的一段贴上来,看看底层是怎么实现的。

/*
 * Choose a name for the proxy class to generate.
 */
long num;
synchronized (nextUniqueNumberLock) {
    num = nextUniqueNumber++;
}
String proxyName = proxyPkg + proxyClassNamePrefix + num;

/*
 * Generate the specified proxy class.
 */
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
    proxyName, interfaces);
try {
    proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
     throw new IllegalArgumentException(e.toString());
}

可以看到,proxyName 是动态生成的代理类的名称,一般是 ·$ProxyN 的格式。N 代表代理是 N 次生成动态代理。

然后见证奇迹的时刻到了,关键的核心步骤有两个:

  1. ProxyGenerator.generateProxyClass() 方法生成了类加载器要用到的字节码。它需要的参数只有两个,1)类名,2)实现的接口的 Class 对象。然后它就神奇地生成了一堆字节码 byte[],基本就是一个凭空造出来的编译好的. class 文件。这个方法来自神秘的 sun.misc 包。也查不到源码。
  2. 最后神秘的字节码和加载器,以及类名一起被交到另一个 native 方法 defineClass0( ) 里,由它生成代理类的 Class 对象。至于 native 方法怎么实现,源码里也查不到。

最后再总结一下,使用 Proxy 的三步,

  1. 在第三个参数,实现 InvocationHandler 接口的对象的 invoke() 方法里写业务逻辑。
  2. 第二个参数是代理实现接口的 Class 对象
  3. 第一个参数是一个 ClassLoader。一般直接用调用类的加载器

如果实在想知道鬼畜的 ProxyGenerator.generateProxyClass()的内部原理,就看谁能把人肉源码机 R 大 @RednaxelaFX 召唤出来了,哈哈:v

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
毕业设计,基于SpringBoot+Vue+MySQL开发的课程作业管理系,源码+数据库+开题报告+论文答辩+毕业论文+视频演示 随着科学技术的飞速发展,社会的方方面面、各行各业都在努力与现代的先进技术接轨,通过科技手段来提高自身的优势,课程作业管理系统当然也不能排除在外。课程作业管理系统是以实际运用为开发背景,运用软件工程原理和开发方法,采用springboot框架构建的一个管理系统。整个开发过程首先对软件系统进行需求分析,得出系统的主要功能。接着对系统进行总体设计和详细设计。总体设计主要包括系统功能设计、系统总体结构设计、系统数据结构设计和系统安全设计等;详细设计主要包括系统数据库访问的实现,主要功能模块的具体实现,模块实现关键代码等。最后对系统进行功能测试,并对测试结果进行分析总结,得出系统中存在的不足及需要改进的地方,为以后的系统维护提供了方便,同时也为今后开发类似系统提供了借鉴和帮助。这种个性化的网上管理系统特别注重交互协调与管理的相互配合,激发了管理人员的创造性与主动性,对课程作业管理系统而言非常有利。 本课程作业管理系统采用的数据库是Mysql,使用springboot框架开发。在设计过程中,充分保证了系统代码的良好可读性、实用性、易扩展性、通用性、便于后期维护、操作方便以及页面简洁等特点。 1、关于课程作业管理系统的基本要求: (1)功能要求:可以管理首页、个人中心、公告信息管理、班级管理、学生管理、教师管理、课程类型管理、课程信息管理、学生选课管理、作业布置管理、作业提交管理、作业评分管理、课程评价管理、课程资源管理等功能模块。 (2)性能:在不同操作系统上均能无差错实现在不同类型的用户登入相应界面后能不出差错、方便地进行预期操作。 (3)安全与保密要求:用户都必须通过注册、登录才能进入系统,并且用户的权限也需要根据用户的类型进行限定。 (4)环境要求:支持多种平台,可在Windows系列、Vista系统等多种操作系统下使用。 关键词:课程作业管理系统,springboot框架; Mysql数据库 Java技术
毕业设计,基于SpringBoot+Vue+MySQL开发的旅游网站,源码+数据库+开题报告+论文答辩+毕业论文+视频演示 随着科学技术的飞速发展,各行各业都在努力与现代先进技术接轨,通过科技手段提高自身的优势,旅游网站当然也不能排除在外,随着旅游网站的不断成熟,它彻底改变了过去传统的旅游网站方式,不仅使旅游管理难度变低了,还提升了旅游网站的灵活性。这种个性化的旅游网站特别注重交互协调经营与管理的相互配合,激发了管理人员的创造性与主动性,对旅游管理的管理而言非常有利。 本文首先分析了旅游网站的发展背景和意义,简要阐述了旅游网站系统开发的主要内容和优势,然后简要介绍了国内外旅游网站系统的研究和应用现状,并对系统开发技术,系统分析和总体设计,实现详细功能等。 本旅游网站系统采用的数据库是MySQL,使用Java技术开发,在设计过程中,充分保证了系统代码的良好可读性、实用性、易扩展性、通用性、便于后期维护、操作方便以及页面简洁等特点。 关键词:旅游网站;Java;SpringBoot; Vue; MySQL 数据库 旅游网站主要功能如下: 1.用户管理:注册、登录、退出、修改密码; 2.分类显示:显示旅游路线的分类; 3.旅游路线显示:按分类查询旅游路线、通过关键字搜索旅游路线、查看旅游路线的详细信息; 4.购物车管理:向购物车中添加旅游路线、修改购物车中旅游路线数量、删除购物车中旅游路线、我的购物车; 5.订单管理:通过购物车中生成订单、查看我的订单、查看某个订单的详细、订单支付、取消未付款订单。 6.首页:提供一个网站首页,该网站用户的登录,注册,所有旅游路线的一级分类,热门旅游路线和最新旅游路线的展示等。 7.旅游路线管理:旅游路线的上架、下架。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值