通过代理接口在内存中动态生成代理类源代码并编译实现的真正动态代理

代理类

package question3;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.ToolProvider;


/*
 * 	写一个ArrayList类的代理,其内部实现和ArrayList中完全相同的功能,
 * 	并可以计算每个方法运行的时间。
 * 
 *  解题方式四
 *  1.实现ProxyHandler,以达到代理代码的自定义
 *  2.实现内存中的字节码动态编译
 *  3.实现代理类的真正动态生成
 *  4.比java api 代理类更简短的参数列表,抛弃ClassLoader参数
 *  5.与SUN 的 api 底层实现方式不同,本方法使用JVM的编译器API
 *  
 *  
 *  优点:真正意义上的动态代理实现!!!!
 *  
 *  缺点:由于tools api支持有限,并且为JDK1.6的新支持,兼容性
 *       太差,编译速度也不快,这也是为什么SUN自己都不用,反而
 *       用Groove,真打脸,糊弄人么这不是
 *  
 *  注:1秒=1 000 000 000纳秒 jdk api中以毫微秒表示纳秒
 */
public class ArrayListProxy_4
{
	
	public static void main(String[] args) throws Exception
	{
		//collection接口的方法太多了,输出代理类源代码
		//的话,控制台太占地方
		setOutputSource(false);
		Collection collection = (Collection) newProxyInstance(new ProxyHandler()
		{
			long taken;
			ArrayList target = new ArrayList();
			@Override
			public Object invoke(Method method, Object... args)
			{
				
				Object returnValue = null;
				
				//以纳秒为单位输出耗时
				taken = System.nanoTime();
				try
                {
					//如果代理了多个接口,那么这个地方调用前也需要
					//判断当前method是属于哪个目标实例的方法
					//否则会抛异常
					returnValue = method.invoke(target, args);
                }
                catch (Exception e)
                {
	                e.printStackTrace();
                }
                taken = System.nanoTime() - taken;
                System.out.println("method "+method.getName()+"() invoked times taken by "+taken+"ns" + "    return " + returnValue);
                return returnValue;
			}
		}, Collection.class);
		
		//测试两个方法,足够了
		System.out.println("the proxy class name "+collection.getClass().getName());
		collection.size();
		collection.add("asd");
		
		//Runnable接口就一个方法,可以输出一下源代码
		//貌似源代码的格式还是不错的,(*^__^*)嘻嘻
		setOutputSource(true);
		//测试Runnable接口的代理
		Runnable runnable = (Runnable) newProxyInstance(new ProxyHandler()
		{
			long taken;
			Thread target = new Thread();
			@Override
			public Object invoke(Method method, Object... args)
			{
				
				Object returnValue = null;
				
				//以纳秒为单位输出耗时
				taken = System.nanoTime();
				try
                {
					//如果代理了多个接口,那么这个地方调用前也需要
					//判断当前method是属于哪个目标实例的方法
					//否则会抛异常
					returnValue = method.invoke(target, args);
                }
                catch (Exception e)
                {
	                e.printStackTrace();
                }
                taken = System.nanoTime() - taken;
                System.out.println("method "+method.getName()+"() invoked times taken by "+taken+"ns" + "    return " + returnValue);
                return returnValue;
			}
		}, Runnable.class);
		System.out.println("the proxy class name "+runnable.getClass().getName());
		runnable.run();
	}

	/**
	 * 代理对象的计数
	 */
	private static int proxy_count;
	
	private static boolean isOutputSource = false;
	
	private static void setOutputSource(boolean b)
	{
		isOutputSource = b;
	}
	
	/**
	 * 
	 * @param loader
	 * @param h
	 * @param interfaces 代理类需要实现的接口,如果这些接口中含有子父接口关系
	 * 						的接口,将只会保留子接口的实现,以避免重复实现
	 * @return
	 * @throws Exception
	 */
	public static Object newProxyInstance(ProxyHandler h,Class<?>... interfaces) throws Exception  
	{
		
		//以集合的方式判断是否有重复的接口
		//这将包括一样的接口,以及子父接口
		//只保留最后的子接口
		Set<Class<?>> interfaceSet = new HashSet<Class<?>>();
		for (Class<?> clazz : interfaces)
        {
			//添加前先检查是否重复,如果重复的话抛出
			removeSuperAndSameInterfaces(interfaceSet, clazz);
	        interfaceSet.add(clazz);
        }
		
		//为代理创建内存编译器
		MemoryCompiler compiler = new MemoryCompiler();
		//为需要的代理接口在内存中生成源代码
		//并进行内存编译返回代理类的实例
		Object proxy = compiler.compileJavaSourceFile(interfaceSet);
		//将代理程序传递给代理类
		proxy.getClass().getField("h").set(proxy, h);
		
		//将代理对象的计数增加并返回代理实例
		proxy_count++;
		return proxy;
	}
	
