作者:zuoxiaolong8810(左潇龙),转载请注明出处。

我特意将本系列改了下名字,原名是《设计模式学习之路》,原因是因为之前写过一篇《spring源码学习之路》,但是我感觉本次写二十三种设计模式,更多的还是分享给各位自己的理解,所以感觉学习之路有点不合适,另外,从本章开始,正式启用本人称呼,LZ。

好了,废话至此,本章接着讨论第二种要介绍的设计模式,代理模式。

LZ不希望写的东西与网络上的资料千篇一律,所以这一系列不会像很多典型文章一章,只是列出这个模式的定义以及一堆适用的情况,然后就是一堆这个模式的各个角色,对于这种罗列LZ并不反对,相比之下会比较清晰,但是如果脱离了实际,就会导致看的人特别是初学者觉得设计模式很陌生很遥远。

LZ并不反对这种教学式的标准模式,但说实话,LZ本人看这种帖子从来都感觉收获不大,看一遍看一遍,到现在都没记住那些各个适用的情况与一堆乱七八糟的角色。

所以LZ探讨代理模式,不会再按这个步骤进行,而是跟着自己的思维进行。

首先代理模式,可以分为两种,一种是静态代理,一种是动态代理。

两种代理从虚拟机加载类的角度来讲,本质上都是一样的,都是在原有类的行为基础上,加入一些多出的行为,甚至完全替换原有的行为。

静态代理采用的方式就是我们手动的将这些行为换进去,然后让编译器帮我们编译,同时也就将字节码在原有类的基础上加入一些其他的东西或者替换原有的东西,产生一个新的与原有类接口相同却行为不同的类型。

说归说,我们来真实的去试验一下,实验的话需要找一个示例,就拿我们的数据库连接来做例子吧。

我们都知道,数据库连接是很珍贵的资源,频繁的开关数据库连接是非常浪费服务器的CPU资源以及内存的,所以我们一般都是使用数据库连接池来解决这一问题,即创造一堆等待被使用的连接,等到用的时候就从池里取一个,不用了再放回去,数据库连接在整个应用启动期间,几乎是不关闭的,除非是超过了最大闲置时间。

但是在程序员编写程序的时候,会经常使用connection.close()这样的方法,去关闭数据库连接,而且这样做是对的,所以你并不能告诉程序员们说,你们使用连接都不要关了,去调用一个其他的类似归还给连接池的方法吧。这是不符合程序员的编程思维的,也很勉强,而且具有风险性,因为程序员会忘的。

解决这一问题的办法就是使用代理模式,因为代理模式可以替代原有类的行为,所以我们要做的就是替换掉connection的close行为。

下面是connection接口原有的样子,我去掉了很多方法,因为都类似,全贴上来占地方。

import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Wrapper;public interface Connection  extends Wrapper {Statement createStatement() throws SQLException;void close() throws SQLException;}

这里只贴了两个方法,但是我们代理的精髓只要两个方法就能掌握,下面使用静态代理,采用静态代理我们通常会使用组合的方式,为了保持对程序猿是透明的,我们实现Connection这个接口。

如下所示。

import java.sql.SQLException;
import java.sql.Statement;public class ConnectionProxy implements Connection{private Connection connection;public ConnectionProxy(Connection connection) {super();this.connection = connection;}public Statement createStatement() throws SQLException{return connection.createStatement();}public void close() throws SQLException{System.out.println("不真正关闭连接,归还给连接池");}}

我们在构造方法中让调用者强行传入一个原有的连接,接下来我们将我们不关心的方法,交给真正的Connection接口去处理,就像createStatement方法一样,而我们将真正关心的close方法用我们自己希望的方式去进行。

