热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Java中的静态代理、通用动态代理类以及原理剖析

Java中的静态代理、通用动态代理类的实现以及动态

代理模式和静态代理

在开发中,代理模式是常用的模式之一,一般来说我们使用的代理模式基本上都是静态代理,实现模式大致如下 : 

,

我们以网络代理为例,简单演示一下静态代理的实现 : 

// 网络接口
interface Network {
	public void surfTheInternet();

	public void gotoFacebook();
}

// 普通网络
class CommonNetwork implements Network {

	@Override
	public void surfTheInternet() {
		System.out.println("直接上网,随便看点什么...");
	}

	@Override
	public void gotoFacebook() {
		System.out.println("上facebook,被墙了,没法弄啊!!!");
	}

}

// 网络代理
class NetworkProxy implements Network {
	@Override
	public void surfTheInternet() {
		System.out.println("代理上网");
	}

	@Override
	public void gotoFacebook() {
		System.out.println("上facebook, 即使被墙了,使用网络代理也能上!!!");
	}
}
main函数 : 

	public static void main(String[] args) {
		Network myNetwork = new CommonNetwork();
		myNetwork.surfTheInternet();
		myNetwork.gotoFacebook();
		
		myNetwork = new NetworkProxy() ;
		myNetwork.gotoFacebook();
	}
输出 : 

直接上网,随便看点什么...
上facebook,被墙了,没法弄啊!!!
上facebook, 即使被墙了,使用网络代理也能上!!!
总之,代理对象就是把被代理对象包装一层,在其内部做一些额外的工作,比如用户需要上facebook, 而普通网络无法直接访问,网络代理帮助用户先翻墙,然后再访问facebook。这就是代理的作用了。

动态代理

     上面的网络代理示例中,一个代理只能代理一种类型,而且是在编译器就已经确定被代理的对象。而动态代理是在运行时,通过反射机制实现动态代理,并且能够代理各种类型的对象。JDK 5中引入的动态代理机制,允许开发人员在运行时刻动态的创建出代理类及其对象。在运行时刻,可以动态创建出一个实现了多个接口的代理类。每个代理类的对象都会关联一个表示内部处理逻辑的InvocationHandler接 口的实现。当使用者调用了代理对象所代理的接口中的方法的时候,这个调用的信息会被传递给InvocationHandler的invoke方法。在 invoke方法的参数中可以获取到代理对象、方法对应的Method对象和调用的实际参数。invoke方法的返回值被返回给使用者,这种做法实际上相当于对方法调用进行了拦截,这样我们就可以在调用invoke的前后做自己想做的事。熟悉AOP的人对这种使用模式应该不陌生。但是这种方式不需要依赖AspectJ等AOP框架。下面我们看一个示例 : 

新增一个被代理类 :

/**
 * 社会化组件, 带有授权、分享功能
 * @author mrsimple
 *
 */
public interface Socialize {
	// 授权
	public void doAothorize();
	// 分享
	public void share();
}

public class SocializeImpl implements Socialize {

	@Override
	public void doAothorize() {
		System.out.println("doAothorize");
	}

	@Override
	public void share() {
		System.out.println("share");
	}

}
 

通用动态代理类:

/**
 * 动态代理通用类, 实现InvocationHandler接口。
 * 
 * @author mrsimple
 *
 */
public class CommonProxy implements InvocationHandler {

	/**
	 * 目标对象
	 */
	private Object mTarget;

	/**
	 * 方法调用日志
	 */
	private List mHistories = new ArrayList();

	/**
	 * 注入被代理的对象
	 * @param obj
	 */
	private CommonProxy(Object obj) {
		mTarget = obj;
	}

	/**
	 * 执行真正代码之前做的事
	 * @param md
	 * @param args
	 */
	private void before(Method md, Object[] args) {
		System.out.println("** 执行" + md.getName() + "函数之前, 记录到日志系统 **");
	}

	/*
	 * 调用任何函数都会以invoke函数为入口, proxy参数是被代理的真实对象,method为被调用的函数,arg为参数
	 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) {

		// before
		before(method, args);
		// 将调用记录保存起来
		mHistories.add(method);
		try {
			// 执行真正的函数调用
			return method.invoke(mTarget, args);
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 通过代理创建对象, 返回的必须是接口类型
	 * 
	 * @param obj
	 *            目标接口类型
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static  T createProxy(T obj) {

		return (T) Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
				.getClass().getInterfaces(), new CommonProxy(obj));

	}

	/**
	 * 
	 * @return
	 */
	public List getHistories() {
		return mHistories;
	}

}
上面就是一个通用的动态代理类。创建代理对象的工厂方法createProxy中的Proxy类是至关重要的,参数也有点不好理解,我们来分析一下。

Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
				.getClass().getInterfaces(), new CommonProxy(obj));