	/**
	 * 删除指定接口在集合中相同接口和父接口
	 * 
	 * <p>该方法主要用来保证最后的接口集合不存在重复的
	 * 方法出现在代理类中,比如相同的接口,以及指定接口
	 * 的父接口
	 * 
	 * <p>该方法是{@link #newProxyInstance(ClassLoader, ProxyHandle, Class...)}
	 * 方法的内联方法
	 * 
	 * <p>该方法使用递归方式进行向上迭代
	 * 
	 * @param set 需要进行检查的集合
	 * @param clazz 起始的最下层接口
	 * @see #newProxyInstance(ClassLoader, ProxyHandle, Class...)
	 */
	private static void removeSuperAndSameInterfaces(Set<Class<?>> set, Class<?> clazz)
	{
		if(set.contains(clazz))
			set.remove(clazz);
		Class<?>[] interfaces = clazz.getInterfaces();
		if(0 != interfaces.length)
			for (Class<?> super_clazz : interfaces)
				removeSuperAndSameInterfaces(set, super_clazz);
	}
	
	/**
	 * 代理类的代理程序接口
	 */
	public static interface ProxyHandler
	{
		//代理对象的实际调用方法
		public Object invoke(Method method, Object... args);
	}
	
	/**
	 * 本类为内存编译器
	 * 
	 * 实现了根据代理接口动态生成源文件,并在内存中进行编译
	 */
	static class MemoryCompiler 
	{
		/**
		 * 根据指定的接口生成代理类的源文件并进行编译
		 * 
		 * @param interfaceSet 代理类的代理接口
		 * @return 代理类的实例对象
		 */
		public Object compileJavaSourceFile(Set<Class<?>> interfaceSet)
		{
			//根据代理接口生成代理类的源代码
			String source = writeJavaSourceFileForMemory(interfaceSet);
			//创建java编译器
			JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
			//创建内存文件管理器
			MemoryFileManager filemanage = new MemoryFileManager(compiler
					.getStandardFileManager(null, null, null));
			//创建代理类的内存编译单元
			JavaFileObject file = MemoryFileManager.makeSource("Proxy$"+(proxy_count),source);
			//生成编译表,就一个元素的ArrayList,api需要,不写不行。。。
			Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(file);
			//为编译器创建编译任务
			JavaCompiler.CompilationTask task = compiler.getTask(null, filemanage,null, null, null, compilationUnits);
			//执行编译任务
			boolean success = task.call();
			
			//输出代理类的源代码。。。
			if(isOutputSource)
				System.out.println(source);
			//输出编译是否成功
			System.out.println("compile " + success);
			
			//定义一个万能引用,用来返回代理对象
			Object obj = null;
			try
            {
				//将代理类的字节码加载进内存,并为其创建实例
	            Class<?> clazz = filemanage.getClassLoader(null).loadClass("Proxy$"+(proxy_count));
	            obj = clazz.newInstance();
            }
			//好吧,万恶的ClassNotFoundException着实浪费我不少时间
            catch (Exception e)
            {
	            e.printStackTrace();
            }
			//返回代理类的实例对象
			return obj;
		}
		
		/**
		 * 将代理类的源代码写入内存
		 * @param interfaceSet 需要实现的接口
		 * @return 源代码的字符串
		 */
		public String writeJavaSourceFileForMemory(Set<Class<?>> interfaceSet) 
		{
			StringBuilder sb = new StringBuilder();
			
			writeClassHead(sb, interfaceSet);
			sb.append("{\n");
			sb.append("\tpublic question3.ArrayListProxy_4.ProxyHandler h;\n");
			writeMethods(sb, interfaceSet);
			sb.append("}");
			return sb.toString();
		}
		
