简介
搁我以前的理解,spring aop就是对现有方法的增强。其实,“增强”是不准确的,是完全替换了原有方法的执行,只不过替换后的方法包含了原有方法的逻辑。换句话说,如果替换后的方法没有包含原有方法的逻辑,则aop完全可以“挂羊头,卖狗肉”。
从spring aop,我们可以学习做到:自定义一个注解,辅助threadlocal等工具,即可低侵入,重新整合成新的业务逻辑。
AOP本身较为复杂,本文作为学习AOP的引子,先抛弃一些跟本质不太相关的东西,从最简单的例子开始。
从基于HTTP协议的远端调用开始说起
基于http协议的远程调用。对于客户端,只需声明一个InterfaceA,spring便可以通过ProxyFactoryBean返回一个代理实例,该代理实例方法封装了调用信息,调用http客户端(比如httpclient)发送给服务端,拿到结果后解析成InterfaceA所定义方法的返回值。
没有InterfaceA的实现类,调用其接口方法 = 封装数据,发送http请求,解析响应,返回结果
。如何做到呢?FactoryBean!
public class HttpInvokerProxyFactoryBean extends HttpInvokerClientInterceptor
implements FactoryBean<Object> {
private Object serviceProxy;
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
if (getServiceInterface() == null) {
throw new IllegalArgumentException("Property 'serviceInterface' is required");
}
// 返回代理类的实例
this.serviceProxy = new ProxyFactory(getServiceInterface(), this).getProxy(getBeanClassLoader());
}
public Object getObject() {
return this.serviceProxy;
}
public Class<?> getObjectType() {
return getServiceInterface();
}
public boolean isSingleton() {
return true;
}
}
很明显,我们有必要了解下Proxy和ProxyFactory
public class ProxyFactory extends ProxyCreatorSupport{
// 构造方法
public ProxyFactory() {}
public ProxyFactory(Object target) {}
public ProxyFactory(Class[] proxyInterfaces) {}
public ProxyFactory(Class proxyInterface, Interceptor interceptor) {}
public ProxyFactory(Class proxyInterface, TargetSource targetSource) {}
// 获取代理对象
public Object getProxy() {
return createAopProxy().getProxy();
}
public Object getProxy(ClassLoader classLoader) {
return createAopProxy().getProxy(classLoader);
}
public static <T> T getProxy(Class<T> proxyInterface, Interceptor interceptor) {
return (T) new ProxyFactory(proxyInterface, interceptor).getProxy();
}
public static <T> T getProxy(Class<T> proxyInterface, TargetSource targetSource) {
return (T) new ProxyFactory(proxyInterface, targetSource).getProxy();
}
public static Object getProxy(TargetSource targetSource) {
if (targetSource.getTargetClass() == null) {
throw new IllegalArgumentException("Cannot create class proxy for TargetSource with null target class");
}
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTargetSource(targetSource);
proxyFactory.setProxyTargetClass(true);
return proxyFactory.getProxy();
}
}
观察ProxyFactory提供的方法,以T getProxy(Class<T> proxyInterface, Interceptor interceptor)
为例,我们提供一个接口,再提供一个interceptor,就可以返回一个代理类的实例。
如果依我的意思,T getProxy(Class<T> proxyInterface, Interceptor interceptor)
方法的实现可以这样
public static <T> T getProxy(Class<T> proxyInterface, InvocationHandler invocationHandler) {
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, invocationHandler);
}
然后我们传入一个InvocationHandler实例,在其invoke方法中,实现封装数据,发送http请求,解析响应,返回结果
的逻辑。
事实上,传入interceptor实例的逻辑就是这样。然而,ProxyFactory中getProxy是依靠AopProxy实现的,ProxyFactory的功能只是AopProxyFactory这一整套模型功能的子集。针对基于HTTP协议的远程调用而言,使用InvocationHandler的invoke方法实现调用接口已经够用,但对于更复杂的需求,比如将封装数据,发送http请求,解析响应,返回结果
分割成几个步骤,每个步骤由用户提供实现类,并且在配置文件中可配,实现“横切”业务逻辑,则需要更复杂的模型支持。
但这个例子已经可以说明AOP的基本原理:通过FactoryBean加载配置文件中的配置,根据配置信息使用Proxy.newProxyInstance(classLoader, proxiedInterfaces, invocationHandler)
生成代理实例。那么现在的难点是:如何将散落在配置文件中的配置和方法,组成一个完整的执行流程,充实invocationHandler中的invoke方法。
从一个项目开始说起
笔者曾经实现过一个个性化推送项目,根据用户的收听记录,为用户推送消息,以达到挽留用户的目的,具体情况是:
- 根据用户的最后收听时间,将用户分为不同的组
- 每个组对应一个推送策略,每个策略有多个推送选项。比如用户已经7天没有登录app,则为用户推送一些文案,推送选项包括:订阅专辑更新、推荐专辑(根据机器学习得到)以及默认推送文案。
- 推送策略的多个推送选项有优先级,假设“订阅专辑更新”优先级最高,则如果用户订阅的专辑有更新,为用户推送“亲爱的xx,您订阅的xx有更新了”。如果没有更新,则尝试推荐专辑,依次递推。
我在实现该项目时,运用了工厂模式及责任链模式。具体流程如下:
- 加载配置。将所有推送选项加载进来,根据策略配置组成推送链,并建立推送分组和推送链的映射关系(形成一个“推送策略工厂”)。
- 推送过程。根据用户属性计算用户所属的分组,通过“推送策略工厂”返回该分组对应的推送链,触发推送链。
今天笔者拜读《Spring技术内幕》,看到spring aop的源码分析,其实现与笔者项目真是异曲同工(当然,多少还有点不一样),对应关系如下:
个性化推送 Spring AOP
用户 目标对象要增强的方法
推送选项,比如“订阅专辑更新” Advice
推送策略 拦截链
推送策略工厂 拦截链工厂
希望这可以作为引子,可以让读者更容易理解下面的内容。
AopProxy和AopProxyFactory
在所有数据已加载完毕的基础上(比如初始化拦截器链),spring aop的执行逻辑
- 获取代理对象
- 如何获取代理对象?
Proxy.newProxyInstance(classLoader, proxiedInterfaces, invocationHandler)
- 在invocationHandler的invoke方法中实现代理类的方法逻辑
- 找到拦截链工厂,获取该方法对应的拦截链
- 执行拦截链
我以前的理解,代理方法的逻辑 = 各种通知 + 方法本身
,这个不能说错,但不够准确。
代理对象的方法逻辑 = 根据拦截链工厂找到对应的拦截链 + 执行拦截链
,执行拦截链 = 前置通知 + 方法本身 + 后置通知(+ 可能的异常通知)
所以回过头再来看整个流程,对比着看
HttpInvokerProxyFactoryBean extends HttpInvokerClientInterceptor {
public Object getObject() {
// 简化逻辑
return new ProxyFactory(getServiceInterface(), this).getProxy(getBeanClassLoader())
// 实际调用的还是AopProxy
}
}
public class ProxyFactoryBean extends ProxyCreatorSupport{
public Object getObject() throws BeansException {
initializeAdvisorChain();
// 简化逻辑
return new DefaultAopProxyFactory().createAopProxy(AdvisedSupport).getProxy(this.proxyClassLoader)
}
}
AopProxyFactory中获取接口方法对应的拦截器链,当然这个活由AdvisedSupport实现,AdvisedSupport含有用户的所有配置,将用户的配置信息加载到内存,这需要一整套模型,比如Advice和PointCut。但拦截器链的基本构成Interceptor,是另一套模型。这两套模型,一个代表了“加载配置”,一个代表了如何编织。在HttpInvokerProxyFactoryBean中,因为传入的直接是一个特定的Interceptor,省了”读取配置,创建拦截链”这类麻烦事。