一、前期回顾
上一篇文章《JAVA设计模式之模板方法模式和建造者模式》谈到了设计模式中建造类的模式,我们来回顾下。模板方法模式定义了核心的算法结构,然后子类可以实现某些特定结构的算法内容,建造者模式完全把核心的算法内容交给子类去实现。我们这篇博文来学习下最常用的设计模式,代理模式。
二、代理模式的定义与实践
定义:Provide a surrogate or placeholder for another object to control access to it.
翻译:为其他对象提供一种代理以控制对这个对象的访问。
代理模式还是比较好理解,就是委托别人做事情,比如我们要租房子,委托中介去找房子,这就是代理模式。代理模式分为静态代理模式和动态代理模式,我们来结合代码看看代理模式如何实现的。
2.1 静态代理
/***
定义消费者接口,查找指定区域,指定价格以下的房子*/
public interface ICustomer {
/**查找房子,指定区域,指定价格*/
String findHouse(String location,int price);
}
/**真正的找房消费者,所以只关注找到这个目标**/
public class YungCustomer implements ICustomer {
@Override
public String findHouse(String location,int price) {
System.out.println("找到了位于["+location+"],价格"+price+"以下的房子");
return "找到了位于["+location+"],价格"+price+"以下的房子";
}
}
/**找房子中介类,找到合适房子再反馈给指消费者**/
public class AgencyProxy implements ICustomer{
private ICustomer coustomer;
public AgencyProxy(ICustomer coustomer) {
this.coustomer = coustomer;
}
@Override
public String findHouse(String location,int price) {
String result="没有符合条件的房子";
before();
if (null != coustomer){
result= coustomer.findHouse(location,price);
}
after();
return result;
}
private void before(){
System.out.println("开始找房子");
}
private void after(){
System.out.println("结束找房子");
}
}
/***场景类*/
public class Client {
public static void main(String[] args) {
ICustomer customer = new YungCustomer();
ICustomer proxy = new AgencyProxy(customer);
String result = proxy.findHouse("钓鱼岛附近", 2000);
System.out.println(result);
}
}
输出结果:
开始找房子
找到了位于[钓鱼岛附近],价格2000以下的房子
结束找房子
静态代理模式比较简单,总结就是代理类中包含实际调用者的引用,当符合条件时候,在去调用真正的对象方法。
2.2 JDK动态代理
动态代理比较复杂一点,相对于静态代理的指定代理对象,动态代理是在运行时候才知道实际代理对象。动态代理应用比较广泛,我们用的最多的框架Spring中 AOP就采用了动态代理。我们来把上面的代码改造成动态代理。
/**动态代理handler**/
public class MyInvocationHandler implements InvocationHandler {
private Object object;
public MyInvocationHandler(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result= method.invoke(object,args);
after();
return result;
}
private void before(){
System.out.println("开始找房子");
}
private void after(){
System.out.println("结束找房子");
}
}
public class Client {
public static void main(String[] args) {
// 获取动态代理对象。
ICustomer customer = (ICustomer) Proxy.newProxyInstance(Client.class.getClassLoader()
, new Class[]{ICustomer.class}
, new MyInvocationHandler(new YungCustomer()));
customer.findHouse("钓鱼岛附近", 2000);
}
}
输出结果:
开始找房子
找到了位于[钓鱼岛附近],价格2000以下的房子
结束找房子
动态代理要求代理的类必须要有接口,同时实现InvocationHandler的接口,实现接口的invoke方法即可,在invoke方法内可以在真正执行方法的前后添加想做的事情,比如说记录日志,通知消息等等,这就是AOP的思想,在不改变原有业务代码的情况下,添加功能。但是jdk动态代理的缺点就是代理的类必须要有接口才行,为了弥补这个缺点,出现了一款不需要接口就能被代理的第三方库,CGLIB库。
2.3.CGLIB动态代理
2.3.1 CGLIB介绍
CGLIB是一个强大的、高性能的代码生成库。它被广泛使用在基于代理的AOP框架(例如Spring AOP和dynaop)提供方法拦截。在实现内部,CGLIB库使用了ASM这一个轻量但高性能的字节码操作框架来转化字节码,产生新类。
2.3.2 CGLIB的使用
既然是第三方库,我们需要添加maven依赖
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.9</version>
</dependency>
CGLIB也和jdk动态代理一样,需要设置回调方法,在JDK动态代理中我们要实现
InvocationHandler,而在CGLIB中我们需要实现net.sf.cglib.proxy.MethodInterceptor
接口作为回调接口。我们先看代码
/**实现回调接口**/
public class MyMthondInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object result = methodProxy.invokeSuper(o,objects);
after();
return result;
}
private void before(){
System.out.println("开始找房子");
}
private void after(){
System.out.println("结束找房子");
}
}
public class Client {
public static void main(String[] args) {
//创建回调实例
MyMthondInterceptor interceptor=new MyMthondInterceptor();
//CGLIB创建实例
Enhancer enhancer = new Enhancer();
//设置需要代理的类
enhancer.setSuperclass(YungCustomer.class);
//设置回调类
enhancer.setCallback(interceptor);
//获取代理对象
YungCustomer customer= (YungCustomer) enhancer.create();
//执行方法
customer.findHouse("钓鱼岛",1000);
}
}
输出结果:
开始找房子
找到了位于[钓鱼岛],价格1000以下的房子
结束找房子
这里CGLIB实现结果和JDK动态代理完全一样。上面实现回调方法的时候需要注意,推荐使用methodProxy.invokeSuper(o,objects);
,这里调用的是CJLIB库的方法,如果使用method.invoke(o,args);
需要注意的是,这里使用的就是JDK的动态代理了,同时invoke的object必须是传入的代理实例,而不是方法中形参object,否则会导致死循环调用。同时考虑到性能,还是建议使用第一种调用方式。
CGLIB库还提供了很多其他的特性,比如回调方法过滤等等。有兴趣的同学可以自行研究。
三、代理模式的优点与缺点
优点
1.职责清晰
真实的角色实现实际的业务逻辑,不用关心其他非核心的业务逻辑。业务是业务,辅助功能是辅助功能,职责非常清晰。比如要实现日志功能,不用耦合在实际的业务代码中,只要做一个代理即可。
2.良好的扩展性
由于核心的业务逻辑已经封装好了,后面要增强业务功能,也可以使用代理模式代理增加功能即可。
缺点
1.类的臃肿
对于静态代理来说,如果过多的使用静态代理会带来类臃肿。一般会在接口协议转换中使用比较多代理模式。 2.复杂性 对于动态代理来说,设计到回调方法的实现,特别是CGLIB中的使用,还是带来了一定的复杂性。
3.性能
对于动态代理来说,都是运行期进行字节码操作,所以还是带来了一些性能损耗,但是这不能作为不使用动态代理的理由,任何东西都是有两面性的。
四、代理模式的使用场景
1.方法增强;比如增加日志,事务等功能。
2.远程RPC调用;现在很多分布式系统RPC调用都是采用了代理模式。
3.协议等的转换;比如需要适用另外一套协议接口,会使用代理模式,先转换为老协议,然后再调用实际的类去执行。
4.懒加载;有些框架会在开始的时候使用代理类来替换实际类,等到真正要使用该类的时候才进行加载,从而达到了懒加载的效果。
五、总结
本篇博客介绍了代理模式,代理模式分为静态代理和动态代理,动态代理又分为jdk动态代理和CGLIB动态代理。JDK动态代理必须要有接口才能使用,CGLIB弥补了这个缺陷,可以直接对类进行代理。同时CGLIB动态代理性能相对于JDK动态代理要优秀。
六、参考
《设计模式之禅》