Java核心技术点之动态代理

    本篇博文会从代理的概念出发,介绍Java中动态代理技术的使用,并进一步探索它的实现原理。由于个人水平有限,叙述中难免出现不清晰或是不准确的地方,希望大家可以指正,谢谢大家:)

一、概述

1. 什么是代理

    我们大家都知道微商代理,简单地说就是代替厂家卖商品,厂家“委托”代理为其销售商品。关于微商代理,首先我们从他们那里买东西时通常不知道背后的厂家究竟是谁,也就是说,“委托者”对我们来说是不可见的;其次,微商代理主要以朋友圈的人为目标客户,这就相当于为厂家做了一次对客户群体的“过滤”。我们把微商代理和厂家进一步抽象,前者可抽象为代理类,后者可抽象为委托类(被代理类)。通过使用代理,通常有两个优点,并且能够分别与我们提到的微商代理的两个特点对应起来:

  • 优点一:可以隐藏委托类的实现;
  • 优点二:可以实现客户与委托类间的解耦,在不修改委托类代码的情况下能够做一些额外的处理。

 

2. 静态代理

     若代理类在程序运行前就已经存在,那么这种代理方式被成为静态代理,这种情况下的代理类通常都是我们在Java代码中定义的。 通常情况下,静态代理中的代理类和委托类会实现同一接口或是派生自相同的父类。下面我们用Vendor类代表生产厂家,BusinessAgent类代表微商代理,来介绍下静态代理的简单实现,委托类和代理类都实现了Sell接口,Sell接口的定义如下:

public interface Sell {void sell();void ad();
}

 

    Vendor类的定义如下:

public class Vendor implements Sell {public void sell() {System.out.println("In sell method");}public void ad() {System,out.println("ad method")}
}

 

    代理类BusinessAgent的定义如下:

 1 public class BusinessAgent implements Sell {
 2     private Vendor mVendor;
 3   
 4     public BusinessAgent(Vendor vendor) {
 5         mVendor = vendor;
 6     }
 7    
 8     public void sell() { mVendor.sell(); }
 9     public void ad() { mVendor.ad(); }
10 }

    从BusinessAgent类的定义我们可以了解到,静态代理可以通过聚合来实现,让代理类持有一个委托类的引用即可。

    下面我们考虑一下这个需求:给Vendor类增加一个过滤功能,只卖货给大学生。通过静态代理,我们无需修改Vendor类的代码就可以实现,只需在BusinessAgent类中的sell方法中添加一个判断即可如下所示:

public class BusinessAgent implements Sell {...public void sell() {if (isCollegeStudent()) {vendor.sell();}}...
}

    这对应着我们上面提到的使用代理的第二个优点:可以实现客户与委托类间的解耦,在不修改委托类代码的情况下能够做一些额外的处理。静态代理的局限在于运行前必须编写好代理类,下面我们重点来介绍下运行时生成代理类的动态代理方式。

 

二、动态代理

1. 什么是动态代理

    代理类在程序运行时创建的代理方式被成为动态代理。也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。这么说比较抽象,下面我们结合一个实例来介绍一下动态代理的这个优势是怎么体现的。

    现在,假设我们要实现这样一个需求:在执行委托类中的方法之前输出“before”,在执行完毕后输出“after”。我们还是以上面例子中的Vendor类作为委托类,BusinessAgent类作为代理类来进行介绍。首先我们来使用静态代理来实现这一需求,相关代码如下:

public class BusinessAgent implements Sell {private Vendor mVendor;public BusinessAgent(Vendor vendor) {this.mVendor = vendor;}public void sell() {System.out.println("before");mVendor.sell();System.out.println("after");}public void ad() {System.out.println("before");mVendor.ad();System.out.println("after");}
}

     从以上代码中我们可以了解到,通过静态代理实现我们的需求需要我们在每个方法中都添加相应的逻辑,这里只存在两个方法所以工作量还不算大,假如Sell接口中包含上百个方法呢?这时候使用静态代理就会编写许多冗余代码。通过使用动态代理,我们可以做一个“统一指示”,从而对所有代理类的方法进行统一处理,而不用逐一修改每个方法。下面我们来具体介绍下如何使用动态代理方式实现我们的需求。

 

2. 使用动态代理

(1)InvocationHandler接口

    在使用动态代理时,我们需要定义一个位于代理类与委托类之间的中介类,这个中介类被要求实现InvocationHandler接口,这个接口的定义如下:

