第二十一章 Spring之假如让你来写AOP——Weaver(织入器)篇
Spring源码阅读目录
第一部分——IOC篇
第一章 Spring之最熟悉的陌生人——IOC
第二章 Spring之假如让你来写IOC容器——加载资源篇
第三章 Spring之假如让你来写IOC容器——解析配置文件篇
第四章 Spring之假如让你来写IOC容器——XML配置文件篇
第五章 Spring之假如让你来写IOC容器——BeanFactory和FactoryBean
第六章 Spring之假如让你来写IOC容器——Scope和属性填充
第七章 Spring之假如让你来写IOC容器——属性填充特别篇:SpEL表达式
第八章 Spring之假如让你来写IOC容器——拓展篇
第九章 Spring之源码阅读——环境搭建篇
第十章 Spring之源码阅读——IOC篇
第二部分——AOP篇
第十一章 Spring之不太熟的熟人——AOP
第十二章 Spring之不得不了解的内容——概念篇
第十三章 Spring之假如让你来写AOP——AOP联盟篇
第十四章 Spring之假如让你来写AOP——雏形篇
第十五章 Spring之假如让你来写AOP——Joinpoint(连接点)篇
第十六章 Spring之假如让你来写AOP——Pointcut(切点)篇
第十七章 Spring之假如让你来写AOP——Advice(通知)上篇
第十八章 Spring之假如让你来写AOP——Advice(通知)下篇
第十九章 Spring之假如让你来写AOP——番外篇:Spring早期设计
第二十章 Spring之假如让你来写AOP——Aspect(切面)篇
第二十一章 Spring之假如让你来写AOP——Weaver(织入器)篇
第二十二章 Spring之假如让你来写AOP——Target Object(目标对象)篇
文章目录
- Spring源码阅读目录
- 第一部分——IOC篇
- 第二部分——AOP篇
- 前言
- 尝试动手写IOC容器
- 第二十一版 Weaver(织入器)篇
- 总结
前言
对于Spring一直都是既熟悉又陌生,说对它熟悉吧,平时用用没啥问题,但面试的时候被问的一脸懵逼,就很尴尬,都不好意思在简历上写着熟悉Spring了
所以决定花点时间研究研究Spring的源码。主要参考的书籍是:《Spring源码深度解析(第2版)》、《Spring揭秘》、《Spring技术内幕:深入解析Spring架构与设计原理(第2版)》
书接上回,在上篇 第二十章 Spring之假如让你来写AOP——Aspect(切面)篇 中,A君 已经完成了 Aspect(切面) 部分,对切点和通知进行了统一管理。接下来看看 A君 会有什么骚操作吧
尝试动手写IOC容器
出场人物:A君(苦逼的开发)、老大(项目经理)
背景:老大要求A君在一周内开发个简单的 IOC容器
前情提要: A君 已经完成了 Aspect(切面) 部分,对切点和通知进行了统一管理 。。。
第二十一版 Weaver(织入器)篇
“ A君,现在AOP部分已经快进入尾声了。还有个重要的部分——Weaver(织入器)。对于这部分内容,想必你或多或少都接触过。简而言之,就是创建代理对象,把对应的切面织入进去。当然啦,代理有基于JDK的,也有基于CGLIB的,这两个都要实现。A君,你有什么思路吗?” 老大 问到
“那自然是定义个接口,包含创建代理的方法。接口代理也好,CGLIB代理也好,都遵循此接口。” A君 不假思索回答道
“不错,可是这样子定义,使用者必须清楚代理有多少种实现,才能正确的创建出来代理对象!举个例子:以后新增一个创建代理的方式,那么使用者就得跟着改动,这在软件设计中可是大忌。创建代理中间是否少了点东西?” 老大 循循善诱道
“工厂模式!?” A君 疑惑道
“是的,你需要个工厂。使用者传入对应的配置信息即可,这样,即使以后有多少个实现,对于调用者来说是一样的,什么都不用改。除此之外,我看了你之前写的代码,缺少配置信息。我们可以根据是否存在接口,来推断使用哪个代理,但是不要剥夺用户自己指定配置的权利,这样用户没配置我们自己推断,配置了就以用户的为准,才能立于不败之地。” 老大 笑道
“明白了!” A君 忽然有种醍醐灌顶的感觉:也许,任何设计都是被用户折腾出来的,逼得程序员处处考虑拓展性、灵活性
“先这样子吧,你回去好好想想怎么做吧!” 老大 开始下逐客令
“好的。” A君 默默的走回自己的工位。开始思索:如何实现 老大 所说的内容。后续创建代理都要依赖于配置,先从配置开始吧。可是配置要有哪些内容呢?A君 略一沉吟,列出下面几点:
- 是否使用类代理,在有接口的情况下,默认会使用JDK代理,但是保不准用户就是想用类代理
- 代理对象是否可见,正常情况下,代理对用户是黑盒,但是由于 AOP 存在自调用问题,即 this.xxx() 时切面无法正常执行,故而由此配置。例:
原因也简单,this是当前对象,也就是目标对象,没有经过代理对象,所以无法增强
- Advise 是否可见,默认为true
- 是否冻结,冻结后,配置无法再进行修改
A君 平时玩游戏时,总是选择简单难度,其实游戏本身是存在多种难度的,简单难度是面向普罗大众的,困难难度是为了给高玩提供的,各个阶层玩家都能有足够的游戏体验。这里配置也是一样的道理,对于普通用户来说,有些配置不需要去关心,想要掌控全部配置,那得对 AOP 有充足的认识才行。A君 定义 ProxyConfig 作为配置类,代码如下:
public class ProxyConfig {
/**
* advise是否可见
*/
boolean opaque = false;
/**
* 允许代理对象内部通过 AopContext 访问当前代理,适用于自调用场景
*/
boolean exposeProxy = false;
/**
* 是否使用CGLIB代理
*/
private boolean proxyTargetClass = false;
/**
* 代理配置是否被冻结,防止运行时修改
*/
private boolean frozen = false;
public boolean isProxyTargetClass() {
return this.proxyTargetClass;
}
public void setProxyTargetClass(boolean proxyTargetClass) {
this.proxyTargetClass = proxyTargetClass;
}
public boolean isOpaque() {
return this.opaque;
}
public void setOpaque(boolean opaque) {
this.opaque = opaque;
}
/**
* Return whether the AOP proxy will expose the AOP proxy for
* each invocation.
*/
public boolean isExposeProxy() {
return this.exposeProxy;
}
public void setExposeProxy(boolean exposeProxy) {
this.exposeProxy = exposeProxy;
}
public boolean isFrozen() {
return this.frozen;
}
public void setFrozen(boolean frozen) {
this.frozen = frozen;
}
public void copyFrom(ProxyConfig other) {
this.proxyTargetClass = other.proxyTargetClass;
this.exposeProxy = other.exposeProxy;
this.frozen = other.frozen;
this.opaque = other.opaque;
}
}
让 Advised 去统一管理这些配置,Advised 接口新增方法如下:
配置类简单,难得是如何根据配置类,去实现不同功能代理,不过,接口倒是可以先定义出来。A君 定义 AopProxy 作为所有代理实现的标准,代码如下:
/**
* 创建代理接口
*/
public interface AopProxy {
Object getProxy();
Object getProxy(ClassLoader classLoader);
}
纠结良久,A君 打算先从 JDK代理 开始弄,相较于 CGCLIB,平时在工作中用到,比较熟悉点。 JDK代理 必须实现 InvocationHandler 接口,那入手点就在对应的方法上,在调用 invoke 方法时,如果方法是对应的增强方法,就调用对应的通知链。不过在此之前,需要给他加点料。嘿嘿,加啥料呢?主要就是针对上诉配置添加的,尽量让用户方便。主要添加三个接口,如下:
- SpringImitationProxy 接口:添加防伪标识,标记这个代理类是由框架生成的。嘿嘿
- Advised 接口:针对 opaque 配置,使其可以访问 AOP 的所有内容
- DecoratingProxy 接口:这个是专门给 JDK代理 用的,用以获取目标类 Class对象
A君 在 AopProxyUtils 工具类中,添加如下方法:
static Class<?>[] completeProxiedInterfaces(AdvisedSupport advised, boolean decoratingProxy) {
Class<?>[] specifiedInterfaces = advised.getProxiedInterfaces();
if (specifiedInterfaces.length == 0) {
Class<?> targetClass = advised.getTargetClass();
if (targetClass != null) {
if (targetClass.isInterface()) {//目标类是接口
advised.setInterfaces(targetClass);
} else if (Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) {//类、lambda就添加对应的接口
advised.setInterfaces(targetClass.getInterfaces());
}
specifiedInterfaces = advised.getProxiedInterfaces();
}
}
List<Class<?>> proxiedInterfaces = new ArrayList<>(specifiedInterfaces.length + 3);
proxiedInterfaces.addAll(Arrays.asList(specifiedInterfaces));
/**
* 添加标记接口
*/
if (!advised.isInterfaceProxied(SpringImitationProxy.class)) {
proxiedInterfaces.add(SpringImitationProxy.class);
}
/**
* 目标类可见,且不包含Advised接口
*/
if (!advised.isOpaque() && !advised.isInterfaceProxied(Advised.class)) {
proxiedInterfaces.add(Advised.class);
}
/**
* 用于jdk代理中,获取对应的目标class
*/
if (decoratingProxy && !advised.isInterfaceProxied(DecoratingProxy.class)) {
proxiedInterfaces.add(DecoratingProxy.class);
}
return proxiedInterfaces.toArray(new Class[0]);
}
方法没什么难度,就是根据对应的配置去添加对应的接口,料添加完了,剩下的就没有什么了,就是根据调用的方法去匹配对应的通知,有则调用整个通知链。无则直接调用目标方法。唯二需要注意的点是:第一点,需要根据 exposeProxy 配置,判断是否需要绑定到线程中。第二点,如果返回值 this
,需要确定下是否需要返回代理对象。JdkDynamicAopProxy 代码如下:
import com.hqd.ch03.v21.aopalliance.intercept.MethodInvocation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;
public class JdkDynamicAopProxy implements InvocationHandler, AopProxy {
private final AdvisedSupport advised;
private final Class<?>[] proxiedInterfaces;
public JdkDynamicAopProxy(AdvisedSupport config) {
this.advised = config;
this.proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
}
@Override
public Object getProxy() {
return getProxy(Thread.currentThread().getContextClassLoader());
}
@Override
public Object getProxy(ClassLoader classLoader) {
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
Object target = this.advised.getTarget();
try {
Object retVal;
/**
* 代理对象可见,绑定到线程中去
*/
if (this.advised.exposeProxy) {
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, this.advised.getTargetClass());
if (chain.isEmpty()) {
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = method.invoke(target, argsToUse);
} else {
MethodInvocation invocation =
new ReflectiveMethodInvocation(target, proxy, method, args, chain.toArray());
retVal = invocation.proceed();
}
Class<?> returnType = method.getReturnType();
/**
* 返回值是否是this,如果是this,则判断是否需要返回代理对象
*/
if (retVal != null && retVal == target &&
returnType != Object.class && returnType.isInstance(proxy) &&
!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
retVal = proxy;
} else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
throw new RuntimeException(
"Null return value from advice does not match primitive return type for: " + method);
}
return retVal;
} finally {
if (setProxyContext) {
AopContext.setCurrentProxy(oldProxy);
}
}
}
}
“ JDK代理 到这就结束了,比想象中的简单呢。” A君 暗喜
接下来就是 CGLIB代理 了,其实和 JDK代理 逻辑差不多,在此之前,A君 通过 Callback数组 把通知织入进去,重点就在这里。JDK代理 中,是根据方法名、参数等信息去对调用对应的方法,而 CGLIB代理 则是通过 Callback数组 下标。那怎么知道数组下标呢?那自然是实现对应的接口了,这个接口就是 CallbackFilter。剩下的基本就一样了。A君 定义 ProxyCallbackFilter 类,实现 CallbackFilter 接口。代码如下:
/**
* 规定callbacks数组顺序
*/
private static final int AOP_PROXY = 0;//通知方法
private static final int INVOKE_TARGET = 1;//目标对象方法
private static final int NO_OVERRIDE = 2;//空操作
private static final int DISPATCH_TARGET = 3;//获取目标类class对象
private static final int DISPATCH_ADVISED = 4;//advised 相关的方法
private static class ProxyCallbackFilter implements CallbackFilter {
private final AdvisedSupport advised;
public ProxyCallbackFilter(AdvisedSupport advised) {
this.advised = advised;
}
/**
* 这里返回值就是callbacks的下标
* 也就是说,这里是去匹配对应的回调函数
*
* @return
*/
@Override
public int accept(Method method) {
/**
* 空操作
*/
if (AopUtils.isFinalizeMethod(method)) {
return NO_OVERRIDE;
}
/**
* Advised相关的方法委托给ADVISED分发器
*/
if (!this.advised.isOpaque() && method.getDeclaringClass().isInterface() &&
method.getDeclaringClass().isAssignableFrom(Advised.class)) {
return DISPATCH_ADVISED;
}
Class<?> targetClass = this.advised.getTargetClass();
List<?> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
boolean haveAdvice = !chain.isEmpty();
boolean exposeProxy = this.advised.isExposeProxy();
boolean isFrozen = this.advised.isFrozen();
/**
* 方法存在通知,使用增强拦截器
*/
if (haveAdvice || !isFrozen) {
return AOP_PROXY;
} else {//不存在,调用目标方法
if (exposeProxy) {
return INVOKE_TARGET;
}
Class<?> returnType = method.getReturnType();
if (targetClass != null && returnType.isAssignableFrom(targetClass)) {
return INVOKE_TARGET;
} else {
return DISPATCH_TARGET;
}
}
}
}
ProxyCallbackFilter 出来后,主体逻辑就出来了,接着只需要根据定义的顺序去定义 Callback数组 即可。A君 定义个方法,用来组装 Callback数组。代码如下:
private Callback[] getCallbacks(Class<?> rootClass) {
boolean exposeProxy = this.advised.isExposeProxy();
boolean isStatic = true;
//1.切面增强调用拦截器
Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);
Callback targetInterceptor;
/**
* 2.代理对象拦截器,是否暴露代理对象
* 两个拦截器的唯一区别就是,是否暴露代理对象
*/
if (exposeProxy) {
targetInterceptor = new StaticUnadvisedExposedInterceptor(this.advised.getTarget());
} else {
targetInterceptor = new StaticUnadvisedInterceptor(this.advised.getTarget());
}
//4.序列化拦截器
Callback targetDispatcher = new StaticDispatcher(this.advised.getTarget());
Callback[] mainCallbacks = new Callback[]{
aopInterceptor,
targetInterceptor,
new SerializableNoOp(),
targetDispatcher, this.advisedDispatcher
};
return mainCallbacks;
}
为了文章整体的整洁性,A君 就贴主体逻辑。getProxy 方法代码如下:
public Object getProxy(ClassLoader classLoader) {
Enhancer enhancer = new Enhancer();
Class<?> rootClass = this.advised.getTargetClass();
Class<?> proxySuperClass = rootClass;
/**
* 如果是代理类,则再往上获取父类
*/
if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
proxySuperClass = rootClass.getSuperclass();
Class<?>[] additionalInterfaces = rootClass.getInterfaces();
for (Class<?> additionalInterface : additionalInterfaces) {
this.advised.addInterface(additionalInterface);
}
}
enhancer.setSuperclass(proxySuperClass);
/**
* 指定命名策略,防止冲突
*/
enhancer.setNamingPolicy(SpringImitationNamingPolicy.INSTANCE);
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
if (classLoader != null) {
enhancer.setClassLoader(classLoader);
}
/**
* 添加默认拦截器,执行增强
*/
Callback[] callbacks = getCallbacks(rootClass);
Class<?>[] types = new Class<?>[callbacks.length];
for (int x = 0; x < types.length; x++) {
types[x] = callbacks[x].getClass();
}
enhancer.setCallbackFilter(new ProxyCallbackFilter(
this.advised.getConfigurationOnlyCopy()));
enhancer.setCallbacks(callbacks);
enhancer.setCallbackTypes(types);
/**
* 创建代理对象
*/
return createProxyClassAndInstance(enhancer, callbacks);
}
好了, CGLIB代理 和 JDK代理 都已经实现了。就剩下一个工厂了
工厂的实现并没有什么特殊的地方,无非就是根据配置和接口推断使用哪个代理。A君 定义 AopProxyFactory 接口。代码如下:
public interface AopProxyFactory {
AopProxy createAopProxy(AdvisedSupport config);
}
默认实现为 DefaultAopProxyFactory。代码如下:
import com.hqd.ch03.v21.aop.SpringImitationProxy;
import com.hqd.ch03.v21.utils.ClassUtils;
import java.lang.reflect.Proxy;
public class DefaultAopProxyFactory implements AopProxyFactory {
@Override
public AopProxy createAopProxy(AdvisedSupport config) {
if (config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new RuntimeException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new CglibAopProxy(config);
} else {
return new JdkDynamicAopProxy(config);
}
}
private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
Class<?>[] ifcs = config.getProxiedInterfaces();
return (ifcs.length == 0 || (ifcs.length == 1 && SpringImitationProxy.class.isAssignableFrom(ifcs[0])));
}
}
至此整个 Weaver(织入器) 就全部完事了。A君 按捺不住心中的喜悦,开始编写测试代码。代码如下:
@Test
public void v21() throws Throwable {
System.out.println("############# 第二十一版 Weaver(织入器)篇 #############");
System.out.println("############# 接口代理 #############");
com.hqd.ch03.v21.aop.framework.aspectj.AspectJExpressionPointcut pointcut =
new com.hqd.ch03.v21.aop.framework.aspectj.AspectJExpressionPointcut("execution(* *.test*(..)) and args(msg)");
Object ap = new AopBean();
//前置通知
AopTest aop = new AopTest();
Method beforeTest = aop.getClass().getDeclaredMethod("beforeTestV19", JoinPoint.class, String.class);
List list = new ArrayList<>();
//前置通知
com.hqd.ch03.v21.aop.framework.aspectj.AspectJMethodBeforeAdvice beforeAdvice =
new com.hqd.ch03.v21.aop.framework.aspectj.AspectJMethodBeforeAdvice(pointcut, beforeTest, aop);
com.hqd.ch03.v21.aop.Advisor advisor = new com.hqd.ch03.v21.aop.framework.aspectj.AspectJPointcutAdvisor(beforeAdvice);
list.add(com.hqd.ch03.v21.aop.interceptor.ExposeInvocationInterceptor.ADVISOR);
list.add(advisor);
com.hqd.ch03.v21.aop.framework.ProxyFactory proxyFactory = new com.hqd.ch03.v21.aop.framework.ProxyFactory(ap);
proxyFactory.addInterface(IAopBean.class);
proxyFactory.addAdvisors(list);
IAopBean jdkProxy = (IAopBean) proxyFactory.getProxy();
jdkProxy.test1("111");
System.out.println("############# cglib代理 #############");
proxyFactory.setProxyTargetClass(true);
AopBean cgProxy = (AopBean) proxyFactory.getProxy();
cgProxy.test1("111");
}
测试结果如下:
“好了,Weaver(织入器) 部分已经完成了,可以交差了。” A君 欢呼
总结
正所谓树欲静而风不止,欲知后事如何,请看下回分解(✪ω✪)