java的动态代理机制.md

java核心技术 专栏收录该内容
9 篇文章 1 订阅

1.楔子

在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的我们的功能,我们更需要学习的是其底层是怎么样的一个原理,而AOP的原理就是java的动态代理机制,所以本篇随笔就是对java的动态机制进行一个回顾。

2.简介

代理模式:给某一个对象提供一个代理,并由代理对象来控制对真实对象的访问。

在java的动态代理机制中,有两个重要的类或接口,一个是InvocationHandler(Interface)、另一个则是Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。首先我们先来看看java的API帮助文档是怎么样对这两个类进行描述的:

2.1 InvocationHandle接口

每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的invoke方法来进行调用。我们来看看InvocationHandler这个接口的唯一一个方法 invoke 方法:

public Object invoke(Object proxy, Method method, Object[] args)   throws Throwable;
// proxy:  指代我们所代理的那个真实对象
// method:  指代的是我们所要调用真实对象的某个方法的Method对象
// args:  指代的是调用真实对象某个方法时接受的参数

在这里插入图片描述

2.2 Proxy类

Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance这个方法:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
// loader:一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
// interfaces:一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,
// 如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
// h:一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上

2.3分类

根据字节码的创建时机来分类,可以分为静态代理动态代理

  • 所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和真实主题角色的关系在运行前就确定了。
  • 而动态代理的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以在运行前并不存在代理类的字节码文件

2.4 为什么类可以动态的生成?

这就涉及到Java虚拟机的类加载机制了,Java虚拟机类加载过程主要分为五个阶段:加载、验证、准备、解析、初始化。其中加载阶段需要完成以下3件事情:

  • 通过一个类的全限定名来获取定义此类的二进制字节流
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  • 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据访问入口

由于虚拟机规范对这3点要求并不具体,所以实际的实现是非常灵活的,关于第1点,获取类的二进制字节流(class字节码)就有很多途径:

  • 从ZIP包获取,这是JAR、EAR、WAR等格式的基础
  • 从网络中获取,典型的应用是 Applet
  • 运行时计算生成,这种场景使用最多的是动态代理技术,在 java.lang.reflect.Proxy 类中,就是用了 ProxyGenerator.generateProxyClass 来为特定接口生成形式为 *$Proxy 的代理类的二进制字节流
  • 由其它文件生成,典型应用是JSP,即由JSP文件生成对应的Class类
  • 从数据库中获取等等

所以,动态代理就是想办法,根据接口或目标对象,计算出代理类的字节码,然后再加载到JVM中使用。

2.5常见的字节码操作类库:

  • Apache BCEL:是Java classworking广泛使用的一种框架,它可以深入到JVM汇编语言进行类操作的细节。
  • ASM:是一个Java字节码操作框架。它可以用于直接以二进制形式动态生成stub根类或其他代理类,或者在加载时动态修改类。
  • CGLIB(Code Generation Library):是一个功能强大,高性能和高质量的代码生成库,用于扩展JAVA类并在运行时实现接口。
  • Javassist:是Java的加载时反射系统,它是一个用于在Java中编辑字节码的类库; 它使Java程序能够在运行时定义新类,并在JVM加载之前修改类文件。

2.6实现动态代理的思考方向

为了让生成的代理类与目标对象(真实主题角色)保持一致性,从现在开始将介绍以下两种最常见的方式:

  • 通过实现接口的方式 -> JDK动态代理
  • 通过继承类的方式 -> CGLIB动态代理
    注:使用ASM对使用者要求比较高,使用Javassist会比较麻烦

3.实例

代理模式示例如下:
在这里插入图片描述

首先我们定义了一个Subject类型的接口,为其声明了两个方法:

public interface Subject {
    void rent();
    void hello(String str);
}

接着,定义了一个类来实现这个接口,这个类就是我们的真实对象,RealSubject类:

public class RealSubject implements Subject {
    @Override
    public void rent() {
        System.out.println("I want to rent my house");
    }
    @Override
    public void hello(String str) {
        System.out.println("hello: " + str);
    }
}

下一步,我们就要定义一个动态代理类了,前面说个,每一个动态代理类都必须要实现 InvocationHandler 这个接口,因此我们这个动态代理类也不例外:

public class DynamicProxy implements InvocationHandler {
    // 这个就是我们要代理的真实对象
    private Object subject;

    //  构造方法,给我们要代理的真实对象赋初值
    public DynamicProxy(Object subject) {
        this.subject = subject;
    }

    @Override
    public Object invoke(Object object, Method method, Object[] args)
            throws Throwable {
        // 在代理真实对象前我们可以添加一些自己的操作
        System.out.println("before rent house");
        System.out.println("Method:" + method);
        // 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
        method.invoke(subject, args);
        // 在代理真实对象后我们也可以添加一些自己的操作
        System.out.println("after rent house");
        return null;
    }
}

最后,来看看我们的Client类:

