第十七章 Spring之假如让你来写AOP——Advice(通知)上篇
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之曾经的老朋友——事务
第二十六章 Spring之假如让你来写事务——初稿篇
第二十七章 Spring之假如让你来写事务——铁三角篇
第二十八章 Spring之假如让你来写事务——属性篇
第二十九章 Spring之假如让你来写事务——状态篇
第三十章 Spring之假如让你来写事务——管理篇
第三十一章 Spring之假如让你来写事务——融入IOC容器篇
第三十二章 Spring之源码阅读——事务篇
第四部分——MVC篇
第三十三章 Spring之梦开始的地方——MVC
第三十四章 Spring之假如让你来写MVC——草图篇
第三十五章 Spring之假如让你来写MVC——映射器篇
第三十六章 Spring之假如让你来写MVC——拦截器篇
第三十七章 Spring之假如让你来写MVC——控制器篇
第三十八章 Spring之假如让你来写MVC——适配器篇
第三十九章 Spring之假如让你来写MVC——番外篇:类型转换
第四十章 Spring之假如让你来写MVC——ModelAndView篇
第四十一章 Spring之假如让你来写MVC——番外篇:数据绑定
第四十二章 Spring之假如让你来写MVC——视图篇
第四十三章 Spring之假如让你来写MVC——上传文件篇
第四十四章 Spring之假如让你来写MVC——异常处理器篇
第四十五章 Spring之假如让你来写MVC——国际化篇
第四十六章 Spring之假如让你来写MVC——主题解析器篇
第四十七章 Spring之假如让你来写MVC——闪存管理器篇
第四十八章 Spring之假如让你来写MVC——请求映射视图篇
第四十九章 Spring之假如让你来写MVC——番外篇:属性操作
第五十章 Spring之假如让你来写MVC——融入IOC容器篇
第五十一章 Spring之源码阅读——MVC篇
第五部分——Boot篇
第五十二章 Spring之再进一步——Boot
第五十三章 Spring之假如让你来写Boot——环境篇
第五十四章 Spring之假如让你来写Boot——注解篇(上)
第五十五章 Spring之假如让你来写Boot——注解篇(下)
第五十六章 Spring之假如让你来写Boot——SPI篇
第五十七章 Spring之假如让你来写Boot——配置文件篇(上)
第五十八章 Spring之假如让你来写Boot——配置文件篇(下)
第五十九章 Spring之假如让你来写Boot——番外篇:再谈Bean定义
第六十章 Spring之假如让你来写Boot——自动装配篇
第六十一章 Spring之假如让你来写Boot——番外篇:杂谈Starter
第六十二章 Spring之假如让你来写Boot——番外篇:重构BeanFactory
第六十三章 Spring之假如让你来写Boot——番外篇:再谈ApplicationContext
第六十四章 Spring之假如让你来写Boot——内嵌Web容器篇
第六十五章 Spring之假如让你来写Boot——Main方法启动篇
第六十六章 Spring之最终章——结语篇
文章目录
前言
对于Spring一直都是既熟悉又陌生,说对它熟悉吧,平时用用没啥问题,但面试的时候被问的一脸懵逼,就很尴尬,都不好意思在简历上写着熟悉Spring了

