先简单看下代码结构,springmvc是springframework的子项目,gradle中只依赖了外部的servlet,且是provided,我将他理解成纯粹的基于servlet的web框架
servlet的核心在于网络请求的分发,可以理解成web请求的uri和具体的处理方法的映射关系,可以按照流程划分成两部分,一部分做映射关系的初始化,另一部分是根据具体的http请求找到处理的方法,反射执行,返回结果。
springframework 5.0.x
那么springmvc核心就是继承HttpServlet,看下类图
DispatcherServlet类是核心,除了极少部分的工作在FrameworkServlet或者HttpServletBean中做,绝大多数核心业务在DispatcherServlet
核心过程1--初始化
我本以为他的init会在启动项目的时候直接执行,调试发现他会在第一次接收到http请求时执行onRefresh方法,这个调用的开关放在FrameworkServlet里
FrameworkServlet
DispatcherServlet
在initStrategies方法中去执行servlet需要的对象,红框之外的都是一些注册文件处理器,多语言处理器,主题处理,异常处理,视图处理等,最核心的是红框内
initHandlerMappings 注册路径方法映射关系,initHandlerAdapters 注册处理方法的适配器
mapping的获取不是自己去扫描找,而是直接在context拿到HandlerMapping 类型的对象,所以这个mapping的创建是在初始化bean的时候做的
如果是获取所有的mapper,现在一共有5种mapping,也就是在注册映射时 springmvc 把它分了几类,我了解的就RequestMappingHandlerMapping
和beanNameUrlHandlerMapping,比如RequestMappingHandlerMapping就是按照Controller注解的方式来注册
这个HandlerMapping 接口的getHandler方法返回一个HandlerExecutionChain 对象,里面包含了对应的Method和bean
到这,初始化的节点结束;
核心过程2 --响应请求
一个http请求到servlet,他会根据是否已经初始化选择是否执行onRefresh,一般会根据不同的请求来执行具体的doGet/doPost等
但是不管是什么都会进入到doService
doService里面调用doDispatch,不挂截图,挂代码,解释在注释
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// 这里是针对二进制文件,比如图片视频的处理
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
// 这里是根据当前的请求去匹配上面说的5种mapping中的哪一种,再使用mapping找到当前的path对应的HandlerExecutionChain
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
// 用mapping.getHandler拿到HandlerMethod,再从适配器mapping中选择合适的adapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
// 这里是Get缓存上次的请求
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
//这是执行的核心,使用适配器反射执行
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
拿到adapter之后,HandleAdapter接口下适配当前的adapter,执行handleInternal
最终执行invoke,得到返回值是在ServletInvocableHandlerMethod
实际上最终执行就是一个method.invoke(bean,args),bean在HandlerMethod能拿到,args从request拿到
执行完之后,会执行接下来的响应返回,异常处理,拦截器处理等等,不过mvc的核心流程已经走完了。
手写一个简单的mvc
思路梳理一下
1.定义好需要的注解,包扫描注解,依赖注入,路径注解,参数
初始化过程:
2.扫描包(这里可以写成从xml读)
3.bean实例化
4.依赖注入
5.生成路径方法映射关系
处理请求:
6.根据请求path找到method
7.根据method所属class找到实例bean
8.处理method的参数列表
9.执行method.invoke(bean,args)
这里我写的处理请求只处理到get,其他的原理类似
其他倒没什么,处理参数列表的时候注意一点,如果我们是用注解定义的参数名,那么可以从注解的value拿到;如果不是,那么可能需要利用反射去获取函数的参数名,
java8之前都是用spring-core的LocalVariableTableParameterNameDiscoverer,java8之后新增Parameter类,可以Parameter.getName获取参数名,但是调试的时候会发现拿到的是args0之类的,这是因为javac生成class文件的时候,参数信息默认是不保存的,可以javac -parameters来做(我在idea2019里加了参数也不好用,拿到的还是args0,索性定义了注解)
贴一个demo用例
代码结构
具体的代码我放在Github上,欢迎Star和issue
https://github.com/wjkcoder/phorcys-mvc
说下怎么跑的,Jetty
用springboot用的多了,都忘了需要配置tomcat;用物理的又很麻烦,用tomcat-plugin也有问题,我选择了jetty,更快
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>9.4.32.v20200930</version>
</plugin>
pom里加个依赖,加个mvn的启动参数就ok
好了,就分享到这里,手撕源码少有的快乐!