代理在我们日常开发过程中有着很重要的角色,它可以处理一些日志记录,权限控制,事务等。主要分为静态代理和动态代理,他们的主要区别是有没有源文件,静态代理是有源文件的,应用起来比较直观但是稍有浪费且不智能,所以我们一般使用的是动态代理,他是在程序运行期间动态生成的,程序加载动态生成的字节码生成Class对象加以运用,灵活性高。
下面我们介绍一下jdk的动态代理。
首先声明一个接口IHello.java
public interface IHello {
void sayHello(String name);
}
在创建一个接口的具体的实现类HelloService.java
public class HelloService implements IHello {
@Override
public void sayHello(String name) {
System.out.println("hello "+name);
}
}
使用jdk的动态代理,代理的逻辑必须写在InvocationHandler的实现类中,当调用接口的具体方法时,实际上是调用的InvocationHandler中的invoke方法,这个在后面会有详细的解释。
HelloHandler.java
public class HelloHandler implements InvocationHandler {
/**
* 调用的具体的对象
*/
private Object concreteObj;
public HelloHandler(IHello hello) {
concreteObj = hello;
}
/**
* @param proxy 生成的代理对象的回填
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("proxy:"+proxy.getClass().getName());
long start = System.currentTimeMillis();
Object o = method.invoke(concreteObj, args);
System.out.println("cost time :" + (System.currentTimeMillis() - start) + "ms");
return o;
}
}
需要的类都准备好了,我们来测试一下
public static void main(String[] args) {
IHello hello = (IHello) Proxy.newProxyInstance(IHello.class.getClassLoader(), new Class[]{IHello.class}, new HelloHandler(new HelloService()));
hello.sayHello("test");
}
结果为:
proxy:com.sun.proxy.$Proxy0
hello test
cost time :0ms
我们这个动态代理的作用就是统计一下方法调用的时间。
我们发现了两个问题:
- 这个HelloHandler中invoke方法第一个参数proxy是怎么填入的并且他的class的name为何为com.sun.proxy.$Proxy0
- 我调用的是IHello中的sayHello方法,为啥他会走到HelloHandler中的invoke方法。
我们带着这两个疑问继续看起来。
我们先看一下Proxy是如何newProxyInstance的
下面我们看一下生成的字节码是怎样的
public static void main(String[] args) {
createProxyClassFile();
}
public static void createProxyClassFile() {
String name = "ProxyHello";
byte[] data = ProxyGenerator.generateProxyClass(name, new Class[]{IHello.class});
try {
FileOutputStream out = new FileOutputStream(name + ".class");
out.write(data);
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
创建的文件这这样的
public final class ProxyHello extends Proxy implements IHello {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public ProxyHello(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void sayHello(String var1) throws {
try {
super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m3 = Class.forName("com.pajk.homecare.unittest.proxy.IHello").getMethod("sayHello", new Class[]{Class.forName("java.lang.String")});
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
根据这个文件,我们看一下,当调用代理的sayHello方法时,调用的是handler的invoke方法,传入的第一个参数是this,所以就是Proxy的实例了。所以上面提出的两个问题得解。