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之假如让你来写AOP——融入IOC容器篇
第二十四章 Spring之源码阅读——AOP篇
文章目录
- 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);}@Overridepublic Object getProxy() {return getProxy(Thread.currentThread().getContextClassLoader());}@Overridepublic Object getProxy(ClassLoader classLoader) {return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);}@Overridepublic 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*/@Overridepublic 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 {@Overridepublic 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君 按捺不住心中的喜悦,开始编写测试代码。代码如下:
@Testpublic 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君 欢呼
总结
正所谓树欲静而风不止,欲知后事如何,请看下回分解(✪ω✪)