此处为了更形象,LZ给出一个本人写的非常简单的连接池,意图在于表明实现的思路。下面我们来看一下连接池的变化,在里面注明了变化点。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;public class DataSource {private static LinkedList<Connection> connectionList = new LinkedList<Connection>();static{try {Class.forName("com.mysql.jdbc.Driver");} catch (ClassNotFoundException e) {e.printStackTrace();}}private static Connection createNewConnection() throws SQLException{return DriverManager.getConnection("url","username", "password");}private DataSource(){if (connectionList == null || connectionList.size() == 0) {for (int i = 0; i < 10; i++) {try {connectionList.add(createNewConnection());} catch (SQLException e) {e.printStackTrace();}}}}public Connection getConnection() throws Exception{if (connectionList.size() > 0) {//return connectionList.remove();  这是原有的方式,直接返回连接,这样可能会被程序员把连接给关闭掉//下面是使用代理的方式,程序员再调用close时,就会归还到连接池return new ConnectionProxy(connectionList.remove());}return null;}public void recoveryConnection(Connection connection){connectionList.add(connection);}public static DataSource getInstance(){return DataSourceInstance.dataSource;}private static class DataSourceInstance{private static DataSource dataSource = new DataSource();}}

连接池我们把它做成单例,所以假设是上述连接池的话,我们代理中的close方法可以再具体化一点,就像下面这样,用归还给连接池的动作取代关闭连接的动作。

 public void close() throws SQLException{DataSource.getInstance().recoveryConnection(connection);}

好了,这下我们的连接池返回的连接全是代理,就算程序员调用了close方法也只会归还给连接池了。

我们使用代理模式解决了上述问题,从静态代理的使用上来看,我们一般是这么做的。

                1,代理类一般要持有一个被代理的对象的引用。

                2,对于我们不关心的方法,全部委托给被代理的对象处理。

                3,自己处理我们关心的方法。

这种代理是死的,不会在运行时动态创建,因为我们相当于在编译期,也就是你按下CTRL+S的那一刻,就给被代理的对象生成了一个不可动态改变的代理类。

静态代理对于这种,被代理的对象很固定,我们只需要去代理一个类或者若干固定的类,数量不是太多的时候,可以使用,而且其实效果比动态代理更好,因为动态代理就是在运行期间动态生成代理类,所以需要消耗的时间会更久一点。就像上述的情况,其实就比较适合使用静态代理。

下面介绍下动态代理,动态代理是JDK自带的功能,它需要你去实现一个InvocationHandler接口,并且调用Proxy的静态方法去产生代理类。

接下来我们依然使用上面的示例,但是这次该用动态代理处理,我们来试一下看如何做。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;public class ConnectionProxy implements InvocationHandler{private Connection connection;public ConnectionProxy(Connection connection) {super();this.connection = connection;}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//这里判断是Connection接口的close方法的话if (Connection.class.isAssignableFrom(proxy.getClass()) && method.getName().equals("close")) {//我们不执行真正的close方法//method.invoke(connection, args);//将连接归还连接池DataSource.getInstance().recoveryConnection(connection);return null;}else {return method.invoke(connection, args);}}public Connection getConnectionProxy(){return (Connection) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Connection.class}, this);}}

上面是我们针对connection写的动态代理,InvocationHandler接口只有一个invoke方法需要实现,这个方法是用来在生成的代理类用回调使用的,关于动态代理的原理一会做详细的分析,这里我们先只关注用法。很显然,动态代理是将每个方法的具体执行过程交给了我们在invoke方法里处理。而具体的使用方法,我们只需要创造一个ConnectionProxy的实例,并且将调用getConnectionProxy方法的返回结果作为数据库连接池返回的连接就可以了。

上述便是我们针对connection做动态代理的方式,但是我们从中得不到任何好处,除了能少写点代码以外,因为这个动态代理还是只能代理Connection这一个接口,如果我们写出这种动态代理的方式的话,说明我们应该使用静态代理处理这个问题,因为它代表我们其实只希望代理一个类就好。从重构的角度来说,其实更简单点,那就是在你发现你使用静态代理的时候,需要写一大堆重复代码的时候,就请改用动态代理试试吧。

通常情况下,动态代理的使用是为了解决这样一种问题,就是我们需要代理一系列类的某一些方法,最典型的应用就是我们前段时间讨论过的springAOP,我们需要创造出一批代理类,切入到一系列类当中的某一些方法中。下面给出一个经常使用的动态代理方式。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class DynamicProxy implements InvocationHandler{private Object source;public DynamicProxy(Object source) {super();this.source = source;}public void before(){System.out.println("在方法前做一些事,比如打开事务");}public void after(){System.out.println("在方法返回前做一些事,比如提交事务");}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//假设我们切入toString方法,其他其实也是类似的,一般我们这里大部分是针对特定的方法做事情的,通常不会对类的全部方法切入//比如我们常用的事务管理器,我们通常配置的就是对save,update,delete等方法才打开事务if (method.getName().equals("toString")) {before();}Object result = method.invoke(source, args);if (method.getName().equals("toString")) {after();}return result;}public Object getProxy(){return Proxy.newProxyInstance(getClass().getClassLoader(), source.getClass().getInterfaces(), this);}}

上述我做了一些注释,其实已经说明一些问题,这个代理类的作用是可以代理任何类,因为它被传入的对象是Object,而不再是具体的类,比如刚才的Connection,这些产生的代理类在调用toString方法时会被插入before方法和after方法。

动态代理有一个强制性要求,就是被代理的类必须实现了某一个接口,或者本身就是接口,就像我们的Connection。

道理其实很简单,这是因为动态代理生成的代理类是继承Proxy类的,并且会实现被你传入newProxyInstance方法的所有接口,所以我们可以将生成的代理强转为任意一个代理的接口或者Proxy去使用,但是Proxy里面几乎全是静态方法,没有实例方法,所以转换成Proxy意义不大,几乎没什么用。假设我们的类没有实现任何接口,那么就意味着你只能将生成的代理类转换成Proxy,那么就算生成了,其实也没什么用,而且就算你传入了接口,可以强转,你也用不了这个没有实现你传入接口的这个类的方法。

你可能会说,假设有个接口A,那我将接口A传给newProxyInstance方法,并代理一个没实现接口A的类B,但类B与接口A有一样的方法可以吗?

答案是可以的,并且JDK的动态代理只认你传入的接口,只要你传入,你就可以强转成这个接口,这个一会解释,但是你无法在invoke方法里调用method.invoke方法,也就是说,你只能全部替换A接口的方法,而不能使用类B中原有与接口A方法描述相同的方法,这是因为invoke中传入的Method的class信息是接口A,而类B因为没实现接口A,所以无法执行传入的Method,会抛出非法参数异常。

下面我贴出测试代码,各位可以自己试一下,具体为何会这样是在后面解释的,这里不再多做解释。

先是一个普通接口。

public interface TestInterface {void method1();void method2();void method3();
}

然后是一个类,和接口一模一样的方法,但是就是没实现这个接口。

public class TestClass{public void method1() {System.out.println("TestClass.method1");}public void method2() {System.out.println("TestClass.method2");}public void method3() {System.out.println("TestClass.method3");}}

下面是测试类。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class DynamicProxy implements InvocationHandler{private Object source;public DynamicProxy(Object source) {super();this.source = source;}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("接口的方法全部变成这样了");//这里source是TestClass,但是我们不能使用反射调用它的方法,像下面这样,放开这一行会抛异常//return method.invoke(source, args);return null;}public static void main(String[] args) {//只要你传入就可以强转成功TestInterface object =  (TestInterface) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{TestInterface.class}, new DynamicProxy(new TestClass()));object.method1();object.method2();object.method3();}
}

上面我们运行就会发现接口的方法全部都只能输出一个很2的字符串了。如果是要继续使用TestClass的方法也不是不行,只要你确认你传入的类包括了所有你传入的接口的方法,只是没实现这些接口而已,那么你可以在invoke中这样使用。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("before");Method sourceMethod = source.getClass().getDeclaredMethod(method.getName(), method.getParameterTypes());sourceMethod.setAccessible(true);Object result = sourceMethod.invoke(source, args);System.out.println("after");return result;}

这就与你实现接口的表现行为一致了,但是我们本来就只需要一句method.invoke就可以了,就因为没实现接口就要多写两行,所以这种突破JDK动态代理必须实现接口的行为就有点画蛇添足了。因为你本来就实现了该接口的方法,只是差了那一句implements而已。

上面写这个例子只是为了解释LZ当初的疑惑,因为LZ曾一度认为不实现接口就不能使用动态代理,现在想想那时候LZ有点2,呵呵。

好了,从现在开始,我们开始详细讲解动态代理的原理,这算是进阶篇,如果是新手的话,可以跳过下面的内容,因为现在还没必要知道这些,而且弄不好会越看越蒙,不过仅仅是LZ个人建议,你要有耐心,完全可以继续看下去。

接下来我们结合源码去看一下,代理类是如何产生的,首先当然就是要进入Proxy的newProxyInstance方法,这里是产生代理的入口,源码如下。

    public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException{if (h == null) {throw new NullPointerException();}/** Look up or generate the designated proxy class.*/Class cl = getProxyClass(loader, interfaces);/** Invoke its constructor with the designated invocation handler.*/try {Constructor cons = cl.getConstructor(constructorParams);return (Object) cons.newInstance(new Object[] { h });} catch (NoSuchMethodException e) {throw new InternalError(e.toString());} catch (IllegalAccessException e) {throw new InternalError(e.toString());} catch (InstantiationException e) {throw new InternalError(e.toString());} catch (InvocationTargetException e) {throw new InternalError(e.toString());}}

这个方法其实很简单,首先获取了代理类的运行时Class引用,然后调用了这个Class中的构造方法,这个构造方法只有一个参数,正是InvocationHandler接口,由此产生了一个代理类的实例。那么关键的地方就在于如何获取的代理类运行时的class信息的呢?我们进入getProxyClass方法看一下。为了方便起见,我直接加注释,这个方法需要解释的地方比较多。

public static Class<?> getProxyClass(ClassLoader loader,Class<?>... interfaces) throws IllegalArgumentException {//如果传入的接口长度大于65535就抛出异常,我去你妹。。。if (interfaces.length > 65535) {throw new IllegalArgumentException("interface limit exceeded");}Class proxyClass = null;/* collect interface names to use as key for proxy class cache */String[] interfaceNames = new String[interfaces.length];Set interfaceSet = new HashSet(); // for detecting duplicatesfor (int i = 0; i < interfaces.length; i++) {/** Verify that the class loader resolves the name of this interface* to the same Class object.*/String interfaceName = interfaces[i].getName();Class interfaceClass = null;try {//加载每一个接口的运行时Class信息interfaceClass = Class.forName(interfaceName, false, loader);} catch (ClassNotFoundException e) {}//如果采用你传入的类加载器载入的Class和你传入的Class不相等则抛出异常if (interfaceClass != interfaces[i]) {throw new IllegalArgumentException(interfaces[i]+ " is not visible from class loader");}//如果你传入的不是接口抛出异常/** Verify that the Class object actually represents an interface.*/if (!interfaceClass.isInterface()) {throw new IllegalArgumentException(interfaceClass.getName()+ " is not an interface");}//如果你传入的接口重复抛异常/** Verify that this interface is not a duplicate.*/if (interfaceSet.contains(interfaceClass)) {throw new IllegalArgumentException("repeated interface: "+ interfaceClass.getName());}interfaceSet.add(interfaceClass);interfaceNames[i] = interfaceName;}/** Using string representations of the proxy interfaces as keys in the* proxy class cache (instead of their Class objects) is sufficient* because we require the proxy interfaces to be resolvable by name* through the supplied class loader, and it has the advantage that* using a string representation of a class makes for an implicit weak* reference to the class.*/Object key = Arrays.asList(interfaceNames);/** Find or create the proxy class cache for the class loader.*/Map cache;synchronized (loaderToCache) {//这个是为了存储每一个类加载器所载入过的代理接口的代理类cache = (Map) loaderToCache.get(loader);if (cache == null) {cache = new HashMap();loaderToCache.put(loader, cache);}/** This mapping will remain valid for the duration of this method,* without further synchronization, because the mapping will only be* removed if the class loader becomes unreachable.*/}/** Look up the list of interfaces in the proxy class cache using the* key. This lookup will result in one of three possible kinds of* values: null, if there is currently no proxy class for the list of* interfaces in the class loader, the pendingGenerationMarker object,* if a proxy class for the list of interfaces is currently being* generated, or a weak reference to a Class object, if a proxy class* for the list of interfaces has already been generated.*/synchronized (cache) {/** Note that we need not worry about reaping the cache for entries* with cleared weak references because if a proxy class has been* garbage collected, its class loader will have been garbage* collected as well, so the entire cache will be reaped from the* loaderToCache map.*/do {//检查是否有生成好的代理Object value = cache.get(key);if (value instanceof Reference) {proxyClass = (Class) ((Reference) value).get();}//有的话直接返回if (proxyClass != null) {// proxy class already generated: return itreturn proxyClass;//否则看一下这个代理类是不是正在构造中,是的话就在cache对象上等待} else if (value == pendingGenerationMarker) {// proxy class being generated: wait for ittry {cache.wait();} catch (InterruptedException e) {/** The class generation that we are waiting for should* take a small, bounded time, so we can safely ignore* thread interrupts here.*/}continue;//如果没有现成的,也没有创造中的,那就开始创造代理类} else {/** No proxy class for this list of interfaces has been* generated or is being generated, so we will go and* generate it now. Mark it as pending generation.*///将当前代理类置为正在构造中,并直接退出循环cache.put(key, pendingGenerationMarker);break;}} while (true);}try {String proxyPkg = null; // package to define proxy class in//这一段是看你传入的接口中有没有不是public的接口,如果有,这些接口必须全部在一个包里定义的,否则抛异常/** Record the package of a non-public proxy interface so that the* proxy class will be defined in the same package. Verify that all* non-public proxy interfaces are in the same package.*/for (int i = 0; i < interfaces.length; i++) {int flags = interfaces[i].getModifiers();if (!Modifier.isPublic(flags)) {String name = interfaces[i].getName();int n = name.lastIndexOf('.');String pkg = ((n == -1) ? "" : name.substring(0, n + 1));if (proxyPkg == null) {proxyPkg = pkg;} else if (!pkg.equals(proxyPkg)) {throw new IllegalArgumentException("non-public interfaces from different packages");}}}if (proxyPkg == null) { // if no non-public proxy interfaces,proxyPkg = ""; // use the unnamed package}{/** Choose a name for the proxy class to generate.*/long num;synchronized (nextUniqueNumberLock) {num = nextUniqueNumber++;}//生成一个随机代理类名String proxyName = proxyPkg + proxyClassNamePrefix + num;/** Verify that the class loader hasn't already defined a class* with the chosen name.*///这一句就是重中之重了,生成代理类的class文件,这就是JDK动态代理的原理了,通过动态生成class文件来产生的代理类//这个generateProxyClass方法下面会着重介绍/** Generate the specified proxy class.*/byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);try {//得到class文件二进制流后,直接载入代理类proxyClass = defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);} catch (ClassFormatError e) {/** A ClassFormatError here means that (barring bugs in the* proxy class generation code) there was some other invalid* aspect of the arguments supplied to the proxy class* creation (such as virtual machine limitations exceeded).*/throw new IllegalArgumentException(e.toString());}}//proxyClasses这个Map是为了来判断是不是代理的Class// add to set of all generated proxy classes, for isProxyClassproxyClasses.put(proxyClass, null);} finally {/** We must clean up the "pending generation" state of the proxy* class cache entry somehow. If a proxy class was successfully* generated, store it in the cache (with a weak reference);* otherwise, remove the reserved entry. In all cases, notify all* waiters on reserved entries in this cache.*/synchronized (cache) {if (proxyClass != null) {//最终将生成的代理用弱引用包装起来放到cache当中cache.put(key, new WeakReference(proxyClass));} else {//如果代理类是空则移除代理的接口所代表的key值cache.remove(key);}//通知正在等待在cache对象上的线程,告诉他们可以继续了,代理Class加载完毕了cache.notifyAll();}}return proxyClass;}

上面基本上已经解释的很清楚了,下面就是去看一下byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces)这句话是如何处理的,也就是如何得到的代理类的class文件的,我们进去源码看一下,我依然会加上注释。

public static byte[] generateProxyClass(String paramString, Class[] paramArrayOfClass) {//新建一个ProxyGenerator实例,传入类名和接口数组ProxyGenerator localProxyGenerator = new ProxyGenerator(paramString, paramArrayOfClass);//这个才是真正生成class文件的地方final byte[] arrayOfByte = localProxyGenerator.generateClassFile();//看保存生成文件的标志是否为真,如果是就将class文件生成到本地,生成时要检查权限if (saveGeneratedFiles) {AccessController.doPrivileged(new PrivilegedAction() {public Object run() {try {FileOutputStream localFileOutputStream = new FileOutputStream( ProxyGenerator.dotToSlash(this.val$name) + ".class");localFileOutputStream.write(arrayOfByte);localFileOutputStream.close();return null;} catch (IOException localIOException) {throw new InternalError( "I/O exception saving generated file: " + localIOException);}}});}return arrayOfByte;}

我们继续跟踪到localProxyGenerator.generateClassFile()这一句当中,依然会加上注释。

private byte[] generateClassFile() {//addProxyMethod方法,就是将方法都加入到一个列表中,并与对应的class对应起来//这里给Object对应了三个方法hashCode,toString和equalsaddProxyMethod(hashCodeMethod, Object.class);addProxyMethod(equalsMethod, Object.class);addProxyMethod(toStringMethod, Object.class);//同样将接口与接口下的方法对应起来for (int i = 0; i < this.interfaces.length; i++) {localObject1 = this.interfaces[i].getMethods();for (int k = 0; k < localObject1.length; k++) {addProxyMethod(localObject1[k], this.interfaces[i]);}}//检查所有代理方法的返回类型for (Iterator localIterator1 = this.proxyMethods.values().iterator(); localIterator1.hasNext();) {localObject1 = (List) localIterator1.next();checkReturnTypes((List) localObject1);}Object localObject2;try {//方法中加入构造方法,这个构造方法只有一个,就是一个带有InvocationHandler接口的构造方法//这个才是真正给class文件,也就是代理类加入方法了,不过还没真正处理,只是先加进来等待循环,构造方法在class文件中的名称描述是<init>this.methods.add(generateConstructor());//循环代理方法for (localIterator1 = this.proxyMethods.values().iterator(); localIterator1.hasNext();) {localObject1 = (List) localIterator1.next();for (localIterator2 = ((List) localObject1).iterator(); localIterator2.hasNext();) {localObject2 = (ProxyMethod) localIterator2.next();//给每一个代理方法加一个Method类型的属性,数字10是class文件的标识符,代表这些属性都是private static的this.fields.add(new FieldInfo(((ProxyMethod) localObject2).methodFieldName,"Ljava/lang/reflect/Method;", 10));//将每一个代理方法都加到代理类的方法中this.methods.add(((ProxyMethod) localObject2).generateMethod());}}Iterator localIterator2;//加入一个静态初始化块,将每一个属性都初始化,这里静态代码块也叫类构造方法,其实就是名称为<clinit>的方法,所以加到方法列表this.methods.add(generateStaticInitializer());} catch (IOException localIOException1) {throw new InternalError("unexpected I/O Exception");}//方法和属性个数都不能超过65535,包括刚才的接口个数也是这样,//这是因为在class文件中,这些个数都是用4位16进制表示的,所以最大值是2的16次方-1if (this.methods.size() > 65535) {throw new IllegalArgumentException("method limit exceeded");}if (this.fields.size() > 65535) {throw new IllegalArgumentException("field limit exceeded");}//这里是将类名中的.转成成斜线为了写入class文件。this.cp.getClass(dotToSlash(this.className));this.cp.getClass("java/lang/reflect/Proxy");for (int j = 0; j < this.interfaces.length; j++) {this.cp.getClass(dotToSlash(this.interfaces[j].getName()));}this.cp.setReadOnly();//这里开始真正的写class文件ByteArrayOutputStream localByteArrayOutputStream = new ByteArrayOutputStream();Object localObject1 = new DataOutputStream(localByteArrayOutputStream);try {//写入class文件的标识号,标识这是一个class文件((DataOutputStream) localObject1).writeInt(-889275714);//次版本号0((DataOutputStream) localObject1).writeShort(0);//主版本号,49代表的是JDK1.5((DataOutputStream) localObject1).writeShort(49);//这里写入的是常量池,包括一些属性名称,类名称,方法描述符,属性描述符等等,常量池在加载时会被放到方法区或者说永久代。this.cp.write((OutputStream) localObject1);//这里写入的是这个类的访问标识,49代表的是public final,也就是说JDK动态代理生成的代理类都是final的((DataOutputStream) localObject1).writeShort(49);//写入代理类的类名((DataOutputStream) localObject1).writeShort(this.cp.getClass(dotToSlash(this.className)));//写入代理类的父类类名,也就是Proxy类,这个位置的类如果说是JAVA文件,相当于extend后面的类,也就是父类((DataOutputStream) localObject1).writeShort(this.cp.getClass("java/lang/reflect/Proxy"));//写入代理类所实现的接口数量((DataOutputStream) localObject1).writeShort(this.interfaces.length);//写入代理类所实现的接口类名,同样的,对于JAVA文件来说,相当于implements后面的接口,也就是实现的接口for (int m = 0; m < this.interfaces.length; m++) {((DataOutputStream) localObject1).writeShort(this.cp.getClass(dotToSlash(this.interfaces[m].getName())));}//写入属性个数((DataOutputStream) localObject1).writeShort(this.fields.size());//写入属性描述for (Iterator localIterator3 = this.fields.iterator(); localIterator3.hasNext();) {localObject2 = (FieldInfo) localIterator3.next();((FieldInfo) localObject2).write((DataOutputStream) localObject1);}//写入方法个数((DataOutputStream) localObject1).writeShort(this.methods.size());//写入方法描述,方法的code属性,以及构造方法和类构造方法都在这里被写入了。for (localIterator3 = this.methods.iterator(); localIterator3.hasNext();) {localObject2 = (MethodInfo) localIterator3.next();((MethodInfo) localObject2).write((DataOutputStream) localObject1);}//结束((DataOutputStream) localObject1).writeShort(0);} catch (IOException localIOException2) {throw new InternalError("unexpected I/O Exception");}return localByteArrayOutputStream.toByteArray();}

其实代理类的class文件并不复杂,还是有很多规律可循的,所以上述过程基本上可以让各位了解下JDK动态代理生成代理类时都生成了什么东西。

下面我们可以调用下JDK中生成Class文件的方法,并且写入到本地文件,然后使用反编译工具来看一下生成的代理类到底是什么样子的。下面是生成文件的测试类。我们暂且将生成的类名写成TestProxy,代理的接口就是我们上面的TestInterface。如下。

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;import sun.misc.ProxyGenerator;public class CreateClassTest {public static void main(String[] args) throws IOException {byte[] classFile = ProxyGenerator.generateProxyClass("TestProxy", new Class[]{TestInterface.class});File file = new File("F:/TestProxy.class");FileOutputStream fos = new FileOutputStream(file);fos.write(classFile);fos.flush();fos.close();}}

生成后,我们反编译过来会是如下格式的JAVA文件。我加入了注释,大致说明了下文件中生成的部分与刚才分析的时候写入的过程的对应关系。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;//public final的,继承Proxy,实现你传入的接口
public final class TestProxy extends Proxyimplements TestInterface
{//private static 的Method属性,对应所有方法private static Method m1;private static Method m5;private static Method m3;private static Method m4;private static Method m0;private static Method m2;//唯一的构造方法,需要一个InvocationHandler接口传入public TestProxy(InvocationHandler paramInvocationHandler)throws {super(paramInvocationHandler);}//重写Object的三个方法public final boolean equals(Object paramObject)throws {try{return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();}catch (Error|RuntimeException localError){throw localError;}catch (Throwable localThrowable){throw new UndeclaredThrowableException(localThrowable);}}public final void method3()throws {try{this.h.invoke(this, m5, null);return;}catch (Error|RuntimeException localError){throw localError;}catch (Throwable localThrowable){throw new UndeclaredThrowableException(localThrowable);}}//代理的三个方法,回调传入的InvocationHandler的invoke方法public final void method1()throws {try{this.h.invoke(this, m3, null);return;}catch (Error|RuntimeException localError){throw localError;}catch (Throwable localThrowable){throw new UndeclaredThrowableException(localThrowable);}}public final void method2()throws {try{this.h.invoke(this, m4, null);return;}catch (Error|RuntimeException localError){throw localError;}catch (Throwable localThrowable){throw new UndeclaredThrowableException(localThrowable);}}public final int hashCode()throws {try{return ((Integer)this.h.invoke(this, m0, null)).intValue();}catch (Error|RuntimeException localError){throw localError;}catch (Throwable localThrowable){throw new UndeclaredThrowableException(localThrowable);}}public final String toString()throws {try{return (String)this.h.invoke(this, m2, null);}catch (Error|RuntimeException localError){throw localError;}catch (Throwable localThrowable){throw new UndeclaredThrowableException(localThrowable);}}//这个就是刚才this.methods.add(generateStaticInitializer());这一句话所加入的静态初始化块,初始化每一个属性static{try{//每一个属性所代表的Method都是与上面加入代理方法列表时与固定类绑定的,这是class文件中的格式,方法要与固定的类绑定m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });m5 = Class.forName("TestInterface").getMethod("method3", new Class[0]);m3 = Class.forName("TestInterface").getMethod("method1", new Class[0]);m4 = Class.forName("TestInterface").getMethod("method2", new Class[0]);m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);return;}catch (NoSuchMethodException localNoSuchMethodException){throw new NoSuchMethodError(localNoSuchMethodException.getMessage());}catch (ClassNotFoundException localClassNotFoundException){throw new NoClassDefFoundError(localClassNotFoundException.getMessage());}}
}

看到这里就知道invoke方法是干嘛的了,其实就是生成的代理类对每一个方法的处理就是回调invoke方法。从生成的代理类源文件中也可以发现,每一个Method除了hashCode,toString和equals外,都是与所属的接口绑定的,所以这也就解释了为什么我们不实现这个接口,只传入进入的话,不能直接使用method.invoke,而是要转成source对应的method才可以调用。

好了,代理模式就分析到这里了,这里讲的更多的是代理模式的原理,对于如何使用并没有讲述太多,是因为代理模式在平时工作中用的虽然很多,但我们大多是使用的现成的,原因很简单,就是因为spring的AOP已经给我们弄了一个很好的动态代理的框架,所以我们几乎不需要自己去写,只要明白其原理,知道动态代理和静态代理主要处理的问题是那种的,知道在何处用,也能够用起来得心应手就可以了,当然这只是LZ个人之见,仅供参考。

下期预告,简单工厂模式。

(二)代理模式详解(包含原理详解)相关推荐

  1. TOPSIS(逼近理想解)算法原理详解与代码实现

    写在前面: 个人理解:针对存在多项指标,多个方案的方案评价分析方法,也就是根据已存在的一份数据,判断数据中各个方案的优劣.中心思想是首先确定各项指标的最优理想值(正理想值)和最劣理想值(负理想解),所 ...

  2. 静态代理模式(多线程底部原理)

    静态代理模式总结(线程底部原理) 真实对象和代理对象都要实现同一个接口 代理对象要代理真实角色 好处: - 代理对象可以做很多真实对象做不了的事情 - 真实对象专注做自己的事情 创建静态代理模式:一个 ...

  3. 大话设计模式(二)代理模式

    大话设计模式(二)代理模式 前言 代理模式(Proxy Pattern)是GoF 23种Java常用设计模式之一.代理模式的定义:Provide a surrogate or placeholder ...

  4. 代理模式详解(包含原理详解)

    http://www.cnblogs.com/zuoxiaolong/p/pattern3.html 作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为 ...

  5. 用WEB技术栈开发NATIVE应用(二):WEEX 前端SDK原理详解

    摘要: WEEX依旧采取传统的web开发技术栈进行开发,同时app在终端的运行体验不输native app.其同时解决了开发效率.发版速度以及用户体验三个核心问题.那么WEEX是如何实现的?目前WEE ...

  6. C#设计模式之十二代理模式(Proxy Pattern)【结构型】

    一.引言 今天我们要讲[结构型]设计模式的第七个模式,也是"结构型"设计模式中的最后一个模式,该模式是[代理模式],英文名称是:Proxy Pattern.还是老套路,先从名字上来 ...

  7. Java 代理模式的实现和原理详细分析

    文章目录 代理模式 静态代理 1. 静态代理的概念 2. 静态代理的实现 动态代理 1. 动态代理的概念 2. 动态代理的实现 2.1 如何创建一个动态代理对象 2.2 完整的动态代理的例子 3.动态 ...

  8. XxlJob(二) 负载均衡用法及实现原理详解

    目录 一.配置一个应用执行器 二.同一台机器上模拟负载均衡 1. 环境准备 2. 触发任务,选择轮询策略 3. 机器实例动态伸缩 三.负载均衡原理解析 1.  根据应用名查找地址列表 ​2. Exec ...

  9. [网络安全自学篇] 四十二.DNS欺骗和钓鱼网站原理详解及防御机理

    这是作者的网络安全自学教程系列,主要是关于安全工具和实践操作的在线笔记,特分享出来与博友们学习,希望您们喜欢,一起进步.前文分享了中间人攻击或ARP欺骗攻击,从ARP原理到局域网配置进行描述.这篇文章 ...

最新文章

  1. java web运行的快慢_WebAssembly执行速度真的很强悍吗?对微软Edge很无语
  2. 让网管暴寒的网络故障(一笑了之)
  3. firefox 自定义快捷键
  4. Shell脚本攻略03-使用shell进行数学运算
  5. Android开发工具新接触
  6. jq鼠标移入移除事件
  7. 程序员如何缓解“电脑病”
  8. javaone_JavaOne 2015 –又一年,又向前迈进了一步
  9. Careercup - Microsoft面试题 - 5428361417457664
  10. 字符串模式匹配——最长公共子序列与子串 KMP 算法
  11. LeetCode刷题(13)
  12. 首个官方气象数据集公开,已训练出20多个“青出于蓝”的AI
  13. C#之向文件追加内容
  14. err=etherbase address must be explicitly specified
  15. eclipse——配置maven插件
  16. 如何学习财务数据分析,有哪些好工具?
  17. HTML5响应式手机模板:H5网站设计——政府街道社区wap微信官网模板(11个页面) HTML+CSS+JavaScript 手机模板wap 手机网站模板 企业手机网站模板
  18. Google I/O 2021: 在重要时刻提供帮助
  19. 802.11 Tx Rate Control
  20. 如何判断两条线段是否相交

热门文章

  1. 机器学习系列(二)——回归模型
  2. 【spring】依赖注入之@Autowired依赖注入
  3. 索引的作用和为什么要创建索引
  4. 高手勿进!写给初中级程序员以及还在大学修炼的“准程序员”的成长秘籍
  5. 匈牙利命名法(Hungarian Notation)
  6. java命名规范(驼峰命名法)
  7. 如何通俗理解 beta分布、汤普森采样和狄利克雷分布
  8. 艾永亮:颠覆传统行业,打造超级产品,了解并响应用户需求是关键
  9. 数据结构考研:数据、数据元素、数据项、数据对象、数据结构的区别/详细解释(计算机/软件工程/王道论坛)
  10. Nginx设置开启/关闭/重启/开机自启