一 理解代理
代理是一种设计模式,指对目标对象方法的间接调用,可以实现不修改原方法逻辑的前提下扩展功能,这遵循了开闭原则。但我始终觉得代理这个词描述的不准确,原因在于代理的虽然意指代为执行,但实际上对象方法的执行并非跳过,代理之后的表现仅仅为原对象方法执行的前后多了补充逻辑,这难道不是函数增强吗?
代理的需求来自实际项目的实施过程中产生的问题,某些特定场景下开发者不被允许或很难去调整一个既有的方法,然而目标是对方法进行增强实现,那么能做就是在函数调用的前后来补充一些设计逻辑,为了避免对原函数进行重构/调整,减轻测试的压力,开发者想到了一个绝妙的解决方案——代理。
所以本文从需求开始,逐步介绍代理的各类实现以及在AOP层面的应用。
二 增强函数
举个例子,一个信息发送的需求,目前以短信方式实现:
public class MessageClient
{
public void sendMessage(String msg)
{
System.out.println("发送短信:" + msg);
}
}
现在需求发生了变化,要求在将发送的短信内容进行扩充,但是不允许调整原函数,怎么办?一种以面向对象的方式的解决方案是从MessageClient派生一个子类,重写sendMessage函数:
public class EnhancedMessageClient extends MessageClient
{
@Override
public void sendMessage(String msg)
{
System.out.println("sendMessage之前的补充处理逻辑");
super.sendMessage(msg);
}
}
这是一个最常见的解决方案,重写父类方法,对参数执行增强处理逻辑后,重新调用父类方法,使其表现的行为和父类一致,这就增强了函数实现。
但这种实现方式并不优雅,代理模式就是为解决这个问题诞生的。
三 代理模式
代理模式来完成上述需求似乎更加妥当,但是代理模式要求方法为接口声明,那么我们重新调整信息发送客户端:
public interface SendMessage
{
public void doSend(String msg);
}
public class MessageClient implements SendMessage
{
public void sendMessage(String msg)
{
System.out.println("发送短信:" + msg);
}
}
接下来需要设计一个代理类,代理类同样实现了SendMessge接口,其目的是为了和被代理类的行为表现一致。其次,还需要让代理类持有一个被代理类的对象成员,用以执行被代理类的真正逻辑,而代理类仅作额外的处理逻辑:
public class MessageClientProxy implements SendMessage
{
private SendMessage impl = null;
public MessageClientProxy(SendMessage impl)
{
this.impl = impl;
}
@Override
public void sendMessage(String msg)
{
before();
impl.sendMessage(msg);
after();
}
private void before()
{
System.out.println("sendMessage之前的补充处理逻辑");
}
private void after()
{
System.out.println("sendMessage之后的补充处理逻辑");
}
}
这样我们就可以毫无痕迹的实现对MessageClient的sendMessage方法做补充处理,而MessageClient毫无察觉,应用转为通过MessageClientProxy来调用sendMessage。这种代理模式亦称之为静态代理。
代理模式和通过派生子类重写父类逻辑一样,有一个致命缺陷,当需求不断发生变化的时候,或者需要很多被增强的函数的场景下,类型定义(代理类定义或派生类定义)爆发,这对项目维护是致命的。
那么如何在满足需求的前提下,遏制类型定义爆发呢?动态代理!
四 JDK动态代理
Java原生支持动态代理,我们常称之为JDK动态代理,它要求我们实现一个接口InvocationHandler,如此MessageClientProxy就不再需要了,我们仅仅实现一个InvocationHandler接口就可以:
public class DynamicProxy implements InvocationHandler
{
private Object targe;
public DynamicProxy(Object target)
{
this.targe = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable
{
before();
method.invoke(targe, params);
after();
return null;
}
private void before()
{
System.out.println("sendMessage之前的补充处理逻辑");
}
private void after()
{
System.out.println("sendMessage之后的补充处理逻辑");
}
}
光实现InvocationHandler接口不够,它还要求应用程序通过Proxy来返回一个实现了SendMessage接口的代理类——即MessageClient:
public static void main(String[] args)
{
DynamicProxy proxy = new DynamicProxy(new MessageClient());
SendMessage impl = (MessageClient) Proxy.newProxyInstance(MessageClient.class.getClassLoader(),
MessageClient.class.getInterfaces(), proxy);
impl.sendMessage("我被代理了");
}
Proxy.newProxyInstance方法的三个参数分别为:
- ClassLoader
- 实现类的接口
- 代理对象
从参数上可以隐讳的看到JDK动态代理的一个弊端——要求被代理类的增强函数一定通过接口声明!这是JDK对动态代理的实现逻辑决定的,这部分介绍大家可以参考网上的各类资料,都很详细。
而我要强调的是,虽然我们通过JDK动态代理刻画了一个以不变应万变的模板,但是JDK动态代理的调用方式确实恶心,所有调用处必须通过Proxy.newProxyInstance方法来获取接口的实现对象,所以我对其进行改进:
public class DynamicProxy implements InvocationHandler
{
...
@SuppressWarnings("unchecked")
public <T> T getProxy()
{
return (T) Proxy.newProxyInstance(this.targe.getClass().getClassLoader(), this.getClass().getInterfaces(),
this);
}
...
}
如上在DymanicProxy中对Proxy.newProxyInstance函数进行封装,注意getProxy方法的返回值为泛型,如此定义是为了便于应用调用函数时,不必进行类型的强制转换,那么应用在使用的时候就化简成了如下形式:
public static void main(String[] args)
{
DynamicProxy proxy = new DynamicProxy(new MessageClient());
SendMessage impl = proxy.getProxy();
impl.sendMessage("我被代理了");
}
到此为止,我们真的解决之前所说的问题了吗?绝不!首先DymanicProxy虽然能够对任何接口实现类进行函数增强,但是其增强逻辑是一样的!!!其次,JDK动态代理只能针对接口模式实现函数增强,如果一个实现类没有任何接口声明呢?
先说第一个问题,既然能想到函数增强逻辑写死不好,那么动用万能的面向对象特性——封装即可解决,我们况且认为对一个函数进行增强,分前后两端,当然了原函数逻辑就不用妄想了,除非用字节码技术进行处理。所以我们定义一个增强处理接口:
public interface EnhanceMethod
{
public void before();
public void after();
}
如此应用再去使用代理模板的时候,仅仅需要传入一个增强接口的实现即可:
public static void main(String[] args)
{
DynamicProxy proxy = new DynamicProxy(new MessageClient(), new EnhanceMethod()
{
@Override
public void before()
{
System.out.println("sendMessage之前的补充处理逻辑");
}
@Override
public void after()
{
System.out.println("sendMessage之后的补充处理逻辑");
}
});
SendMessage impl = proxy.getProxy();
impl.sendMessage("我被代理了");
}
回头再说第二个问题,JDK动态代理的实现机制决定了它无法对非接口实现类进行函数增强,这个问题怎么解决?CGLIB给了我们一份满分答卷。
五 CGLIB动态代理
CGLIG专门为解决非接口类动态代理而实现,它的实现方法又有变化,它要求实现接口MethodInterceptor,从接口命名可以隐讳的看出CGLIB动态代理针对方法进行增强,我们对DynamicProxy重写,改名为CgligProxy:
public class CglibProxy implements MethodInterceptor
{
private Class<?> clazz;
private EnhanceMethod enhance;
public CglibProxy(Class<?> clazz, EnhanceMethod enhance)
{
this.clazz = clazz;
this.enhance = enhance;
}
@SuppressWarnings("unchecked")
public <T> T getProxy()
{
return (T) Enhancer.create(clazz, this);
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable
{
enhance.before();
Object result = proxy.invokeSuper(obj, args);
enhance.after();
return result;
}
}
按照如上方式实现CGLIB版本的动态代理模板后,应用的使用逻辑相应调整为:
public static void main(String[] args)
{
CglibProxy proxy = new CglibProxy(MessageClient.class, new EnhanceMethod()
{
@Override
public void before()
{
System.out.println("sendMessage之前的补充处理逻辑");
}
@Override
public void after()
{
System.out.println("sendMessage之后的补充处理逻辑");
}
});
SendMessage impl = proxy.getProxy();
impl.sendMessage("我被代理了");
}
点到为止,对本文进行小结,首先代理并非真正的代为执行,对其理解为函数的无侵入增强更为合适,其次代理的实现一般分为两类:静态代理和动态代理,动态代理有根据实现机制的不同分为:JDK动态代理和CGLIB动态代理。
理解了代理和其应用之后,第二部分将对AOP进行介绍,包括AOP的前生今世,应用场景,Spring等主流框架的实现机制以及如何自行实现一个AOP框架等等。