通过Proxy.newProxyInstance函数来创建代理对象,我们看看这个函数的声明(去掉了一些相关说明): 

    /**
     * Returns an instance of a proxy class for the specified interfaces
     * that dispatches method invocations to the specified invocation
     * handler.  This method is equivalent to:
     *
     * @param   loader the class loader to define the proxy class
     * @param   interfaces the list of interfaces for the proxy class
     *          to implement
     * @param   h the invocation handler to dispatch method invocations to
     * @return  a proxy instance with the specified invocation handler of a
     *          proxy class that is defined by the specified class loader
     *          and that implements the specified interfaces
     */
    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class[] interfaces,
                                          InvocationHandler h)

可以看到,第一个参数是被代理对象的ClassLoader, 参数2是被代理对象所有接口列表的Class数组,参数三为InvocationHandler对象,就是实现了InvocationHandler接口的类,对应上面的CommonProxy类型。看我们的实现 : 

	public static  T createProxy(T obj) {

		return (T) Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
				.getClass().getInterfaces(), new CommonProxy(obj));

	}
被代理对象obj通过参数传递进来,我们通过obj.getClass().getClassLoader()获取ClassLoader对象,然后通过obj.getClass().getInterfaces()获取它实现的所有接口,然后将obj包装到实现了InvocationHandler接口的CommonProxy对象中。通过newProxyInstance函数我们就获得了一个动态代理对象。

下面看看这个动态代理类的使用:

	public static void main(String[] args) {
		// 获得对象
		Socialize mCOntroller= CommonProxy
				.createProxy(new SocializeImpl());
		// 调用方法
		mController.doAothorize();
		mController.share();

		// 获取动态代理对象,然后输出调用历史记录
		CommonProxy proxy = (CommonProxy) Proxy
				.getInvocationHandler(mController);
		for (Method md : proxy.getHistories()) {
			System.out.println("调用了: " + md.getName());
		}
		
		// 通用的动态代理类, 返回的是接口类型
		Network network = CommonProxy.createProxy(new CommonNetwork());
		network.surfTheInternet();
	}
输出 : 

** 执行doAothorize函数之前, 记录到日志系统 **
doAothorize
** 执行share函数之前, 记录到日志系统 **
share
调用了: doAothorize
调用了: share
** 执行surfTheInternet函数之前, 记录到日志系统 **
直接上网,随便看点什么...

可以看到,我们可以通过CommonProxy代理不同类型的对象,如果我们把对外的接口都通过动态代理来实现,那么所有的函数调用最终都会经过invoke函数的转发,因此我们就可以在这里做一些操作,比如日志系统、事务、拦截器、权限控制等。这也就是AOP(面向切面编程)的基本原理吧。

动态代理原理剖析

下面我们来简单的分析一下动态代理的实现原理,主要的类是Proxy以及它的newProxyInstance函数。其实动态代理的原理大致是这样的,获取被代理对象的所有接口,因此可以通过反射获取到被代理对象所有的函数。JVM内部会构建一个继承自Proxy类,且实现了被代理对象所有接口的$Proxy+数字(例如$Proxy1)的类,这些工作都是通过Proxy.newProxyInstance函数实现,并且返回该$ProxyX类的对象。调用$ProxyX类对象的相关函数时,它会将其转发到InvocationHandler实现类中的invoke函数,因此我们可以在此进行其他额外的操作。下面,我们给main函数加上一些代码,如下 : 
	public static void main(String[] args) {
		
		// 通用的动态代理类, 返回的是接口类型
		Network network = CommonProxy.createProxy(new CommonNetwork());
		network.surfTheInternet();
		
		// 输出network对象的类名
		System.out.println(network.getClass().getName());
		// 输出network所属类的所有函数
		for (Method md : network.getClass().getDeclaredMethods()) {
			System.out.println("函数 : " + md.getName());
		}
		
		// 输出network所属类的所有函数
		for (Class cls : network.getClass().getInterfaces()) {
			System.out.println("实现的接口 : " + cls.getName());
		}
		
		// 输出network所属类的父类
		System.out.println(network.getClass().getGenericSuperclass());
		
	}
输出 : 
直接上网,随便看点什么...
com.thingking.in.java.proxy.$Proxy1
函数 : equals
函数 : toString
函数 : hashCode
函数 : surfTheInternet
函数 : gotoFacebook
实现的接口 : com.thingking.in.java.proxy.Network
class java.lang.reflect.Proxy
可以看到network所属的类是com.thingking.in.java.proxy.$Proxy1,其中.$Proxy之前是我的demo的报名,$Proxy1是network的类名。然后这个类实现的函数有equals, toString, hashCode, surfTheInternet, gotoFacebook这几个函数,前三个都是Object类的方法,后两个是Network接口的方法。实现的接口是Network,其父类是jdk中的java.lang.reflect.Proxy。输出的结果正好验证了我们上面所说的。
下面是Proxy.newProxyInstance方法的简单模拟实现 ( 实际情况当然没有那么简单 ) : 

