前置知识:
假设现在有这么一个需求:
创建了一个接口A,里面有display()函数、select()函数、add()函数,类AImpl实现了这三个函数,类AstaticProxy作为类AImpl的日志类,在AImpl每个函数执行完打印“调用了display函数”,调用了"select函数"...类似对AImpl进行装饰
即如下实现代码:
//接口Ainterface
public interface Ainterface {
public void display();
public void select();
public void add();
}
//类AImple
public class AImpl implements Ainterface{
public void display()
{
System.out.println("display");
}
public void select(){
System.out.println("select");
}
public void add()
{
System.out.println("add");
}
}
//AImpl的日志类AstaticProxy,在AImpl实例上添加功能
public class AstaticProxy implements Ainterface{
private Ainterface aimpl;
public AstaticProxy(Ainterface a){
this.aimpl = a;
}
public void display()
{
aimpl.display();
System.out.println("调用了display");
}
public void select(){
aimpl.select();
System.out.println("调用了select");
}
public void add()
{
aimpl.add();
System.out.println("调用了add");
}
}
以上就完成了一个静态代理
在主函数中进行测试:
public class AProxyTest {
public static void main(String[] args) throws Exception
{
Ainterface a = new AImpl();
a.add();
a.display();
a.select();
Ainterface aproxy = new AstaticProxy(a);
aproxy.add();
aproxy.display();
aproxy.select();
}
}
这样代理类AstaticProxy中进行的行为是AImpl多余的,不会影响原本的AImpl类,这就叫静态代理。
你也观察到了,三个方法打印,就要写三遍,而且是高度重复的代码,又或者是要在每个函数前面加同一个自定义函数。如果类似Map接口,方法多到离谱,也要冗余的写十几遍吗?此时就有了动态代理。
java.lang.reflect包下的Proxy类和InvocationHandler接口组合使用就能创建一个动态代理实例
现在让我们舍弃AstaticProxy类,尝试写AdynamicProxy的动态代理类
这个代理类需要实现InvocationHandler接口,该接口内只有一个方法需要实现,就是invoke
在invoke内重写我们需要在每个方法内添加的内容。
在这里我们先不用关心怎么调用这个invoke(也就是如何传参),只需要知道在invoke内怎么使用这三个参数,proxy指代理对象本身,即new AdynamicProxy生成的对象,method是调用的方法,如add();args是调用方法传的参数,如add()无参方法就是null。
//动态代理类AdynamicProxy,代理实现了Ainterface的类
public class AdynamicProxy implements InvocationHandler {
private Ainterface a;
public AdynamicProxy(Ainterface a)
{
this.a = a;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
Object result = method.invoke(a, args);
String methodName = method.getName();
System.out.println("调用了"+methodName);
return result;
}
}
使用这个动态代理类,用Proxy.newProxyInstance
生成代理类对象,看这个函数需要的参数:
第一个参数需要一个ClassLoader,通用写法.class.getClassLoader()
,第二个参数传接口数组,可以写new Class[]{Ainterface.class}
,也可以用通用写法a.getClass().getInterfaces()
,第三个参数传实现了InvocationHandler的代理类,这样就生成了代理类实例。
**调用代理类实例的方法,会执行代理类的invoke方法。**通过反射Object result = method.invoke(a, args);
调用了AImpl的对应方法
public class AProxyTest {
public static void main(String[] args) throws Exception
{
Ainterface a = new AImpl();
Ainterface aproxy = (Ainterface) Proxy.newProxyInstance(Ainterface.class.getClassLoader(), new Class[]{Ainterface.class}, new AdynamicProxy(a));
aproxy.add();
aproxy.display();
}
}
且我们看到Proxyk可以序列化。
我们分析一下newProxyInstance怎么创建的动态代理实例,我们的动态代理类AdynamicProxy里面只有一个构造函数和invoke方法,当然不能直接new生成。这个动态代理实例实际的装配过程就在newProxyInstance。
该函数内大多数代码都是安全检查和获取访问权限,重点在以下三句。最重要的就是getProxyClass0方法。
getProxyClass0方法内,调用了get方法查找缓存内有无已生成的代理类
这里proxyClassCache是一个WeakCache,WeakCache的get方法如果没有查找到对应键值,会创建一个新的条目,具体创建细节此处省略。
ProxyClassCache的键是对接口的哈希,如调用的Key1方法,值是ProxyClassFactory工厂类生成的类
在ProxyClassFactory内就生成了代理实例的类名
ProxyGenerator.generateProxyClass生成代理实例的字节码,defineClass0加载字节码
该类下调用的generateClassFile()
该方法遍历向每个方法中添加了generateMethod()方法,而generateMethod则是生成后的invoke内的代码,到这里就结束分析啦
所以代理类的实现是重新生成了一个代理对象的class文件,该文件内依此向每个方法添加invoke的内容,最后defineClass加载字节码。让我们来找找这个class文件,验证一下分析。
上文介绍了ProxyGenerator.generateProxyClass()方法生成了代理类的字节码文件,我们将这个虚拟机中的文件输出出来。
我们调用简化版的generateProxyClass,随便取个名字,传入a.getClass().getInterface(),输出文件
public class AProxyTest {
public static void main(String[] args) throws Exception
{
Ainterface a = new AImpl();
byte[] classFile = ProxyGenerator.generateProxyClass("org.example.AProxyExtract", a.getClass().getInterfaces());
try(FileOutputS