为什么要用代理?
假设让你在项目的所有类的方法中加入打印日志这段代码,如何在不修改已有代码的前提下,完成这个需求呢?这就需要用到代理了。
静态代理:
例如,如何在不修改代码前提下,在类hk中的print()方法中加入打印日志
public class hk {
public void print(){
System.out.println("打印!");
}
}
首先应实现一个接口
public interface abc {
void print();
}
public class hk implements abc{
public void print(){
System.out.println("打印!");
}
}
然后需要一个代理对象
public class ProxyHandler implements abc{
abc ABC;
public ProxyHandler() {
ABC=new hk();
}
public void print(){
System.out.println("日志");
this.ABC.print();
}
}
执行下列代码后就能完成需求了
ProxyHandler proxy = new ProxyHandler();
proxy.print();
静态代理缺点:需要我们去写代理对象的代码,当类的方法很多,或者有许多的类的时候,工作量非常大。
---------------------------------------------------------------
动态代理:动态代理不需要我们去写代理对象的代码,那如何生成代理对象呢,这就得了解类的生命周期。
之前我们是如何创建一个对象的呢?
例如Person person= new Person()
实际上创建对象person经历了编译,类加载,实例化的过程。
现在我们没有Java源文件了,如何创建对象呢?带着这个问题我们可以先看看动态代理的实现,再深入源码,查找答案。
public class hk implements abc{
public void print(){
System.out.println("打印!");
}
}
public interface abc {
void print();
}
public class ProxyHandler implements InvocationHandler {
private Object targetObject;//被代理的对象
public Object newProxyInstance(Object targetObject){
this.targetObject = targetObject;
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(),this); //获得代理对象
}
//该方法在代理对象调用方法时调用
@Override
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable {
System.out.println("记录日志");
return method.invoke(targetObject,args);
}
}
执行以下代码就完成了需求
public class Text {
public static void main(String[] args){
ProxyHandler proxyHandler=new ProxyHandler();
abc ABC = (abc) proxyHandler.newProxyInstance(new hk());
ABC.print();
}
}
---------------------------------------------------------------
接下来深入源码看看Proxy.newProxyInstance是如何获取代理对象的(以下的是主要的代码,并不是完整的源码)。
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException
{
Class<?> cl = getProxyClass0(loader, interfaces); //获得Class对象,这行代码做了两件事,一:生成字节码文件。二:将字节码文件类加载,生成Class对象
final Constructor<?> cons = cl.getConstructor(constructorParams); //从Class对象中拿到构造函数
return cons.newInstance(new Object[]{h}); //通过反射拿到实例对象
}
private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces)
{
return proxyClassCache.get(loader, interfaces);
}
public V get(K key, P parameter)
{
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
}
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
// 所有代理类对象的前缀
private static final String proxyClassNamePrefix = "$Proxy";
// 代理类的包名
String proxyPkg = null;
//生成代理类的类名
long num = nextUniqueNumber.getAndIncrement(); //计数器
String proxyName = proxyPkg + proxyClassNamePrefix + num; //所以生成的代理类的名称应该是$Proxy0,$Proxy1,$Proxy2......这种规则
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags); //生成字节码数组,相当于字节码文件
return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); //返回Class对象
}
private Static native Class defineClass0(ClassLoader loader,String name,byte[] b,int off,int len); //本地方法,调用操作系统的方法,生成Class对象
---------------------------------------------------------------
通过分析可以知道下列三段代码实现了 生成字节码文件,类加载生成Class文件,实例化代理对象。
这就是如何在不写代码也就是没有Java源文件如何创建对象的答案。
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags); //生成字节码数组,相当于字节码文件
return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); //返回Class对象
cons.newInstance(new Object[]{h}); //通过反射拿到实例对象
创建出来的字节码反编译后主要代码如下:
public final class $Proxy0 extends Proxy implements abc{
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m4;
public final void print(String paramString)
{
this.h.invoke(this, m4, new Object[] { paramString });
}
这就是代理对象的代码了,它是继承Proxy并且实现abc接口的一个类。
---------------------------------------------------------------
动态代理大概过程:
所以无论执行什么方法,都会执行invoke方法,从而达到增强代码的目的。