		/**
		 * 将代理类的class声明写入内存
		 */
		private void writeClassHead(StringBuilder sb, Set<Class<?>> interfaceSet)
		{
			sb.append("public class Proxy$"+(proxy_count)+" implements ");
			int size = interfaceSet.size();
			//遍历所有的代理接口,并在代理类中实现他们
			//不同接口直接以,分开
			Iterator<Class<?>> iterator = interfaceSet.iterator();
			for (int i = 0; i < size; i++)
            {
				sb.append(iterator.next().getCanonicalName());
				if(i != size - 1)
					sb.append(", ");
            }
			sb.append("\n");
		}
		
		/**
		 * 根据代理类的代理接口将代理类的方法实现
		 * 
		 */
		private void writeMethods(StringBuilder sb, Set<Class<?>> interfaceSet)
		{
			Method[] methods;
			//遍历所有的接口,并为代理类逐一实现其内部的所有方法
			for (Class<?> clazz : interfaceSet)
            {
				//拿到当前接口的所有方法
	            methods = clazz.getMethods();
	            //逐一实现方法
	            for (Method method : methods)
                {
	            	sb.append("\tpublic ");
	            	//写入返回类型
	            	Class<?> returnType = method.getReturnType();
	            	//数组有点特殊,直接getname会是一个[L这样开头的
	            	//蛋疼名字
	                if(returnType.isArray())
	                	sb.append(returnType.getCanonicalName());
	                else
	                	sb.append(returnType.getName());
	                //写方法名
	            	sb.append(" ").append(method.getName());
	                sb.append("(");
	                
	                
	                Class<?>[] parameters = method.getParameterTypes();
	                //该变量用来附加在形参参数名称后,
	                //用来区分参数列表中的对象,例如
	                //String arg0,String arg1...
	                int i = 0;
	                //该字符串用来保存形参的参数名称,
	                //调用invoke方法的时候会用到这些
	                //名称的列表
	                String args = "";
	                //该字符串保存了形参的字节码文件
	                //就像Object.class这样的,用来
	                //转发调用请求时的参数类型
	                String pclazz = "";
	                //写入形参列表
	                for (Class<?> parameter : parameters)
                    {
	                    sb.append(parameter.getCanonicalName()).append(" arg").append(i);
	                    args+="arg"+i;
	                    pclazz+=parameter.getCanonicalName()+".class";
	                    if(i != parameters.length - 1)
	                    {
	                    	sb.append(",");
	                    	args+=",";
	                    	pclazz+=",";
	                    }
	                    i++;
                    }
	                
	                //这块实在不知道注释怎么写。。。。。。
	                sb.append(")\n\t{\n");
	                sb.append("\t\tObject obj = null;\n");
	                sb.append("\t\ttry\n\t\t{\n");
	                sb.append("\t\t\tobj = h.invoke(");
	                sb.append(clazz.getCanonicalName()+".class.getMethod(\""+method.getName()+"\","+(parameters.length == 0 ? "new Class<?>[]{}" : pclazz)+"),");
	                sb.append((parameters.length == 0 ? "new Object[]{}" : args));
	                sb.append(")");
	                sb.append(";\n");
	                sb.append("\t\t}\n\t\tcatch (Exception e)\n\t\t{\n\t\t\te.printStackTrace();\n\t\t}\n");
	                sb.append("\t\treturn");
	                
	                //写入返回值,不过要注意的是基本类型
	                //如果直接返回不强制转换为包装类型的
	                //话会出现ClassCastException
	                //Object cannot be cast to primitive type
	                if(returnType != void.class)
	                {
	                	if(returnType == boolean.class)
	                		sb.append(" (Boolean)");
	                	else if(returnType == int.class)
	                		sb.append(" (Integer)");
	                	else if(returnType == byte.class)
	                		sb.append(" (Byte)");
	                	else if(returnType == short.class)
	                		sb.append(" (Short)");
	                	else if(returnType == long.class)
	                		sb.append(" (Long)");
	                	else if(returnType == float.class)
	                		sb.append(" (Float)");
	                	else if(returnType == double.class)
	                		sb.append(" (Double)");
	                	else if(returnType == char.class)
	                		sb.append(" (Character)");
	                	else
	                		sb.append(" ("+returnType.getCanonicalName()+")");
	                	sb.append("obj");
	                }
	                sb.append(";");
	                sb.append("\n\t}\n");
                }
            }
		}
	}
}


字节数组实现的类加载器  
package question3;


/*
 * Copyright (c) 2005 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */


import java.util.Map;

