动态代理和责任链模式无论在Spring还是Mybatis中都有重要的应用,只要随着本篇文章的例子多写代码,反复的体检,就能够掌握。
动态代理的意义在于生成一个占位(又称代理对象),来代理真实对象,从而控制真实对象的访问。
先来说说什么是代理模式。假设这样一个场景,你的公司是一家软件公司,你是一位软件工程师。客户带着需求去找公司显然不会直接和你谈,而是去找需求的同事谈,此时客户会认为需求就代表公司。用一张图可以表示代理模式的含义,如下图所示:
显然客户是通过需求去访问软件工程师的,那么需求(代理对象)的意义在于什么呢?需求可以进行谈判,比如项目启动前的商务谈判,软件的价格、交付、进度的时间节点等,或者项目完成后的需求追讨收款等。需求也有可能在开发软件之前谈判失败,此时需求就会根据公司规则去结束和客户的合作关系这些都不用软件工程师来处理。因此,代理的作用就是,在真实对象访问之前或者之后加入对应的逻辑,或者根据其他规则控制是否使用真实对象,显然在这个例子里需求控制了客户对软件工程师的访问。
经过上面的讨论,我们知道需求和软件工程师是代理和被代理的关系,客户是经过需求去访问软件工程师的。此时客户就是程序的调用者,需求就是代理对象,软件工程师就是真实对象。我们需要在调用者调用对象之前产生一个代理对象,而这个代理对象需要和真实对象之间建立代理关系,所以代理必须分为两个步骤:
(1)代理对象和真实对象建立代理关系;
(2)实现代理对象的代理逻辑方法。
在Java中有多种动态代理技术,比如JDK、CGLIB、Javassist、ASM等,其中含最常用的动态代理技术有两种:一种是JDK动态代理,这是JDK自带的功能;另一种是CGLIB,这个是第三方提供的一个技术。目前,Spring常用的JDK和CGLIB,而Mybatis还是用了Javassist,无论哪种代理技术,它们的理念都是相似的。
(一)JDK动态代理
JDK动态代理是java.lang.reflect.*包提供的方式,它必须借助一个接口才能产生代理对象。所以咋们先定义接口,代码如下所示:
public interface HelloWorld {
public void sayHelloWorld();
}
然后提供实现类HelloWorldImpl来实现接口,代码如下所示:
public class HelloWorldImpl implements HelloWorld {
public void sayHelloWorld() {
System.out.println("Hello World!");
}
}
这就是最简单的Java接口和实现类的关系,此时可以动态代理了,按照我们之前的分析,先要建立起代理对象和真实服务对象的关系,然后实现代理逻辑,所以一共分为两个步骤。
在JDK动态代理中,要实现代理逻辑类必须去实现java.lang.reflect.InvoicationHandler接口,它里面定义了一个invoke方法,并提供接口数组用于挂代理对象,代码如下所示:
public class JdkProxyExample implements InvocationHandler {
//真实对象
private Object target = null;
/**
* 建立代理对象与真实对象之间的代理关系,并返回代理对象
* @param target 真实对象
* @return 返回代理对象
*/
public Object bind(Object target) {
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),this);
}
/**
* 代理方法逻辑
* @param proxy 代理对象
* @param method 当前调度方法
* @param args 当前方法参数
* @return 代理结果返回
* @throws Throwable 异常
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("进入代理逻辑方法");
System.out.println("在调度真实对象之前的服务");
Object object = method.invoke(target,args);
System.out.println("在调度真实对象之后的服务");
return object;
}
}
第1步,建立代理对象和真实对象的关系。这就是使用了bind方法去完成的,方法里面先用类的target保存了真实对象,然后通过如下代码建立并生成代理对象。
Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),this);
其中newProxyInstance方法包含3个参数。
第一个是类加载器,我们采用了target本身的类加载器。
第二个是把生成的动态代理对象下挂在哪些借口下,这个写法就是放在target实现的接口下。HelloWorldImpl对象的接口显然就是HelloWorld,代理对象可以这样声明:HelloWorld proxy = xxxx;
第三个是定义实现方法逻辑的代理类,this表示当前对象,它必须实现InvocationHandler接口的invoke方法,它就是代理逻辑方法的现实方法。
第2步,实现代理逻辑方法。invoice方法可以实现代理逻辑,invoke方法的3个参数的含义如下所示:
proxy,代理对象,就是bind方法生成的对象;
method,当前调度的方法;
args,调度方法的参数。
Object object = method.invoke(target,args);
这行代码相当于调度真实对象的方法,只是通过反射实现而已。
类比前面的例子,proxy相当于需求对象,target相当于软件工程师对象,bind方法就是建立需求和软件工程师代理关系的方法。而invoke就是需求逻辑,它将控制软件工程师的访问。
测试JDK动态代理,如下代码所示:
public class TestJdkProxyExample {
@Test
public void testJdkProxy() {
JdkProxyExample jdkProxyExample = new JdkProxyExample();
//绑定关系,因为挂载在HelloWorld下,所以声明代理对象HelloWorld proxy
HelloWorld proxy = (HelloWorld)jdkProxyExample.bind(new HelloWorldImpl());
proxy.sayHelloWorld();
}
}
首先通过bind方法绑定了代理关系,然后在代理对象调度sayHelloWorld方法是进入了代理的逻辑,测试结果如下:
此时,在调度打印Hello World之前和之后都可以加入相关的逻辑,甚至可以不调度Hello World的打印。这就是JDK动态代理,它是一种最常用的动态代理,十分重要,后面会以JDK动态代理为主讨论,所以一定要能够掌握好它。