public interface InvocationHandler {Object invoke(Object proxy, Method method, Object[] args);
}

    从InvocationHandler这个名称我们就可以知道,实现了这个接口的中介类用做“调用处理器”。当我们调用代理类对象的方法时,这个“调用”会转送到invoke方法中,代理类对象作为proxy参数传入,参数method标识了我们具体调用的是代理类的哪个方法,args为这个方法的参数。这样一来,我们对代理类中的所有方法的调用都会变为对invoke的调用,这样我们可以在invoke方法中添加统一的处理逻辑(也可以根据method参数对不同的代理类方法做不同的处理)。因此我们只需在中介类的invoke方法实现中输出“before”,然后调用委托类的invoke方法,再输出“after”。下面我们来一步一步具体实现它。

 

(2)委托类的定义

    动态代理方式下,要求委托类必须实现某个接口,这里我们实现的是Sell接口。委托类Vendor类的定义如下:

public class Vendor implements Sell {public void sell() {System.out.println("In sell method");}public void ad() {System,out.println("ad method")}
}

 

 

(3)中介类

    上面我们提到过,中介类必须实现InvocationHandler接口,作为调用处理器”拦截“对代理类方法的调用。中介类的定义如下:

 1 public class DynamicProxy implements InvocationHandler {
 2     private Object obj; //obj为委托类对象;
 3     
 4     public DynamicProxy(Object obj) {
 5         this.obj = obj;
 6     }
 7 
 8     @Override
 9     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
10         System.out.println("before");
11         Object result = method.invoke(obj, args);
12         System.out.println("after");
13         return result;
14     }
15 }

     从以上代码中我们可以看到,中介类持有一个委托类对象引用,在invoke方法中调用了委托类对象的相应方法(第11行),看到这里是不是觉得似曾相识?通过聚合方式持有委托类对象引用,把外部对invoke的调用最终都转为对委托类对象的调用。这不就是我们上面介绍的静态代理的一种实现方式吗?实际上,中介类与委托类构成了静态代理关系,在这个关系中,中介类是代理类,委托类就是委托类;

代理类与中介类也构成一个静态代理关系,在这个关系中,中介类是委托类,代理类是代理类。也就是说,动态代理关系由两组静态代理关系组成,这就是动态代理的原理。下面我们来介绍一下如何”指示“以动态生成代理类。

 

(4)动态生成代理类

    动态生成代理类的相关代码如下:

public class Main {
    public static void main(String[] args) {//创建中介类实例DynamicProxy  inter = new DynamicProxy(new Vendor());//加上这句将会产生一个$Proxy0.class文件,这个文件即为动态生成的代理类文件System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true"); //获取代理类实例sellSell sell = (Sell)(Proxy.newProxyInstance(Sell.class.getClassLoader(),new Class[] {Sell.class}, inter));//通过代理类对象调用代理类方法,实际上会转到invoke方法调用
        sell.sell();sell.ad();}
}

    在以上代码中,我们调用Proxy类的newProxyInstance方法来获取一个代理类实例。这个代理类实现了我们指定的接口并且会把方法调用分发到指定的调用处理器。这个方法的声明如下:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
    方法的三个参数含义分别如下:
  • loader:定义了代理类的ClassLoder;
  • interfaces:代理类实现的接口列表
  • h:调用处理器,也就是我们上面定义的实现了InvocationHandler接口的类实例
    我们运行一下,看看我们的动态代理是否能正常工作。我这里运行后的输出为:        说明我们的动态代理确实奏效了。        上面我们已经简单提到过动态代理的原理,这里再简单的总结下:首先通过newProxyInstance方法获取代理类实例,而后我们便可以通过这个代理类实例调用代理类的方法,对代理类的方法的调用实际上都会调用中介类(调用处理器)的invoke方法,在invoke方法中我们调用委托类的相应方法,并且可以添加自己的处理逻辑。下面我们来看一下生成的代理类的代码究竟是怎样的。    

3. 动态代理类的源码分析

    通过运行Main,我们会得到一个名为“$Proxy”的class文件,这个文件即为动态生成的代理类,我们通过反编译来查看下这个代理类的源代码:

