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篇
文章目录
- Spring源码阅读目录
- 前言
- 尝试动手写IOC容器
-
-
- [第三十四版 适配器](#第三十四版 适配器)
-
- 总结
前言
对于Spring一直都是既熟悉又陌生,说对它熟悉吧,平时用用没啥问题,但面试的时候被问的一脸懵逼,就很尴尬,都不好意思在简历上写着熟悉Spring了
所以决定花点时间研究研究Spring的源码。主要参考的书籍是:《Spring源码深度解析(第2版)》、《Spring揭秘》、《Spring技术内幕:深入解析Spring架构与设计原理(第2版)》
书接上回,在上篇 第三十七章 Spring之假如让你来写MVC------控制器篇 中,A君 已经完成了 控制器 部分的功能了。接下来看看 A君 会有什么骚操作吧
尝试动手写IOC容器
出场人物:A君 (苦逼的开发)、老大(项目经理)
背景:老大 要求 A君 在一周内开发个简单的 IOC容器
前情提要:A君 已经完成了 控制器 部分的功能了 。。。
第三十四版 适配器
"A君 呐,你用if..else
来判断调用哪个处理器就很灵性了。" 老大 感叹道
"额。" A君 不知道怎么结果话茬,只能装傻充愣了
"这种拓展性太低了,后边还有其他处理器的实现方式呢。这样子往后稍微发展下,你的代码就变成了传说中的 屎山代码 。还有一点,就是关于请求参数的处理,要知道一个请求可以携带很多参数的,而你现在这个实现,用户只能去request
里获取,这一点用户体验非常的糟糕。去把这两部部分内容处理下吧。" 说完,老大 就没再说什么了
看到 老大 没有继续往下说的意思,看来是要自己发挥。A君 默默的退了出去,准备着手干活了
对于这种类型适配器,A君 其实并不算陌生,早在之前实现 IOC容器 的时候,A君 就已经接触过,只不过当时是根据属性类型找到对应的转换器进行类型转换。而这次,只是改成根据不同的 控制器 找到对应的 适配器 而已,其本质本无实际上的区别。那么 适配器 的接口就好定义了,必然存在着两个方法,一个是能不能转换,另一个是进行转换。基本盘有了,接下来就要考虑特殊性了,由于HTTP
中定义了Last-Modified
。其规范如下:
这个属性跟缓存息息相关,如果没有超过对应时间,则服务器可以直接不处理,返回上次结果。那么整体的接口定义就出来了,A君 新增HandlerAdapter
接口,代码如下:
java
/**
* 控制器的适配器,找到对应的控制器
*/
public interface HandlerAdapter {
/**
* 是否可以转换
*
* @param handler
* @return
*/
boolean supports(Object handler);
/**
* 处理请求
*
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
Object handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
/**
* 如果没有默认实现,默认返回-1
* 这个方法放在这里感觉并不合适,LastModified和适配器并有什么关系,违反了接口单一原则,并不符合Spring一贯的作风
* 新版本已经弃用,这里考虑到老项目
*
* @param request
* @param handler
* @return
*/
@Deprecated
long getLastModified(HttpServletRequest request, Object handler);
}
Last-Modified
的处理方法放在这里感觉并不合适,Last-Modified
和适配器本身并没有什么关系,违反了接口单一原则。按理说,适配器只负责找到处理对应的控制器进行处理,并不会去关心Last-Modified
这些东西,此处的设计不符合Spring一贯的作风,不知道当初是基于什么考虑才如此设计,这里为了贴近Spring,所以也加上getLastModified
方法。在Spring5.3.9后续版本中,该方法已经标记成弃用
好了,接口出来后,先挑个简单的练练手,A君 盯上了Controller
和Servlet
,原因也简单,实现了对应接口的,其实是最好处理的,只需要强转后调用该接口方法就行了。这里 A君 就以Controller
适配器为例,新增SimpleControllerHandlerAdapter
类,代码如下:
java
/**
* Controller相关接口适配
*/
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return (handler instanceof Controller);
}
@Override
public Object handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return ((Controller) handler).handleRequest(request, response);
}
@Override
@SuppressWarnings("deprecation")
public long getLastModified(HttpServletRequest request, Object handler) {
if (handler instanceof LastModified) {
return ((LastModified) handler).getLastModified(request);
}
return -1L;
}
}
简单过后,A君 就开始头疼了,简单之所以简单,是因为实现了对应接口,方法参数、返回值都是固定的。而基于注解的 控制器 ,这两个没有一个是能确定的,不能确定就意味着:需要框架去推算匹配,那么参数处理就会变得异常的麻烦。没办法,只能先易后难。A君 谨遵 老大 的教诲,对于有公共内容的,提取出来一个抽象类。于是,A君 新增AbstractHandlerMethodAdapter
类,代码如下:
java
public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered {
@Override
public final boolean supports(Object handler) {
return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}
@Override
public final Object handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
protected abstract Object handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception;
protected abstract boolean supportsInternal(HandlerMethod handlerMethod);
@Override
@SuppressWarnings("deprecation")
public final long getLastModified(HttpServletRequest request, Object handler) {
return getLastModifiedInternal(request, (HandlerMethod) handler);
}
@Deprecated
protected abstract long getLastModifiedInternal(HttpServletRequest request, HandlerMethod handlerMethod);
}
简单的完成了,接下来就得考虑困难的事了,当务之急,就是要解决参数匹配的问题,不然 注解适配类 无从实现
request
参数
要想自动适配 控制器 参数,A君 得先知道哪些可以成为入参,也就是一个request
请求中,哪些可以作为 控制器 的参数。A君 在翻阅 Servlet规范 时,看到这些东西,如下:
- 请求参数:
- 属性:
3.头:
那么正常情况下,request
中:属性(Attributes)、请求头(Headers)、请求参数(Params) 都可以作为 控制器 的入参。明白参数从哪里之后,就可以先把这部分东西抽象化了。这里要注意的是,虽然 A君 一直以 Servlet规范 作为例子,实际上在Web环境下,不仅仅 Servlet 一种规范,还有很多不同的规范,像:WebSocket、 WebFlux、Portlet 等,所以这里定义接口,更是为了通用性,适配这些规范。A君 定义 RequestAttributes
接口,用来封装 属性(Attributes) 的相关操作。代码如下:
java
/**
* request请求属性
*/
public interface RequestAttributes {
int SCOPE_REQUEST = 0;
int SCOPE_SESSION = 1;
/**
* request作用域
*/
String REFERENCE_REQUEST = "request";
/**
* session作用域
*/
String REFERENCE_SESSION = "session";
Object getAttribute(String name, int scope);
void setAttribute(String name, Object value, int scope);
void removeAttribute(String name, int scope);
String[] getAttributeNames(int scope);
/**
* 注册回调
*
* @param name
* @param callback
* @param scope
*/
void registerDestructionCallback(String name, Runnable callback, int scope);
/**
* 处理引用类型
*
* @param key
* @return
*/
Object resolveReference(String key);
/**
* 获取SessionId
*
* @return
*/
String getSessionId();
/**
* 获取Session同步锁
*
* @return
*/
Object getSessionMutex();
}
这个接口只是规定了 属性(Attributes) 的相关操作,显然是远远不够的,还需要 请求头(Headers)、请求参数(Params) 的相关操作,于是,A君 对其进行拓展,定义WebRequest
接口。代码如下:
java
public interface WebRequest extends RequestAttributes {
/**
* 获取请求头
*
* @param headerName
* @return
*/
String getHeader(String headerName);
String[] getHeaderValues(String headerName);
Iterator<String> getHeaderNames();
String getParameter(String paramName);
String[] getParameterValues(String paramName);
Iterator<String> getParameterNames();
Map<String, String[]> getParameterMap();
Locale getLocale();
String getContextPath();
String getRemoteUser();
Principal getUserPrincipal();
boolean isUserInRole(String role);
boolean isSecure();
/**
* 检查资源是否过期
*
* @param lastModifiedTimestamp
* @return
*/
boolean checkNotModified(long lastModifiedTimestamp);
boolean checkNotModified(String etag);
boolean checkNotModified(String etag, long lastModifiedTimestamp);
String getDescription(boolean includeClientInfo);
}
前文提到,由于存在着多种规范,那么对于获取其真实的请求或者响应对象就很有必要了,A君 不可以预判到用户要用哪种协议,所以只能再定义个接口,用来获取真实的请求对象、响应对象。新增NativeWebRequest
接口,代码如下:
java
public interface NativeWebRequest extends WebRequest {
/**
* 获取真实请求对象
*
* @return
*/
Object getNativeRequest();
/**
* 获取真实响应对象
*
* @return
*/
Object getNativeResponse();
<T> T getNativeRequest(Class<T> requiredType);
<T> T getNativeResponse(Class<T> requiredType);
}
接口定义完成之后,接下来就是实现了,虽然存在着多种规范,但 A君 并不太关心,起码现在不用关心,这里是 MVC 的主场。A君 只关注 Servlet 的实现,那问题就简单了,这些东西只需要从 Servlet容器 中获取就行了。A君 新增ServletWebRequest
类,代码如下:
java
public class ServletWebRequest extends ServletRequestAttributes implements NativeWebRequest {
@Override
public String getParameter(String paramName) {
return getRequest().getParameter(paramName);
}
@Override
public String getHeader(String headerName) {
return getRequest().getHeader(headerName);
}
//省略其他方法
}
参数类型转换
解决了参数的来源问题,现在就可以开始进行参数的转换了。要知道request
请求过来的参数可只有字符串类型或字节流,字节流现在不在考虑的范围之内,那还需要把字符串转成 控制器 对应的类型才行。所谓自动适配,无非就是把请求参数转成方法参数罢了。转换接口依旧是那两板斧,能处理吗?进行处理!玩不出什么花来。于是,A君 照猫画虎,定义HandlerMethodArgumentResolver
接口,代码如下:
java
/**
* 参数转换接口
*/
public interface HandlerMethodArgumentResolver {
boolean supportsParameter(MethodParameter parameter);
Object resolveArgument(MethodParameter parameter, NativeWebRequest webRequest) throws Exception;
}
接着就是实现了,类那么多,A君 还没颠到全部实现的地步,只要实现主流的类就行了,剩余的部分嘛?就看用户自己发挥了。A君 决定先从两个最重要的参数开始,那就是:request
、response
。两个都是类似的,这里就以request
为例,A君 新增ServletRequestMethodArgumentResolver
类,那么问题来了,要支持那些类型呢? 对于Web来说,request
可谓是举足轻重,主要是从request
可以拿到太多的东西了,比如:ServletRequest
、HttpSession
、Principal
、InputStream
等。那么能从request
中获取的东西,就是ServletRequestMethodArgumentResolver
支持的类型。代码如下:
java
public class ServletRequestMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
return (WebRequest.class.isAssignableFrom(paramType) ||
ServletRequest.class.isAssignableFrom(paramType) ||
HttpSession.class.isAssignableFrom(paramType) ||
(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
Principal.class.isAssignableFrom(paramType) ||
InputStream.class.isAssignableFrom(paramType) ||
Reader.class.isAssignableFrom(paramType) ||
HttpMethod.class == paramType);
}
@Override
public Object resolveArgument(MethodParameter parameter,
NativeWebRequest webRequest) throws Exception {
Class<?> paramType = parameter.getParameterType();
if (WebRequest.class.isAssignableFrom(paramType)) {
if (!paramType.isInstance(webRequest)) {
throw new IllegalStateException("Current request is not of type [" + paramType.getName() + "]: " + webRequest);
}
return webRequest;
}
if (ServletRequest.class.isAssignableFrom(paramType)) {
return resolveNativeRequest(webRequest, paramType);
}
return resolveArgument(paramType, resolveNativeRequest(webRequest, HttpServletRequest.class));
}
}
这还是很简单的,一堆if...esle
就行了,A君 感叹道。接着还需要支持下基本类型,首先要考虑的是如何从request
中获取到参数,HTTP
请求只有字符串或字节流,按照类型匹配显然是不合适的。现在 参数转换器 要做的事情,就是根据名称从request
对应的值,转成 控制器 参数的类型:
解析 是 否 request参数 参数类型 是否匹配类型 成功转换 转换失败 控制器参数 结束
A君 先定义一个NamedValueInfo
类,用以包装参数名相关信息。代码如下:
java
protected static class NamedValueInfo {
private final String name;
private final boolean required;
private final String defaultValue;
public NamedValueInfo(String name, boolean required, String defaultValue) {
this.name = name;
this.required = required;
this.defaultValue = defaultValue;
}
}
接着,A君 思考了下:根据方法参数名(方法参数名的获取在AOP 相关章节中有过说明,这里就不在进行赘述了 ),去request
中获取值,不论是 属性(Attributes)、请求头(Headers)、请求参数(Params) 都是从 request
中获取的,只是地方不一样罢了,那这就可以提取一个抽象类了。于是,A君 定义AbstractNamedValueMethodArgumentResolver
类,代码如下:
java
public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public final Object resolveArgument(MethodParameter parameter, NativeWebRequest webRequest) throws Exception {
/**
* 获取目标参数名
*/
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
Object resolvedName = namedValueInfo.name;
if (resolvedName == null) {
throw new IllegalArgumentException(
"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
}
/**
* 从request获取值,由子类实现
*/
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
if (arg == null) {
if (namedValueInfo.defaultValue != null) {
arg = namedValueInfo.defaultValue;
} else if (namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
} else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = namedValueInfo.defaultValue;
}
handleResolvedValue(arg, namedValueInfo.name, parameter, webRequest);
return arg;
}
//省略其他方法
}
好了,抽象类已经抽取完毕,那么 属性(Attributes)、请求头(Headers)、请求参数(Params) 都大同小异了,只需要去各自的区域取值就行了。A君 这里以 请求参数(Params) 为例,新增RequestParamMethodArgumentResolver
类,代码如下:
java
public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver
implements UriComponentsContributor {
@Override
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.hasParameterAnnotation(RequestParam.class)) {
/**
* map类型
*/
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
return (requestParam != null && StringUtils.hasText(requestParam.name()));
} else {
return true;
}
} else {
/**
* 是否是Optional
*/
parameter = parameter.nestedIfOptional();
if (this.useDefaultResolution) {
return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
} else {
return false;
}
}
}
@Override
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
Object arg = null;
if (arg == null) {
String[] paramValues = servletRequest.getParameterValues(name);
if (paramValues != null) {
arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
}
}
return arg;
}
//省略其他方法
}
好了,现在都准备好了,剩下的就是把这些参数处理器进行一下整合,方便别人使用。A君 在定义HandlerMethodArgumentResolverComposite
类,这个不负责具体实现,而是调用别的类来实现转换,类似于一个管理者的角色。代码如下:
java
public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
private final List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>();
private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache =
new ConcurrentHashMap<>(256);
@Override
public boolean supportsParameter(MethodParameter parameter) {
return getArgumentResolver(parameter) != null;
}
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
@Override
public Object resolveArgument(MethodParameter parameter, NativeWebRequest webRequest) throws Exception {
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException("Unsupported parameter type [" +
parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
}
return resolver.resolveArgument(parameter, webRequest);
}
好嘞,现在处理参数部分也完事了,饶了一大圈,总算把参数处理完了,现在可以继续前行了
注解适配器
解决完最麻烦的参数处理之后,剩下的 注解适配器 就没有什么东西了,跟反射调用基本一样了。A君 新增RequestMappingHandlerAdapter
类,代码如下:
java
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
implements BeanFactoryAware, InitializingBean {
private HandlerMethodArgumentResolverComposite argumentResolvers;
protected Object invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
return invocableMethod.invokeAndHandle(webRequest);
} finally {
webRequest.requestCompleted();
}
}
//省略其他代码
}
改造DispatcherServlet
适配器 弄完之后,A君 想起还需要对DispatcherServlet
进行改造,毕竟当初折腾 适配器 的目的,不就是为了替换DispatcherServlet
中的if...else
吗?改造如下:
- 新增 适配器 类:
- 替换
doDispatch
方法中的if...else
:
测试
现在一切都准备好了,可以进入检验成果的时候了。A君 修改HelloController
,新增一个参数。代码如下:
java
@Controller
public class HelloController {
@RequestMapping("/hello")
public String sayHello(HttpServletRequest req, HttpServletResponse resp, String str) {
String key = "message";
String message = (String) req.getAttribute(key);
if (message == null) {
message = "";
}
req.setAttribute(key, message + " V34 HandleMapping! " + str);
return "hello";
}
}
其他测试代码不需要变动,编写测试代码如下:
java
@Test
public void v34() throws Throwable {
System.out.println("############# 第三十四版: 适配器篇 #############");
Tomcat tomcat = new Tomcat();
//设置端口
tomcat.setPort(8082);
//设置静态资源路径
String webApp = new File("src/main/resources/v34").getAbsolutePath();
Context context = tomcat.addWebapp("/test/", webApp);
tomcat.start();
//挂起
tomcat.getServer().await();
}
测试结果如下:
后台成功接收到参数并显示出来了,说明 A君 的努力并没有白费。OK!这下子总算弄好了,也可以向 老大 交差了
总结
正所谓树欲静而风不止,欲知后事如何,请看下回分解(✪ω✪)