/**
 * A class loader which loads classes from byte arrays.
 * 
 * <p><b>This is NOT part of any API supported by Sun Microsystems.
 * If you write code that depends on this, you do so at your own
 * risk.  This code and its internal interfaces are subject to change
 * or deletion without notice.</b></p>
 * @author Peter von der Ahé
 */
public class ByteArrayClassLoader extends ClassLoader {
    /**
     * Maps binary class names to class files stored as byte arrays.
     */
    private Map<String, byte[]> classes;
    
    /**
     * Creates a new instance of ByteArrayClassLoader
     * @param classes a map from binary class names to class files stored as byte arrays
     */
    public ByteArrayClassLoader(Map<String, byte[]> classes) {
        this.classes = classes;
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        try {
            return super.loadClass(name);
        } catch (ClassNotFoundException e) {
            byte[] classData = classes.get(name);
            return defineClass(name, classData, 0, classData.length);
        }
    }
}


内存文件管理器 基于javax.tools api
package question3;


/*
 * Copyright (c) 2006 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */



import java.io.ByteArrayOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;

import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.SimpleJavaFileObject;


/**
 * A file manager for compiling strings to byte arrays.
 * This file manager delegates to another file manager
 * to lookup classes on boot class path.
 * 
 * <p><b>This is NOT part of any API supported by Sun Microsystems.
 * If you write code that depends on this, you do so at your own
 * risk.  This code and its internal interfaces are subject to change
 * or deletion without notice.</b></p>
 * @author Peter von der Ahé
 */
public final class MemoryFileManager extends ForwardingJavaFileManager {
    /**
     * Maps binary class names to class files stored as byte arrays.
     */
    private Map<String, byte[]> classes;

    /**
     * Creates a JavaFileObject representing the given compilation unit.
     * @param name a name representing this source code, for example, the name of a class
     * @param code a compilation unit (source code for a Java program)
     * @return a JavaFileObject represtenting the given compilation unit
     */
    public static JavaFileObject makeSource(String name, String code) {
        return new JavaSourceFromString(name, code);
    }
    
    /**
     * Construct a memory file manager which delegates to the specified
     * file manager for unknown sources.
     * @param fileManager a file manager used to look up class files on class path, etc.
     */
    public MemoryFileManager(JavaFileManager fileManager) {
        super(fileManager);
        classes = new HashMap<String, byte[]>();
    }
    
    /**
     * Get a class loader which first search the classes stored
     * by this file mananger.
     * @return a class loader for compiled files
     */
    @Override
    public ClassLoader getClassLoader(Location location) {
        return new ByteArrayClassLoader(classes);
    }

    @Override
    public JavaFileObject getJavaFileForOutput(Location location,
                                               String name,
                                               Kind kind,
                                               FileObject originatingSource)
        throws UnsupportedOperationException
    {
        if (originatingSource instanceof JavaSourceFromString) {
            return new JavaClassInArray(name);
        } else {
            throw new UnsupportedOperationException();
        }
    }

    protected static URI uriFromString(String uri) {
        try {
            return new URI(uri);
        } catch (URISyntaxException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /**
     * A file object representing a Java class file stored in a byte array.
     */
    private class JavaClassInArray extends SimpleJavaFileObject {

        private String name;

        /**
         * Constructs a JavaClassInArray object.
         * @param name binary name of the class to be stored in this file object
         */
        JavaClassInArray(String name) {
            super(uriFromString("mfm:///" + name.replace('.','/') + Kind.CLASS.extension),
                  Kind.CLASS);
            this.name = name;
        }

        public OutputStream openOutputStream() {
            return new FilterOutputStream(new ByteArrayOutputStream()) {
                public void close() throws IOException {
                    out.close();
                    ByteArrayOutputStream bos = (ByteArrayOutputStream)out;
                    classes.put(name, bos.toByteArray());
                }
            };
        }
    }

    /**
     * A file object used to represent source coming from a string.
     */
    private static class JavaSourceFromString extends SimpleJavaFileObject {
        /**
         * The source code of this "file".
         */
        final String code;
        
        /**
         * Constructs a new JavaSourceFromString.
         * @param name the name of the compilation unit represented by this file object
         * @param code the source code for the compilation unit represented by this file object
         */
        JavaSourceFromString(String name, String code) {
            super(uriFromString("mfm:///" + name.replace('.','/') + Kind.SOURCE.extension),
                  Kind.SOURCE);
            this.code = code;
        }

        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
            return code;
        }
    }

}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值