Java的动态代理是学习Java的时候一个不好理解的知识点,本文将分享我在学习这个知识点中的笔记和理解。
Java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口可以生成JDK动态代理类或动态代理对象。
Proxy提供用于创建动态代理类和代理对象的静态方法,也是所有动态代理类的父类。
如果我们在程序中为一个或多个接口动态地生成实现类,就可以使用Proxy来创建动态类;如果需要为一个或多个接口动态地创建实例,也可以使用Proxy来创建动态代理实例。
Proxy提供了如下两个方法来创建动态代理类和动态代理实例:
static Class<?>getProxyClass(ClassLoader loader,Class<?>… interfaces)
创建一个动态代理类所对应的Class对象,该代理类将实现interfaces所指定的多个接口,第一个ClassLoader指定生成动态代理类的类加载器。
static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandlerh)
直接创建一个动态代理对象,该代理对象的实现类实现了interfaces指定的系列接口,执行代理对象的每个方法时都会被替换执行InvocationHandler对象的invoke方法。
实际上,采用第一种方式获取动态代理类之后,当程序需要通过该代理类来创建对象时一样需要传入一个InvocationHandler对象。也就是说,系统生成的每个代理对象都有一个与之关联的InvocationHandler对象。执行动态代理对象的每个方法时,都是执行InvocationHandler对象的invoke方法。
程序中生成动态代理对象可以先生成一个动态代理类,然后通过动态代理类来创建代理对象的方式来生成一个动态代理对象。
上面过程可以简化成下图的过程。
上面便是使用Java动态代理的一个基础,在贴demo代码之前,很多人学到了这里可能或多或少有一个困惑:我知道怎么去创建一个动态代理对象,不就是按这个流程去实现几个接口和类吗?可是又有什么用呢?自己new一个对象用不就行了吗?还代理来代理去的。确实,如果没有实际的应用场景,就很难体会到这样写的一个优势。
其实我们在平时做项目的时候可能会出现这样一个情况:一段代码可以在很多类里面重复使用。如果代码量少,重复的地方不多,那我们自然可以复制粘贴,如下所示的示意图。
示意图深色的部分就代表重复的部分。假如文件数量多的话,某一天需要对深色部分的代码修改,那意味着我们需要打开每一份源文件修改,显然维护这段代码的工作量将是巨大的。
有些经验的程序员可能会想到将这部分代码放在一个类的方法中,由其他代码调用即可,这样只要修改这一段代码,降低了维护的复杂度。但是这个时候这些调用这个方法的代码和这个方法又耦合了,这个时候动态代理的作用便体现出来了。
下面举一个例子。这里有一个Dog接口,里面定义了两个方法,然后用一个MyDog类实现这个接口。
public interface Dog {
public void info();
public void run();
}
public class MyDog implements Dog{
@Override
public void info() {
System.out.println("我是狗蛋子");
}
@Override
public void run() {
System.out.println("我跑的比**回快");
}
}
现在要求在调用info()和run()方法时能够调用一个通用方法,但就像上面提到的,我们既不想用复制粘贴的方式实现,也不想通过硬编码的方式调用该方法。
public class DogUtil {
public void method1(){
System.out.println("111111111111111");
}
public void method2(){
System.out.println("222222222222222");
}
}
我们可以借助Proxy类和InvocationHandler接口实现,使程序调用info()方法和run()方法时,系统将自动把method1()和method2()两个方法插入到info()和run()方法中执行。
程序的关键在于写一个InvocationHandler实现类,该实现类的invoke方法将会作为代理对象的方法实现。
public class MyInvokationHandler implements InvocationHandler {
//需要被代理的对象
private Object target;
public void setTarget(Object target){
this.target=target;
}
//执行动态代理对象的所有方法时,都会被替换成执行如下的invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
DogUtil t = new DogUtil();
t.method1();
//通过反射以target作为主调来执行method方法
//实现了调用target对象的原有方法
Object result = method.invoke(target,args);
t.method2();
return result;
}
}
然后我们定义一个MyProxyFactory类来为指定的target生成动态代理对象。
public class MyProxyfactory {
//为指定target生成动态代理对象
public static Object getProxy(Object target) throws Exception{
//创建一个MyInvokationHandler对象
MyInvokationHandler handler = new MyInvokationHandler();
//为MyInvokationHandler设置target对象
handler.setTarget(target);
//创建并返回一个动态代理
return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),handler);
}
}
这个类提供一个getProxy()方法,为target对象生成一个动态代理对象,这个对象与target实现了相同的接口,当程序调用动态代理对象的指定方法时,实际上将执行MyInvokationHandler对象的invoke方法,也就是说我们调用动态代理对象的方法时,程序的执行步骤为:
- 创建DogUtil实例
- 执行method1()方法
- 使用反射以target作为调用者执行info()方法或者run()方法
- 执行method2()方法
下面在主程序中测试一下。
public class Test {
public static void main(String[] args) throws Exception{
Dog target = new MyDog();
Dog dog = (Dog)MyProxyfactory.getProxy(target);
dog.info();
dog.run();
}
}
上面程序中的dog实际上是动态代理对象,只是它实现了Dog接口,所以也可以当成Dog对象使用。程序在执行dog的info()和run()之前会先执行DogUtil的method1(),然后再执行dog的info()和run(),最后执行DogUtil的method2()。测试结果如下。
我们可以看到动态代理很灵活的实现了解耦。当我们使用Proxy生成一个动态代理时,通常都是为指定的目标对象来生成动态代理,这种动态代理也被称为AOP代理,也是Spring中的一个重要内容。