代理類
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;
}
}
}