如果不懂动态代理就无法深入理解当下最流行的诸多框架的原理,如spring。
如果不懂动态代理大厂的offer很可能和你插肩而过。
要理解动态代理首先要理解代理模式
什么是代理模式?
有一个打印机的类
public class Printer {
public void print(){
System.out.println("打印!");
}
}
我想在打印之前先记录一下日志怎么做?
最简单的方法:在打印的功能前面直接加上记录日志的功能。
public class Printer {
public void print(){
System.out.println("记录日志!");
System.out.println("打印!");
}
}
看上去好像没有问题,但是我们修改了打印机的源代码,破坏了面向对象的开闭原则,有可能影响到其它功能。怎么解决呢?很容易可以想到,既然不能修改原来的代码,那我新建一个类吧。
public class LogPrinter extends Printer {
public void print(){
System.out.println("记录日志!");
System.out.println("打印!");
}
}
这个类继承了打印机的类,重写了打印机的print方法,提供了记录日志的功能,以后需要打印机的时候使用这个类就好。问题似乎得到了解决,我们可以在这个解决方案的基础上进一步的优化:
先抽象出一个接口:
public interface IPrinter {
void print();
}
打印机类实现这个接口:
public class Printer implements IPrinter {
public void print(){
System.out.println("打印!");
}
}
创建打印机代理类也实现该接口,在构造函数中将打印机对象传进去,实现接口的打印方法时调用打印机对象的打印方法并在前面加上记录日志的功能:
public class PrinterProxy implements IPrinter {
private IPrinter printer;
public PrinterProxy(){
this.printer = new printer();
}
@Override
public void print() {
System.out.println("记录日志");
printer.print();
}
}
试一把吧:
public class Test {
public static void main(String[] args) {
PrinterProxy proxy = new PrinterProxy();
proxy.print();
}
}
结果出来了:
记录日志
打印
以后我们就可以直接实例化PrinterProxy对象调用它的打印方法了,这就是静态代理模式,通过抽象出接口让程序的扩展性和灵活性更高了。
静态代理是完美无缺的吗?
考虑一下,如果我的打印机类中还有别的方法,也需要加上记录日志的功能,就不得不将记录日志的功能写n遍。进一步如果我还有电视机,电冰箱的类里面的所有方法也需要加上记录日志的功能,那要重复的地方就更多了。
怎么办?
动态代理闪亮登场:
要想不重复写记录日志的功能,针对每一个接口实现一个代理类的做法肯定不可行了,可不可以让这些代理类的对象自动生成呢?
Jdk提供了invocationHandler接口和Proxy类,借助这两个工具可以达到我们想要的效果。
invocationHandler接口上场:
//Object proxy:被代理的对象
//Method method:要调用的方法
//Object[] args:方法调用时所需要参数
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
接口里只有一个方法invoke,这个方法非常重要,先混个脸熟,稍后解释。
Proxy类上场,它里面有一个很重要的方法 newProxyInstance:
//CLassLoader loader:被代理对象的类加载器
//Class> interfaces:被代理类全部的接口
//InvocationHandler h:实现InvocationHandler接口的对象
public static Object newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h) throws IllegalArgumentException
调用Proxy的newProxyInstance方法可以生成代理对象
一切准备就绪动态代理模式千呼万唤始出来:
接口IPrinter 和 该接口的实现类 Printer的代码同前。
实现一个类,该类用来创建代理对象,它实现了InvocationHandler接口:
public class ProxyHandler implements InvocationHandler {
private Object targetObject;//被代理的对象
//将被代理的对象传入获得它的类加载器和实现接口作为Proxy.newProxyInstance方法的参数。
public Object newProxyInstance(Object targetObject){
this.targetObject = targetObject;
//targetObject.getClass().getClassLoader():被代理对象的类加载器
//targetObject.getClass().getInterfaces():被代理对象的实现接口
//this 当前对象,该对象实现了InvocationHandler接口所以有invoke方法,通过invoke方法可以调用被代理对象的方法
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);
}
}
被代理的对象targetObject可以通过方法参数传进来:
public Object newProxyInstance(Object targetObject){
this.targetObject=targetObject;
我们重点来分析一下这段代码:
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(),this);
动态代理对象就是通过调用这段代码被创建并返回的。
方法有三个参数:
第一个参数:
targetObject.getClass().getClassLoader():targetObject对象的类加载器。
第二个参数:
targetObject.getClass().getInterfaces():targetObject对象的所有接口
第三个参数:
this:也就是当前对象即实现了InvocationHandler接口的类的对象,在调用方法时会调用它的invoke方法。
再来看一下这段代码:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在这里可以通过判断方法名来决定执行什么功能
System.out.println("记录日志");
//调用被代理对象的方法
return method.invoke(targetObject, args);
}
这个方法就是生成的代理类中的方法被调用时会去自动调用的方法,可以看到在这个方法中调用了被代理对象的方法: method.invoke(targetObject, args);
我们可以在这里加上需要的业务逻辑,比如调用方法前记录日志功能.
见证奇迹的时刻到了:
public class Test {
public static void main(String[] args){
ProxyHandler proxyHandler=new ProxyHandler();
IPrinter printer=(IPrinter) proxyHandler.newProxyInstance(new Printer());
printer.print();
}
}
打印结果:
记录日志
打印
当执行printer.print();时会自动调用invoke方法,很多初学者不理解为什么能调用这个方法,回忆一下创建代理对象的时候是通过
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(),this);
来创建的,方法的第三个参数this是实现了 InvocationHandler 接口的对象, InvocationHandler 接口有invoke方法。现在有点思路了吧~
将被代理的对象作为参数传入就可以执行里面的任意方法,所有的方法调用都通过invoke来完成。不用对每个方法进行处理,动态代理是不是很简洁。
代理模式的定义:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用,通俗的来讲代理模式就是我们生活中常见的中介,动态代理和静态代理的区别在于静态代理我们需要手动的去实现目标对象的代理类,而动态代理可以在运行期间动态的生成代理类。