package com.sun.proxy;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;public final class $Proxy0 extends Proxy implements Sell {//这5个Method对象分别代表equals()、toString()、ad()、sell()、hashCode()方法private static Method m1;private static Method m2;private static Method m4;private static Method m3;private static Method m0;//构造方法接收一个InvocationHandler对象为参数,这个对象就是代理类的“直接委托类”(真正的委托类可以看做代理类的“间接委托类”)public $Proxy0(InvocationHandler var1) throws  {super(var1);}//对equals方法的调用实际上转为对super.h.invoke方法的调用,父类中的h即为我们在构造方法中传入的InvocationHandler对象,以下的toString()、sell()、ad()、hashCode()等方法同理public final boolean equals(Object var1) throws  {try {return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}public final String toString() throws  {try {return (String)super.h.invoke(this, m2, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final void ad() throws  {try {super.h.invoke(this, m4, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final void sell() throws  {try {super.h.invoke(this, m3, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final int hashCode() throws  {try {return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}//这里完成Method对象的初始化(通过反射在运行时获得Method对象)static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);m4 = Class.forName("Sell").getMethod("ad", new Class[0]);m3 = Class.forName("Sell").getMethod("sell", new Class[0]);m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}}
}

     我们可以看到,以上代码的逻辑十分简单,我们在注释中也做出了相关的说明。(以上代码中涉及到反射的使用,对于反射还不是很熟悉的小伙伴可以参考这里:Java核心技术点之反射)

     

    现在,我们已经了解了动态代理的使用,也搞清楚了它的实现原理,更进一步的话我们可以去了解动态代理类的生成过程,只需要去阅读newProxyInstance方法的源码即可,这个方法的逻辑也没有复杂的地方,这里就不展开了。大家可以参考这篇文章:公共技术点之动态代理

 

三、参考资料

1. Java Docs

2. 《深入理解Java虚拟机》

3. 公共技术点之动态代理

     

转载于:https://www.cnblogs.com/absfree/p/5392639.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/573708.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

pythonwebview自动化测试_GitHub - githubwzg/python-appium: 基于PageObject UI自动化测试框架,支持Android/iOS...

0910 UPDATE新增控件集参数化&#xff0c;相同测试步骤的Android/iOS可共用一份测试用例不同测试步骤的用例还需要单独写0904 UPDATE优化Android log及crsahinfo相关输出路径新增iOS crashreport解析新增内容&#xff1a;适配iOS提取android crash信息优化report(增加自动填充包…

linux中使用lftp上传下载文件

lftp是linux中一款ftp服务器相比windows中的ftp显得要复杂不少了&#xff0c;下面我来总结一下lftp文件上传&#xff0c;文件下载&#xff0c;及文件查找等等相关命令吧。 lftp连接的几种方法&#xff0c;最常用的是lftp namesite&#xff0c;这样可以不用明文输入密码。 1、lf…

java 数据库 事务 只读_不使用事务和使用只读事务的区别

转转转&#xff0c;&#xff0c;还需要具体验证(决定把readonly类型的事务完全去掉&#xff0c;以提高性能&#xff0c;这里有讨论&#xff1a;http://stackoverflow.com/questions/2562865/spring-transaction-readonly)。。。如果只提交一个查询&#xff0c;有必要用事务吗&a…

python搭建webapi_怎么搭建Web Api

1.通常我们有个web 让后可以有个web api 提供接口2.通常我们分别建两个项目 web api 依赖web的来进行访问(说到底是依赖是IIS)3.我们先建个SmallCode.Test.Web 再建一个SmallCode.Test.Api4.直接在SmallCode.Test.Web 引用 SamllCode.Test.Api 运行通过地址 /api/Order 无法访问…

shell入门之expr的使用

在expr中加减乘除的使用&#xff0c;脚本例如以下&#xff1a; #!/bin/sh #a test about expr v1expr 5 6 echo "$v1" echo expr 3 5 echo expr 6 / 2 echo expr 9 \* 5 echo expr 9 - 6 执行效果 转载于:https://www.cnblogs.com/bhlsheji/p/5401458.html

java中子线程与主线程通信_Android笔记(三十二) Android中线程之间的通信(四)主线程给子线程发送消息...

之前的例子都是我们在子线程(WorkerThread)当中处理并发送消息&#xff0c;然后在主线程(UI线程)中获取消息并修改UI&#xff0c;那么可以不可以在由主线程发送消息&#xff0c;子线程接收呢&#xff1f;我们按照之前的思路写一下代码&#xff1a;packagecn.lixyz.handlertest;…

java开源笔记软件下载_开发常用软件笔记 - ZhaoQian's Blog - OSCHINA - 中文开源技术交流社区...

notepad使用快捷键CtrlH打开“替换对话框”&#xff0c;在每行的开头添加内容。 勾选左下角的“正则表达式”选项 在“查找目标”里输入上尖号“^”&#xff0c;上尖号代表每行的开头 在“替换为”里输入“private String”。然后点"全部替换"按钮&#xff0c;这样每…

python示例apk_Python获取apk文件URL地址实例

工作中经常需要提取apk文件的特定URL地址&#xff0c;如是想到用Python脚本进行自动处理。需要用到的Python基础知识如下:os.walk()函数声明&#xff1a;os.walk(top,topdownTrue,οnerrοrNone)(1)参数top表示需要遍历的顶级目录的路径。(2)参数topdown的默认值是“True”表示…

