1. JDK动态代理最小案例
JDK 动态代理干什么 怎么用,我想并不需要复述,这里快速贴上本案列的代码
委托类:
public class Xiaobai implements House {
@Override
public void buy() {
System.out.println("我是小白,我要买房");
}
}
委托接口:
public interface House {
public void buy();
}
代理类
public class JdkProxy implements InvocationHandler {
private Object target;
public Object getProxy(Object target2) {
this.target = target2;
Class<? extends Object> aClass = target.getClass();
return Proxy.newProxyInstance(aClass.getClassLoader(), aClass.getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是一个代理");
method.invoke(target,args);
System.out.println("代理结束的啦");
return proxy;
}
}
调用:
House proxy = (House) new JdkProxy().getProxy(new Xiaobai());
System.out.println(proxy.getClass().getName());
proxy.buy();
打印结果如下
2.获取动态代理生成的class文件
这里如果直接打印生成的House的类,会发现,他不并不是原来我实现的 Xiaobai 的实例对象,如图所示
所以要探究他的原理如何,就要看看他生成对应的代理文件是怎么样的
//获取代理类
Class<? extends House> aClass = proxy.getClass();
//获取代理对象CLASS中的内容,这里的$Proxy0 是 上面proxy.getClass().getName() 打印出来的最后一位
byte[] chyxxxes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{House.class});
//保存class内容到 xxxx.class中,后面是流操作
File file = new File("xxxx.class");
FileOutputStream fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(chyxxxes);
fileOutputStream.close();
这里 就把生成的class文件保存下来了.打开对应的文件.
这里分部讲解一下 首先
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("chy.House").getMethod("buy");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
这里可以看出,他把代理对象中的方法 都用反射提取出来. 放入类属性中.这方便后面的调用
这里删除了try 以及 equals toString 这些方法的代理.只看我自己定义的接口如下:
public final class $Proxy0 extends Proxy implements House {
private static Method m3;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final void buy() throws {
super.h.invoke(this, m3, (Object[])null);
}
static {
m3 = Class.forName("chy.House").getMethod("buy");
}
其中
这个 InvocationHandler 是不是很熟悉,他就是 我们自己写代理类时候实现的那个接口
所以 JDK实现的动态代理,必须要有接口,因为 他生成的代理类,会实现对应的接口,然后重写接口里的所有方法,重写的时候,调用你自定义的 InvocationHandler.invoke
3. 尝试自己造个一样的轮子
以上所看,JDK动态代理原理其实很简单.总结起来 步骤如下:
1 . 动态创建一个类,该类实现需要代理的接口,
2. 反射获取对应的方法.
3. 生成对应的class文件,加载进JVM中.
难点在于: 如何生成class文件,并动态加载.
首先我们先去看看 JDK是怎么生成的
进入
newProxyInstance 方法一直走,发现他调用的一个内部类 ProxyClassFactory 来生成代理对象
这里生成了对应 代理对象的 的流文件 然后调用了 defineClass0 方法生成对应的 类
不过这个 defineClass0 是native 的方法,还是私有的.看来白嫖JDK是不行了.
所以 用了个LOW点的方法
4. 一个很山寨的轮子
由上可见, JDK动态代理 调用了,本地的方法生成加载了 对应的类对象, 我这边 为了方便操作, 使用javassis 字节码库进行 类的动态创建.
当然 也可以不用 字节码类库 ,思路如下:
在硬盘上生成对应的 .JAVA文件. 编译成 .class文件,动态加载.
而笔者秉着早点写完早点打游戏的心态 偷个懒
和JDK的一样先来个接口:
public interface ChyInvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
然后主要逻辑类:
public class ChyProxy {
//classPool 创建需要大量性能,作为全局使用
public static ClassPool classPool = ClassPool.getDefault();
public static Method m1 = null;
//生成代理类的名称,按照约定,动态生成的 类前面加上 $
private static final String proxyClassName = "chy.proxy.$ChyProxy";
public static Object newProxyInstance(Class<?>[] interfaces, ChyInvocationHandler invocationHandler) throws NotFoundException, CannotCompileException, IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException {
//区别每一个 代理类的名称
String className = proxyClassName + System.currentTimeMillis();
//创建一个类
CtClass ctClass = classPool.makeClass(className);
//创建代理类的时候,设置一个全局属性 invocationHandler;
ctClass.addField(CtField.make("private chy.proxy.ChyInvocationHandler invocationHandler;",ctClass));
String constructorInitMethode = "";
//因为传入的可能是多个接口,遍历每一个接口
for (Class<?> inf : interfaces) {
//拿到接口CtClass对象
CtClass infCtClass = classPool.get(inf.getTypeName());
//让代理类实现这个接口
ctClass.addInterface(infCtClass);
//拿到接口上所有方法
Method[] infMethods = inf.getDeclaredMethods();
//将接口上所有方法都在 新的代理类中实现一遍
constructorInitMethode += infMethodsHandle(infMethods,ctClass,inf);
}
String tryCath = " try {\n" +
" " +constructorInitMethode+"\n"+
" } catch (NoSuchFieldException e) {\n" +
" e.printStackTrace();\n" +
" } catch (ClassNotFoundException e) {\n" +
" e.printStackTrace();\n" +
" }";
//设置一个有参构造器,把invocationHandler 传进去,并且给 全局方法赋值
CtConstructor constructor = new CtConstructor(new CtClass[] {classPool.get(ChyInvocationHandler.class.getTypeName())}, ctClass);
constructor.setModifiers(Modifier.PUBLIC);
constructor.setBody("{this.invocationHandler=$1;"+tryCath+"}");
ctClass.addConstructor(constructor);
ctClass.writeFile("/Users/bignosecat/IdeaProjects/pg/shejimoshi/target/classes/chy/proxy/");
//获取动态生成的类
Class<?> aClass = ctClass.toClass();
//获取有参构造器
Constructor constructor1 = aClass.getDeclaredConstructor(ChyInvocationHandler.class);
//生成对应的对象
Object o = constructor1.newInstance(invocationHandler);
return o;
}
public static String infMethodsHandle(Method[] methods, CtClass ctClass, Class<?> inf) throws CannotCompileException {
//用于抽取接口方法,用反射投射到属性上;
String initMethod = "";
for (Method method : methods) {
String name = method.getName();
//获取接口的参数个数
Parameter[] parameters = method.getParameters();
//拼接接口的参数
String param = "";
int count = 0;
for (Parameter parameter : parameters) {
if(count != 0){
param = param + ",";
}
String typeName = parameter.getType().getTypeName();
param = param + typeName+ " var"+count;
count++;
}
//invocationHandler.invoke 中参数的拼接
String invokeParam = "(Object[])null";
if(count != 0){
}
//在代理类里设置一个 全局变量存放 接口方法, 用var_ 开头 如 var_buy
String methodName = "var_" + name;
ctClass.addField(CtField.make("private static java.lang.reflect.Method "+methodName+";",ctClass));
String methodStr = " public final void "+name+"("+param+") {\n" +
" try {\n" +
" invocationHandler.invoke(this, var_"+name+", "+invokeParam+");\n" +
" } catch (java.lang.Exception e) {\n" +
" throw e;\n" +
" } catch (java.lang.Throwable t1) {\n" +
" throw new java.lang.reflect.UndeclaredThrowableException(t1);\n" +
" }\n" +
" }";
//创建对应的 实现方法
ctClass.addMethod( CtMethod.make(methodStr,ctClass));
//拼接反射抽取方法
initMethod = initMethod + methodName+" = java.lang.Class.forName(\""+inf.getTypeName()+"\").getMethod(\""+name+"\",null);\n";
}
return initMethod;
}
}
5.轮子测试
在之前的案列上改进一下
委托类和接口都不变, 重写一个 ChyInvocationHandlerHandle 类:
public class ChyInvocationHandlerHandle implements ChyInvocationHandler {
private Object target;
public Object getProxy(Object target2) throws NoSuchMethodException, IllegalAccessException, IOException, InstantiationException, CannotCompileException, NotFoundException, InvocationTargetException, ClassNotFoundException {
this.target = target2;
Class<? extends Object> aClass = target.getClass();
return ChyProxy.newProxyInstance( aClass.getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是一个代理");
method.invoke(target,args);
System.out.println("代理结束的啦");
return proxy;
}
}
运行:
public static void main(String[] args) throws Exception {
House proxy = (House) new ChyInvocationHandlerHandle().getProxy(new Xiaobai());
proxy.buy();
}
结果: