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