通過代理接口在內存中動態生成代理類源代碼並編譯實現的真正動態代理

代理類

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;
        }
    }

}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章