public class Client {
    public static void main(String[] args) {
        // 我们要代理的真实对象
        Subject realSubject = new RealSubject();

        // 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
        InvocationHandler handler = new DynamicProxy(realSubject);

        /*
         * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
         * 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
         * 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
         * 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
         */
        Subject subject = (Subject) Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
                .getClass().getInterfaces(), handler);

        System.out.println(subject.getClass().getName());
        subject.rent();
        subject.hello("world");
    }
}

输出结果如下:
在这里插入图片描述

  • Why:System.out.println(subject.getClass().getName())返回的这个代理对象的类名 $Proxy0
    原因就是在newProxyInstance这个方法的第二个参数上,我们给这个代理对象提供了一组什么接口,那么我这个代理对象就会实现了这组接口,这个时候我们当然可以将这个代理对象强制类型转化为这组接口中的任意一个,因为这里的接口是Subject类型,所以就可以将其转化为Subject类型了。
    同时我们一定要记住,通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号。

  • 当我通过代理对象来调用方法的时候,起实际就是委托由其关联到的handler 对象的invoke方法中来调用,并不是自己来真实调用,而是通过代理的方式来调用的。这就是我们的java动态代理机制

4.参考文档

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

Spring4GWT GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解依赖注入和RPC机制Java扫雷游戏 JVMine JVMine用Applets开发扫雷游戏,可在线玩。 public class JVMine extends java.applet.Applet 简单实现!~ 网页表格组件 GWT Advanced Table GWT Advanced Table 是一个基于 GWT 框架网页表格组件,可实现分页数据显示、数据排序和过滤等功能! Google Tag Library 该标记库和 Google 有关。使用该标记库,利用 Google 为你网站提供网站查询,并且可以直接在你网页里面显示搜查结果。 github-java-api github-java-api 是 Github 网站 API Java 语言版本。 java缓存工具 SimpleCache SimpleCache 是一个简单易用java缓存工具,用来简化缓存代码编写,让你摆脱单调乏味重复工作!1. 完全透明缓存支持,对业务代码零侵入 2. 支持使用Redis和Memcached作为后端缓存。3. 支持缓存数据分区规则定义 4. 使用redis作缓存时,支持list类型高级数据结构,更适合论坛帖子列表这种类型数据 5. 支持混合使用redis缓存和memcached缓存。可以将列表数据缓存到redis中,其他kv结构数据继续缓存到memcached 6. 支持redis主从集群,可以做读写分离。缓存读取自redisslave节点,写入到redismaster节点。 Java对象SQL接口 JoSQL JoSQL(SQLforJavaObjects)为Java开发者提供运用SQL语句来操作Java对象集能力.利用JoSQL可以像操作数据库中数据一样对任何Java对象集进行查询,排序,分组。 搜索自动提示 Autotips AutoTips是为解决应用系统对于【自动提示】需要(如:Google搜索), 而开发架构无关公共控件, 以满足该类需求可以通过快速配置来开发。AutoTips基于搜索引擎Apache Lucene实现。AutoTips提供统一UI。 WAP浏览器 j2wap j2wap 是一个基于JavaWAP浏览器,目前处于BETA测试阶段。它支持WAP 1.2规范,除了WTLS 和WBMP。 Java注册表操作类 jared jared是一个用来操作Windows注册表 Java 类库,你可以用来对注册表信息进行读写。 GIF动画制作工具 GiftedMotion GiftedMotion是一个很小,免费而且易于使用图像互换格式动画是能够设计一个有趣动画了一系列数字图像。使用简便和直截了当,用户只需要加载图片和调整帧您想要,如位置,时间显示和处理方法前帧。 JavaPList类库 Blister Blister是一个用于操作苹果二进制PList文件格式Java开源类库(可用于发送数据给iOS应用程序)。 重复文件检查工具 FindDup.tar FindDup 是一个简单易用工具,用来检查计算机上重复文件。 OpenIDJava客户端 JOpenID JOpenID是一个轻量级OpenID 2.0 Java客户端,仅50KB+(含源代码),允许任何Web网站通过OpenID支持用户直接登录而无需注册,例如Google Account或Yahoo Account。 JActor文件持久化组件 JFile JFile 是 JActor 文件持久化组件,以及一个高吞吐量可靠事务日志组件。 Google地图JSP标签库 利用Google:maps JSP标签库就能够在你Web站点上实现GoogleMaps所有功能而且不需要javascript或AJAX编程。它还能够与JSTL相结合生成数据库驱动动态Maps。 OAuth 实现框架 Agorava Agorava 是一个实现了 OAuth 1.0a 和 OAuth 2.0 框架,提供了简单方式通过社交媒体进行身份认证功能。 EclipseJavaScript插件 JSEditor JSEditor 是 Eclipse 下编辑 JavaScript 源码插件,提供语法高亮以及一些通用面向对象方法。 Java数据库连接池 BoneCP BoneCP 是一个高性能开源java数据库连接池实现库。它设计初衷就是为了提高数据库连接池性能,根据某些测试数据发现,BoneCP是最快连接池。BoneCP很小,只有四十几K
相关推荐
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值