最开始接触设计模式是从《大话设计模式》开始,虽然是用C# 写的,非常浅显易懂,后来在学习java EE 框架实践中才慢慢体会到设计模式的妙处。
代理模式是一种重要的设计模式,在mybatis和Spring IOC 中都有重要的应用,所以理解其思想,是学习spring框架的基础。java中有许多动态代理的技术,比如JDK,CGLIB,Javassist,ASM 等,其中最常用的是JDK,CGLIB(第三方提供),无论哪种代理技术,其理念都是一样的,本文以JDK为例。
什么是代理模式呢?我们想象一个这样的场景,你在一家软件公司担任软件开发工程师。客户有需求的时候会去找公司的产品经理(或者商务)谈,而不是直接找开发人员。此时客户就认为商务谈判人员就代表公司。看下面的图:
可能我们会问,为什么需要一个商务做代理呢?因为商务可以谈判,比如软件的价格,软件的上线时间,进度等,而软件开发工程师通常不适合这些,只需要专注于开发。 商务也可能在开发软件之前谈判失败,此时商务就会根据合同去与客户结束关系,如果谈判成功,就会有后期如何维护等一些列谈判问题。因此,代理就是在真实对象访问之前或者之后加入相应逻辑或者根据一些控制规则是否使用真实对象,本例中即商务控制了客户对软件开发工程师的访问。还是很抽象,对应上面的图: 此时客户就是程序的调用者,商务就是代理对象,软件工程师就是真实对象。我们要在调用者调用对象之前产生一个代理,然后让这个代理和真实对象产生联系。
有了以上认识,可以看到它的好处就是:代理类可以为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务(或者一些公共服务)。这样,我们在项目开发中一些后期加入的功能,比如日志,缓存出某些数据等这些功能,后期想加入,我们就可以使用代理来实现,而没有必要打开已经封装好的委托类,符合开闭原则。
要实现以上逻辑,需要两个步骤 :
1)建立代理对象和真实对象之间的关系 2)实现代理对象的代理逻辑方法
JDK动态代理是java.lang.reflect.*这个包提供,它需要一个接口才能产生代理对象,先定义接口:
public interface HelloWorld {
public void satHelloWorld();
}
接口实现类:
public class HelloWorldImpl implements HelloWorld{
@Override
public void satHelloWorld(){
system.out.println("hello World!")
}
}
然后就可以开始动态代理啦。按照之前的分析,先实现第一步,建立代理对象和真实对象之间的关系,然后实现其逻辑。
在JDK动态代理中,要实现代理逻辑必须先实现java.lang.reflect.InvocationHandler接口,它里面定义了一个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
* 异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("进入代理逻辑方法");
System.out.println("在调度真实对象之前的服务");
Object obj = method.invoke(target, args);// 相当于调用sayHelloWorld方法
System.out.println("在调度真实对象之后的服务");
return obj;
}
}
newProxyInstance方法包含了3个参数,对一个是类加载器,对二个是动态代理的接口挂在哪些接口下,第三个是定义实现方法逻辑的代理类,this表示当前对象。它必须实现InvocationHandler接口下的invoke方法。
invoke方法就是代理逻辑方法的实现方法。包含3个参数含义如下:
1、proxy,代理对象,就是bind方法生成的对象。
2、method、当前调度的方法。
3、调度方法的参数。
我们使用了代理对象调度方法以后,它就会进入到invoke方法里面。
类比前面的例子,proxy相当于商务对象,target相当于软件工程师对象,bind方法就是建立商务和软件工程师代理关系的方法。而invoke就是商务逻辑,它将控制者软件工程师的访问,
下面就可以测试我们的JDK动态代理啦:
public void testJdkProxy() {
JdkProxyExample jdk = new JdkProxyExample();
// 绑定关系,因为挂在接口HelloWorld下,所以声明代理对象HelloWorld proxy
HelloWorld proxy = (HelloWorld) jdk.bind(new HelloWorldImpl());
// 注意,此时HelloWorld对象已经是一个代理对象,它会进入代理的逻辑方法invoke里
proxy.sayHelloWorld();
}
测试结果输出如下:
进入代理逻辑方法
在调度真实对象之前的服务
Hello World
在调度真实对象之后的服务
此时,在打印 Hello World 之前后之后都可以加入相关的逻辑,而与HelloWorldImpl类无关。这就是JDK动态代理,理解它的过程十分重要。
以上核心内容整理自《java EE 互联网轻量级框架整合开发》这本书,后续还会继续整理。。