使用静态工厂方法而不是构造器

注意&#xff1a;静态工厂方法不是设计模式中的工厂方法。 一个类向客户端提供静态工厂方法有如下好处&#xff1a; 有名称&#xff0c;不用根据参数类型和顺序区分重载方法&#xff0c;让代码更易读 是否每次调用都需要新对象是可控制的&#xff0c;对于不可修改的对象可以采取…

极光推送指定用户推送_App用户都睡着了?是时候用推送和活动唤醒一波了!

想要运营好一款App&#xff0c;引流、留存、促活三大环节必不可少。引流解决了用户来的问题&#xff0c;留存解决了用户留下来的问题&#xff0c;而促活解决的是让一部分新注册用户以及许久没有动静的老用户&#xff0c;在平台中再次活跃起来。今天&#xff0c;我们就来聊聊关于…

扫描sdcard文件(递归)

private void saoMiaoSdCard() {// TODO Auto-generated method stub// 判断是否挂载if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {// 获取sdcardFile sdcard Environment.getExternalStorageDirectory();// 创建集合对象list_file new A…

oracle awr报告生成_分享AWR报告的生成和简单分析方法

生成AWR报告方法&#xff1a;第一步&#xff1a;数据库压力测试卡开始时&#xff1a;生成第一个快照&#xff1a;Sql>exec dbms_workload_repository.create_snapshot();第二步&#xff1a;数据库压力测试结束时&#xff1a;生成第二个快照Sql>exec dbms_workload_reposi…

java date.from_java datefromat

DateFormat 中的格式一致 即可) java.text.SimpleDateFormat sdf new java.text.SimpleDateFormat("M/dd/yyyy hh:mm:ss a",java); java.......SimpleDateFormat常用法_计算机软件及应用_IT/计算机_专业资料。有关javaSInpleDateFormat类的常用操作 1. SimpleDateFo…

selenium打开Firefox、IE、Chrome浏览器【python】

selenium打开不同浏览器的脚本。 1.Firefox from selenium import webdriverdriverwebdriver.Firefox() driver.get("http://www.baidu.com") 这里要注意打开的域名一定要加前http:// 否则会报错&#xff1a;selenium.common.exceptions.WebDriverException: Messag…

安卓开发 登录用户信息缓存_在Linux上使用finger命令查询登录用户信息

请关注本头条号&#xff0c;每天坚持更新原创干货技术文章。如需学习视频&#xff0c;请在微信搜索公众号“智传网优”直接开始自助视频学习1. 前言本文主要讲解finger命令的作用和日常使用案例。finger命令是一个用户信息查询命令&#xff0c;它给出了所有登录用户的详细信息。…

java后端传object给js_【JSON】JSON在前端和后端传递

前后台最最传统的交互方式就是表单交互&#xff0c;然后用request.setAttribute方法设置结果&#xff0c;渲染jsp&#xff0c;然而随着前台界面的复杂程度的提高&#xff0c;或者是使用了某些前端框架(sigmagrid)越来越多的界面会使用异步方式提交数据。那么这个过程大致是什么…

POJ 1065 Wooden Sticks

http://blog.csdn.net/acdreamers/article/details/7626671 学习一下Dilworth定理 推荐一篇写得很好博客 要求最少的覆盖&#xff0c;按照Dilworth定理 最少链划分 最长反链长度 所以最少系统 最长导弹高度上升序列长度。 之前写的LIS模板不对。。。。。。 1 #include<cst…

python 对象_Python小课堂面向对象

Python3 面向对象Python从设计之初就已经是一门面向对象的语言&#xff0c;正因为如此&#xff0c;在Python中创建一个类和对象是很容易的。本章节我们将详细介绍Python的面向对象编程。如果你以前没有接触过面向对象的编程语言&#xff0c;那你可能需要先了解一些面向对象语言…

使用jemalloc优化java_Jemalloc优化MySQL和Nginx

Redis 2.4版本之后&#xff0c;默认使用jemalloc来做内存管理&#xff1b;tengine也整合jemalloc。jemalloc从各方评测的结果可见与google tcmalloc都不相伯仲&#xff0c;皆为内存管理器领域最高水平。如下图&#xff1a;最左边的就是glibc的malloc&#xff0c;最右边的就是je…

二维码的生成

我们目前用的是谷歌的zxing来生成二维码&#xff1b;下面呢我分别为大家介绍一下简单的二维码&#xff0c;中间有log的二维码&#xff0c;和彩色二维码&#xff1b; 需要两个权限&#xff1b; <uses-permission android:name"android.permission.CAMERA"/> <…