[java] view plaincopy,,
  1. import java.io.File;  
  2. import java.io.FileWriter;  
  3. import java.lang.reflect.Constructor;  
  4. import java.lang.reflect.Method;  
  5. import java.net.URL;  
  6. import java.net.URLClassLoader;  
  7. import javax.tools.JavaCompiler;  
  8. import javax.tools.StandardJavaFileManager;  
  9. import javax.tools.ToolProvider;  
  10. import javax.tools.JavaCompiler.CompilationTask;  
  11.   
  12. public class Proxy {  
  13.     public static Object newProxyInstance(ClassLoader loader,Class infce, InvocationHandler h) throws Exception { //JDK6 Complier API, CGLib, ASM  
  14.         String methodStr = "";  
  15.         String rt = "\r\n";  
  16.         //利用反射,获得infce接口中方法,本例中就是获得Moveable接口的方法move  
  17.         Method[] methods = infce.getMethods();  
  18.         //拼接infce中所有方法字符串,用来重写infce中的方法,本例中拼出来就是重写的move方法  
  19.         for(Method m : methods) {  
  20.             methodStr += "@Override" + rt +   
  21.                          "public void " + m.getName() + "() {" + rt +  
  22.                          "    try {" + rt +  
  23.                          "    Method md = " + infce.getName() + ".class.getMethod(\"" + m.getName() + "\");" + rt +  
  24.                         /*方法最核心的代码,h是构造$Proxy1时传入的handler,本例中就是LogHandler对象,new Object[] { null}是move方法需要的参数,本例不需要,故为空。这一步将会使我们编写的处理类逻辑LogHandler的invoke方法得到调用。从而达到我们最初要在move方法前加日志逻辑的的目的,下面要做的就是把我们拼好的字符串生成类并load到内存就可以了这样就实现了动态生成代理类并加自己想加的逻辑*/  
  25.                          "    h.invoke(this, md,new Object[] { null});" + rt +  
  26.                          "    }catch(Exception e) {e.printStackTrace();}" + rt +  
  27.                           
  28.                          "}";  
  29.         }  
  30.           
  31.         String src =   
  32.             "package com.bjsxt.proxy;" +  rt +  
  33.             "import java.lang.reflect.Method;" + rt +  
  34.             //这里动态实现infce接口,本例中就是Moveable,构造方法中让Proxy持有处理类Handler的引用  
  35.             "public class $Proxy1 implements " + infce.getName() + "{" + rt +  
  36.             "    public $Proxy1(InvocationHandler h) {" + rt +  
  37.             "        this.h = h;" + rt +  
  38.             "    }" + rt +  
  39.               
  40.               
  41.             "    com.bjsxt.proxy.InvocationHandler h;" + rt +  
  42.             //这里是需要重写Moveable中的方法,见上面该字符串的拼接过程                
  43.             methodStr +  
  44.             "}";  
  45.         String fileName =   
  46.             "d:/src/com/bjsxt/proxy/$Proxy1.java";  
  47.         File f = new File(fileName);  
  48.         FileWriter fw = new FileWriter(f);  
  49.         fw.write(src);  
  50.         fw.flush();  
  51.         fw.close();  
  52.           
  53.         //compile编译上面拼好的字符串  
  54.         JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();  
  55.         StandardJavaFileManager fileMgr = compiler.getStandardFileManager(nullnullnull);  
  56.         Iterable units = fileMgr.getJavaFileObjects(fileName);  
  57.         CompilationTask t = compiler.getTask(null, fileMgr, nullnullnull, units);  
  58.         t.call();  
  59.         fileMgr.close();  
  60.           
  61.         //load into memory and create an instance加载进内存并创建对象  
  62.         URL[] urls = new URL[] {new URL("file:/" + "d:/src/")};  
  63.         URLClassLoader ul = new URLClassLoader(urls);  
  64.         Class c = ul.loadClass("com.bjsxt.proxy.$Proxy1");  
  65.         System.out.println(c);  
  66.           
  67.         Constructor ctr = c.getConstructor(InvocationHandler.class);  
  68.         Object m = ctr.newInstance(h);  
  69.           
  70.         //利用反射,这里就返回一个实现了infce接口也就是本例中Moveable接口的代理对像$Proxy1  
  71.         return m;  
  72.     }  
  73. }  
需要注意的是这里的Proxy类和JDK中java.lang.reflect.Proxy并不相同。只是他们的原理大致相同,实现的细节并不同。JDK中生成$Proxy1并不是拼字符串,而是直接生成二进制码。

Java中的静态代理、通用动态代理类以及原理剖析,,

Java中的静态代理、通用动态代理类以及原理剖析


推荐阅读
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文介绍了一个Java猜拳小游戏的代码,通过使用Scanner类获取用户输入的拳的数字,并随机生成计算机的拳,然后判断胜负。该游戏可以选择剪刀、石头、布三种拳,通过比较两者的拳来决定胜负。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文介绍了在Java中gt、gtgt、gtgtgt和lt之间的区别。通过解释符号的含义和使用例子,帮助读者理解这些符号在二进制表示和移位操作中的作用。同时,文章还提到了负数的补码表示和移位操作的限制。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
author-avatar
小怡的宝_594
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有