上一篇说了JDK动态代理如何使用,这里就有个想法,不使用官方的,自己能否模拟一个,网上一搜还真有不少同学已经实现了,但那句话咋说的:纸上得来终觉浅,绝知此事要躬行。
- 官方接口:
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
}
- 自定义接口(尽量和官方一致):
public interface MyInvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
//自定义类加载器
public static Object newProxyInstance(MyClassLoader myClassLoader,
Class<?>[] interfaces,
MyInvocationHandler h)
//不用自定义类加载器
public static Object newProxyInstance(Object object,
Class<?>[] interfaces,
MyInvocationHandler h)
}
- 实现代码:
public class MyProxyInvocationHandler<T> implements MyInvocationHandler {
private T target;
public MyProxyInvocationHandler(T target) {
this.target = target;
}
/**
*
* @param proxy 代理后的实例对象
* @param method 对象被调用的方法
* @param args 调用参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target, args);
after();
return result;
}
public void before() {
System.out.println("我的名字之前...");
}
public void after() {
System.out.println("我的名字之后...");
}
}
- 自定义类加载器:
public class MyClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) {
//class文件路径
String classPath = MyClassLoader.class.getResource("").getPath() + "/" + name + ".class";
System.out.println(classPath);
//class的包名+文件名
String className = MyClassLoader.class.getPackage().getName() + "." + name;
System.out.println(className);
//读取class,此处借助hutool工具
byte[] bytes = FileUtil.readBytes(classPath);
//加载
return defineClass(className, bytes, 0, bytes.length);
}
}
- 自定义代理类:
public class MyProxy {
public static final String ln = "\r\n";
public static Object newProxyInstance(Object object, Class<?>[] interfaces, MyInvocationHandler h) {
try {
//1 java源码
String src = src(interfaces);
//2. 源码输出到java文件中,并保存到本地
File file = disk(src);
//3、将java文件编译成class文件
compile(file);
//4.1 使用自定义加载器将class加载进jvm里
Class proxyClass = myClassLoader.findClass("$Proxy0");
//5、获取构造方法
Constructor constructor = proxyClass.getConstructor(MyInvocationHandler.class);
//构造代理对象
return constructor.newInstance(h);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 结构固定
* @param interfaces
* @return
*/
private static String src(Class<?>[] interfaces) {
// 找包名
String packageName = interfaces[0].getPackage().getName();
StringBuffer sb = new StringBuffer();
sb.append("package " + packageName + ";" + ln);
sb.append("import java.lang.reflect.Method;" + ln);
sb.append("public class $Proxy0 implements " + interfaces[0].getName() + "{" + ln);
sb.append("private MyInvocationHandler h;"+ln);
sb.append("public $Proxy0(MyInvocationHandler h) { " + ln);
sb.append("this.h = h;"+ln);
sb.append("}" + ln);
for (Method m : interfaces[0].getMethods()) {
sb.append("public " + m.getReturnType().getName() + " "
+ m.getName() + "() {" + ln);
sb.append("try{" + ln);
sb.append("Method m = " + interfaces[0].getName()
+ ".class.getMethod(\"" + m.getName()
+ "\",new Class[]{});" + ln);
sb.append("this.h.invoke(this,m,null);" + ln);
sb.append("}catch(Throwable e){" + ln);
sb.append("e.printStackTrace();" + ln);
sb.append("}"+ln);
sb.append("}"+ln);
}
sb.append("}" + ln);
System.out.println(sb.toString());
return sb.toString();
}
/**
* 源码保存到磁盘
* @param classSrc
* @return
*/
public static File disk(String classSrc) {
String filePath = MyProxy.class.getResource("").getPath() + "$Proxy0.java";
//此处借助hutool工具
return FileUtil.writeBytes(classSrc.getBytes(), filePath);
}
/**
* 编译java文件
* @param file
*/
public static void compile(File file) {
try {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager manage = compiler.getStandardFileManager(null, null, null);
Iterable iterable = manage.getJavaFileObjects(file);
JavaCompiler.CompilationTask task = compiler.getTask(null, manage, null, null, null, iterable);
task.call();
manage.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 测试代码:
public interface User {
void myName();
}
public class UserImpl implements User {
@Override
public void myName() {
System.out.println("我的名字叫张三");
}
}
public class ProxyTest {
public static void main(String[] args) {
User user = new UserImpl();
User userProxy = (User) MyProxy.newProxyInstance(new MyClassLoader(), user.getClass().getInterfaces(), new MyProxyInvocationHandler(user));
userProxy.myName();
}
}
输出:
我的名字之前...
我的名字叫张三
我的名字之后...
我们也可以不使用自定义类加载器,代码如下改造:
public static Object newProxyInstance(Object object, Class<?>[] interfaces, MyInvocationHandler h) {
try {
//1 java源码
String src = src(interfaces);
//2. 源码输出到java文件中,并保存到本地
File file = disk(src);
//3、将java文件编译成class文件
compile(file);
//4、使用自定义加载器将class加载进jvm里
// Class proxyClass = myClassLoader.findClass("$Proxy0");
//使用URLClassLoader加载代理class
URL[] urls = new URL[]{new URL("file:/" + System.getProperty("user.dir") + "/src")};
URLClassLoader urlClassLoader = new URLClassLoader(urls);
Class proxyClass = urlClassLoader.loadClass(object.getClass().getPackage().getName() + ".$Proxy0");
//5、获取构造方法
Constructor constructor = proxyClass.getConstructor(MyInvocationHandler.class);
//构造代理对象
return constructor.newInstance(h);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public class ProxyTest {
public static void main(String[] args) {
User user = new UserImpl();
User userProxy = (User) MyProxy.newProxyInstance(user, user.getClass().getInterfaces(), new MyProxyInvocationHandler(user));
userProxy.myName();
}
}
输出:
我的名字之前...
我的名字叫张三
我的名字之后...
流程还是比较清晰的:
- 代理源码手动创建,格式较为固定
- 源码保存到本地.java文件中
- 调用编译器对.java编译成.class文件
- 使用类加载器加载.class文件
- 获取构造方法
- 获取代理对象
问题:.java文件必须先写到本地,编译后再从本地读取,能否省去读写磁盘操作,直接内存中搞定?