ProxyPattern(即:代理模式),23种常用的面向对象软件的设计模式之一代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
只是看上面的概念陈述可能不太好理解。好比潘金莲和西门庆的故事一样,众所周知,潘金莲是武大郎的老婆,但是却和西门庆看对了眼儿,但是潘金莲很苦恼,如何才能向西门庆表达自己的心思呢?自己已是有夫之妇。于是她找到了王婆,王婆代她向西门庆传达心意。这个故事里,王婆就是“代理对象”。
生活中,有很多代理的例子,和代理模式的思想是一样的,好比我们去找房子,我们拜托房屋中介,这里中介就是代理对象,中介去找房子,但是最后房子却是属于我们的。这就代理的含义。
(1).职责清晰
真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是 编程简洁清晰。
(2).代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了的作用和保护了目标对象的作用。
(3).高扩展性
一个是真正的你要访问的对象(目标类),另一个是代理对象,真正对象与代理对象实现同一个接口,先访问代理类再访问真正要访问的对象。
我们可以一组代码案例加深对代理的理解。还是回到上文所提及的西门庆和潘金莲的故事中。
从前有一种女人,这种女人水性杨花,喜欢抛媚眼
/**
* 喜欢抛媚眼的女人
* @author Keo.Zhao
*
*/
public interface KindWomen {
public void throwEyes();
public void doSomething();
}
/**
* 这是潘金莲
* @author Keo.Zhao
*
*/
public class PJL implements KindWomen{
public void throwEyes(){
System.out.println("潘金莲抛媚眼");
}
public void doSomething(){
System.out.println("潘金莲...");
}
}
/**
* 王婆
* @author Keo.Zhao
*
*/
public class WP implements KindWomen{
private KindWomen kindWomen;
public WP(KindWomen kindWomen){
this.kindWomen = kindWomen;
}
@Override
public void throwEyes() {
kindWomen.throwEyes();
}
@Override
public void doSomething() {
kindWomen.doSomething();
}
}
/**
* 西门庆
* @author Keo.Zhao
*
*/
public class XMQ {
public static void main(String[] args) {
//潘金莲实例
KindWomen pjl = new PJL();
//王婆实例
KindWomen wp = new WP(pjl);
wp.throwEyes();
}
}
最后打印结果
可以看出,虽然是王婆(代理类)执行了throwEyes()抛媚眼方法,但是最后西门庆接收到的,还是来自于潘金莲(目标类)的。
这就是一个静态代理的例子。
上文我已经初步认识了代理模式,现在我们要开始学习动态代理。
动态代理它可以直接给某一个目标对象生成一个代理对象,而不需要代理类存在。
动态代理与代理模式原理是一样的,只是它没有具体的代理类,直接通过反射生成了一个代理对象 (spring学习)
动态代理中,不再需要代理类,也就是说之前故事里的王婆,将没有存在的必要。可以这么理解,时代终究在发展,如今已经是信息化的时代了,微信上一条消息,多远都可以传递,微信取代了王婆,它是一个虚拟平台,本质上就是一个动态代理对象。
动态代理生成技术:
1.jdk提供一个Proxy类可以直接给实现接口类的对象直接生成代理对象。
2.cglib (spring中的类库)
Java.lang.reflect.Proxy类可以直接生成一个代理对象
Proxy.newProxyInstance():产生代理类的实例。仅能代理实现至少一个接口的类
ClassLoader:类加载器。固定写法,和被代理类使用相同的类加载器即可。
Class[] interface:代理类要实现的接口。固定写法,和被代理类使用相同的接口即可。
InvocationHandler:策略(方案)设计模式的应用。如何代理?
InvocationHandler中的invoke方法:调用代理类的任何方法,此方法都会执行
Object proxy:代理对象本身的引用。一般用不着。
Method method:当前调用的方法。
Object[] args:当前方法用到的参数
public static void main(String[] args) {
//马蓉实例
final MR mr = new MR();
KindWomen proxy
= (KindWomen) Proxy.newProxyInstance(mr.getClass().getClassLoader(),
mr.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object o = method.invoke(mr, null);
return o;
}
});
proxy.throwEyes();
}
代理对象的目的,实际上就是为真实对象做事情,以及起到一定的拦截作用(通过改变 Object[] args:当前方法用到的参数,传递给真实对象的参数)。事实上,我们可以在这句代码之前之后做一些事情,好比如下:
public static void main(String[] args) {
//马蓉实例
final MR mr = new MR();
KindWomen proxy
= (KindWomen) Proxy.newProxyInstance(mr.getClass().getClassLoader(),
mr.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开启事物");
Object o = method.invoke(mr, null);
System.out.println("结束事物");
return o;
}
});
proxy.throwEyes();
}
光是看概念陈述并不好理解这个所谓的“面向切面编程”。我们把目光放在上图。前端的UI界面,向后台发送请求,control(web)层接收到了前台传过来的数据,再传到service层,service层经过一些列的逻辑代码,再调用Dao层的方法
。看图我们不难发现,Service层被两条箭头线横截了,你可以在这两条箭头的位置分别写两个方法,一个在Service的逻辑代码发生之前写,一个在其发生之后写。所以,你可以认为Service被这两条线,截出了两个切面,你所写的两个方法是在这个切面上写的,我们正面对着这个切面编程。这就是我们的AOP——面向切面编程。
而在实际的编码中,我们又是如何实现切面编程的呢?没错,就是上文所提及的动态代理!我们来看一个例子,加深对其理解。
public class ObjectFacoty {
public static Object getUserService(){
//目标类
UserService userService = new UserServiceImpl();
//代理类
UserService proxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object invoke = null;
try {
System.out.println("开始事务");
invoke = method.invoke(userService,null);
System.out.println("提交事务");
} catch (Exception e) {
System.out.println("回滚事务");
e.printStackTrace();
}
return invoke;
}
});
return proxy;
}
}