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<Method> mHistories = new ArrayList<Method>();

	/**
	 * 注入被代理的对象
	 * @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> T createProxy(T obj) {

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

	}

	/**
	 *
	 * @return
	 */
	public List<Method> 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> 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
plain
copy

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

需要注意的是这里的Proxy类和JDK中java.lang.reflect.Proxy并不相同。只是他们的原理大致相同,实现的细节并不同。JDK中生成$Proxy1并不是拼字符串,而是直接生成二进制码。

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

时间: 2024-08-06 21:40:47

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

Java基础:静态代理和动态代理

转载请注明出处:jiq?钦's technical Blog 一.静态代理: 假设原来有一个实现了指定接口/抽象类的子类: class RealSubject implements Subject{ public void request(){ System.out.print("real request handling\n"); } } 现在有两种情况会发生: 1新的代码需要调用Subject接口,但是需要给每个接口加入新代码(比如日志记录.权限控制等): 2旧的代码已经使用了Su

java中静态代理和动态代理

一.概述 代理是一种模式,提供了对目标对象的间接访问方式,即通过代理访问目标对象.如此便于在目标实现的基础上增加额外的功能操作,前拦截,后拦截等,以满足自身的业务需求,同时代理模式便于扩展目标对象功能的特点也为多人所用. 按照代理的创建时期,代理类可以分为两种: 静态:由程序员创建代理类或特定工具自动生成源代码再对其编译.在程序运行前代理类的.class文件就已经存在了. 动态:在程序运行时运用反射机制动态创建而成. 我们根据加载被代理类的时机不同,将代理分为静态代理和动态代理.如果我们在代码编

Java静态代理与动态代理模式的实现

前言:    在现实生活中,考虑以下的场景:小王打算要去租房,他相中了一个房子,准备去找房东洽谈相关事宜.但是房东他很忙,平时上班没时间,总没有时间见面,他也没办法.后来,房东想了一个办法,他找到了一个人代替自己和小王洽谈,房东本人不用出面,他只要把他的对房客的要求告诉他找的那个人,那个人和你商量就可以了,这样就可以完成租房这件事了.这种现实场景比比皆是,所呈现出来的其实就是代理模式的原型的一种.我们把焦点转向编程,你是否在编程中经常遇见这样一个问题,对于访问某个对象,我们希望给它的方法前加入一

深入浅出java静态代理和动态代理

首先介绍一下,什么是代理: 代理模式,是常用的设计模式.特征是,代理类与委托类有相同的接口,代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类.以及事后处理消息. 代理类和委托类,存在着关联关系.代理类的对象本身并不真正实现服务,知识通过调用委托类的对象的相关方法. 代理类可以分为两种:静态代理和动态代理. 静态代理: 代理类是由程序员创建,或由工具生成的代码 编译成的.在程序运行前,代理类的 *.class文件已经存在了.直接就可以运行 . 动态代理: 动态代理的代理类.没有直接由

【Java】代处理?代理模式 - 静态代理,动态代理

>不用代理 有时候,我希望在一些方法前后都打印一些日志,于是有了如下代码. 这是一个处理float类型加法的方法,我想在调用它前打印一下参数,调用后打印下计算结果.(至于为什么不直接用+号运算,见[Java]Float计算不准确) package com.nicchagil.study.java.demo.No09代理.No01不用代理; import java.math.BigDecimal; public class FloatCalculator { public float add(fl

java静态代理与动态代理简单分析

原创作品,可以转载,但是请标注出处地址http://www.cnblogs.com/V1haoge/p/5860749.html 1.动态代理(Dynamic Proxy) 代理分为静态代理和动态代理,静态代理是在编译时就将接口.实现类.代理类一股脑儿全部手动完成,但如果我们需要很多的代理,每一个都这么手动的去创建实属浪费时间,而且会有大量的重复代码,此时我们就可以采用动态代理,动态代理可以在程序运行期间根据需要动态的创建代理类及其实例,来完成具体的功能. 其实方法直接调用就可以完成功能,为什么

【java项目实战】代理模式(Proxy Pattern),静态代理 VS 动态代理

这篇博文,我们主要以类图和代码的形式来对照学习一下静态代理和动态代理.重点解析各自的优缺点. 定义 代理模式(Proxy Pattern)是对象的结构型模式,代理模式给某一个对象提供了一个代理对象,并由代理对象控制对原对象的引用. 代理模式不会改变原来的接口和行为,仅仅是转由代理干某件事,代理能够控制原来的目标,比如:代理商,代理商仅仅会买东西,但并不会改变行为.不会制造东西. 让我们通过以下的代码好好理解一下这句话. 分类 静态代理和动态代理 静态代理 静态代理类图 代码演示样例 接口 pac

java 静态代理 JDK动态代理 Cglib动态代理

下面以一个简单的银行账户为例讲述讲述动态代理. 设计一个银行账户类,包含用户的账户余额,实现查询和更新余额功能 这个系统用了一段时间,有客户要求对账说账户余额给弄错了?因为上面没有存取款记录,最后银行不认账,客户收到了损失.银行为了避免这种现象再次发生,决定对这个系统进行修改,但是因为bankAccount太过复杂,希望在不修改bankAccount的情况下,增加日志功能. 静态代理 使用静态代理解决上面的问题. 银行要求所有模块都需要添加日志功能,这对苦逼的程序员来说真的是一个不小的工作量啊,

Java设计模式学习06——静态代理与动态代理(转)

原地址:http://blog.csdn.net/xu__cg/article/details/52970885 一.代理模式 为某个对象提供一个代理,从而控制这个代理的访问.代理类和委托类具有共同的父类或父接口,这样在任何使用委托类对象的地方都可以使用代理类对象替代.代理类负责请求的预处理.过滤.将请求分配给委托类处理.以及委托类处理完请求的后续处理. 二.代理模式结构 UML类图: 由上图代理模式的结构为: 抽象角色: 真实对象和代理对象的共同接口. 代理角色: 代理对象角色内部含有对真实对