所以决定花点时间研究研究Spring的源码。主要参考的书籍是:《Spring源码深度解析(第2版)》、《Spring揭秘》、《Spring技术内幕:深入解析Spring架构与设计原理(第2版)》
书接上回,在上篇 第十六章 Spring之假如让你来写AOP——Pointcut(切点)篇 中,A君 已经完成 Pointcut(切点) 的内容,虽然糙了点。接下来看看 A君 会有什么骚操作吧
尝试动手写IOC容器
出场人物:A君(苦逼的开发)、老大(项目经理)
背景:老大要求A君在一周内开发个简单的 IOC容器
前情提要: A君 已经完成 Pointcut(切点) 的内容 。。。
第十九版(上) 参数匹配器
“Pointcut(切点) 弄得差强人意,先这样把,时间来不及了,后续再优化。接下来要考虑 Advice(通知) 的事了。我看了你之前做的初稿,Advice(通知) 思路是对的,懂得提取前置、后置通知成接口,但是方法调用不支持参数,多个通知顺序问题,细节部分有待提高。还有 环绕通知、引介通知 没有实现,这部分内容也去考虑一下把!” 老大 布置完任务,也不等 A君 回答,就火急火燎的离开了
“得,项目开始赶了。 搞不好又得加班。” A君 无言,算了,还是开始干活吧,现在不是想这些乱七八糟的时候。A君 先从通知执行顺序开始,这个好说,只需要定义一个接口,根据数字排序即可。A君 定义 Ordered 接口,代码如下:
public interface Ordered {
int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
int getOrder();
}
对于不同通知来说,Ordered 显得有点多余,像前置、后置这些,不需要 Ordered 即可确定执行顺序,只有同类型的通知,再由 Ordered 进行进一步排序。A君 又定义了个 AspectJPrecedenceInformation 接口,代码如下:
import com.hqd.ch03.v19.core.Ordered;
/**
* 切面信息
*/
public interface AspectJPrecedenceInformation extends Ordered {
String getAspectName();
/**
* 是否前置通知
*/
boolean isBeforeAdvice();
/**
* 是否后置通知
*/
boolean isAfterAdvice();
}
排序问题搞定后,A君 开始琢磨 Advice(通知) 参数问题,这部分内容是必须的,自己整的 AOP 不支持参数,都不好意思说出口。A君 开始思索如何实现:直接把目标方法的参数扔给通知方法吗?好像不太行,通知方法都有个JoinPoint类型的默认参数,这个参数用户可能用,也可能不用。这个不能确定,那么就需要根据实际情况去计算了。解决完JoinPoint参数的问题,那么接下来可以把参数数组直接扔过去了吗?好像也不太行,一来参数数组在传递的过程中可能发生变化,二来有可能通知方法有可能就用几个参数,下标方案也不太成。那只能根据方法名了,方法名是唯一的,这个编译时候可以保证,当然可以通过字节码修改,这个就不在谈论的范畴之内了。思路已经确定了,老套路,A君 定义 ParameterNameDiscoverer 作为接口规范,代码如下:
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public interface ParameterNameDiscoverer {
/**
* 获取方法参数
*
* @param method
* @return
*/
String[] getParameterNames(Method method);
/**
* 获取构造器参数
*
* @param ctor
* @return
*/
String[] getParameterNames(Constructor<?> ctor);
}
接下来就是具体实现了,这个简单,直接反射获取就行了,A君 随手写了几行代码测试下,结果却让 A君 傻眼了。结果如下:

“我那又大又圆的参数名呢?” A君 愕然。查阅相关资料后,原来 Java 在编译后,参数名会被擦除。JDK8 才支持保留参数名信息,只是编译时需要添加 -parameters 参数,这样子反射才能获取的到。说干就干,A君 先在 IDEA 添加对应的参数,如下:

然后再运行下代码(运行之前记得先刷新下),运行结果如下:

这回正常了。A君 定义 StandardReflectionParameterNameDiscoverer 作为该方案实现,代码如下:
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
public class StandardReflectionParameterNameDiscoverer implements ParameterNameDiscoverer {
@Override
public String[] getParameterNames(Method method) {
return getParameterNames(method.getParameters());
}
@Override
public String[] getParameterNames(Constructor<?> ctor) {
return getParameterNames(ctor.getParameters());
}
private String[] getParameterNames(Parameter[] parameters) {
String[] names = new String[parameters.length];
for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
if (!parameter.isNamePresent()) {
return null;
}
names[i] = parameter.getName();
}
return names;
}
}
代码虽然写完了,不过显然不是好办法。首先无法保证所有用户都是用 JDK8 以上的版本,就算是,还得要求编译时候加参数,从用户体验上来说,也是相当糟糕的。如此说来,只能另寻他法了。之前,A君 在折腾 JVM 的时候,看到一个叫 局部变量表 的东西,简单的说,这个表会保留方法运行时候的变量信息,自然也包括参数信息。A君 随便拿个类验证下,代码如下:

字节码如下:

可以看到,局部变量表 中包含了参数信息。具体内容 A君 就不再这里长篇大论了,这玩意涉及到 JVM 的东西,此次的重点在 AOP,不能一叶障目。对于字节码的操作,ASM 已经提供对应的API。A君 只要根据方法名、参数信息匹配到目标方法即可,A君 定义 LocalVariableTableParameterNameDiscoverer 类作为实现。代码如下:
import org.apache.commons.lang3.StringUtils;
import org.objectweb.asm.*;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* 局部变量表获取参数名
*/
public class LocalVariableTableParameterNameDiscoverer implements ParameterNameDiscoverer {
private String getName(final Executable executable) {
/**
* 构造器
*/
if (executable instanceof Constructor) {
return "<init>";
} else if (executable instanceof Method) {
//正常方法
return executable.getName();
} else {
return null;

浙公网安备 33010602011771号