目录
SpringMVC深度探险(一) —— SpringMVC前传
SpringMVC深度探险(二) —— SpringMVC概览
SpringMVC深度探险(三) —— DispatcherServlet与初始化主线
SpringMVC深度探险(四) —— SpringMVC核心配置文件详解
1. 从源码的角度来看 SpringMVC
SpringMVC核心流程图
简单总结
首先请求进入DispatcherServlet 由DispatcherServlet 从HandlerMappings中提取对应的Handler
此时只是获取到了对应的Handle,然后得去寻找对应的适配器,即:HandlerAdapter
拿到对应HandlerAdapter时,这时候开始调用对应的Handler处理业务逻辑了(这时候实际上已经执行完了我们的Controller) 执行完成之后返回一个ModeAndView
这时候交给我们的ViewResolver通过视图名称查找出对应的视图然后返回
最后 渲染视图 返回渲染后的视图 -->响应请求
SpringMVC 源码解析
首先我们查看继承关系(关键查看蓝色箭头路线) 会发现DispatcherServlet无非就是一个HttpServlet
由此,我们可以去查看Servlet的关键方法:service,doGet,doPost
-
service:
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
processRequest(request, response);
}
else {
super.service(request, response);
}
}
-
doGet:
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
-
doPost:
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
这里会发现无论是哪个方法最后都调用了 processRequest(request, response);我们把焦点放在这个方法上,会发现一个核心的方法:
doService(request, response);然后会发现 这个方法貌似,呃..有点不一样:
protected abstract void doService(HttpServletRequest request, HttpServletResponse response)
throws Exception;
//它居然是一个抽象方法...这就得回到刚刚的继承关系中,找到他的子类了:DispatcherServlet
反正我看到这个方法的实现的时候,脑海里就浮现出4个字:花 里 胡 哨 。代码太多,就不放在笔记里面了,太占地方了.. 为什么这样说呢? 因为你看完之后会发现关键在于:doDispatch(request, response);是的,没看错,这一行才是关键!
我们把视角切入这个方法 (至于代码..还是不放进来了.. ) 总结一下:
把要用的变量都定义好:比如我们的ModelAndView以及异常..等等;
下面即将看到的是一个熟悉的陌生人(噢不,关键词)
processedRequest = checkMultipart(request);
"Multipart" 这个关键词好像在哪见过??..让我想想..(渐渐步入了知识盲区) 哦对!在文件上传的时候!(勉强想起来了。。) 是的,其实这行代码就是判断当前请求是否是一个二进制请求(有没有带文件) 当然 这里只是提一下,并不是本文的核心内容。。。(有时间的小伙伴可以自己去了解一下)
好的,现在回到我们的主题,来看看这个方法:
mappedHandler = getHandler(processedRequest);
看过我们上面流程图的同学应该会知道他现在在干嘛。 现在来获取我们的Handler了..从哪获取呢? 从他的HandlerMapping里面获取。
问题1:至于这个HandlerMappings 哪里来的呢
这个等下讨论 我们先来看看他获取的代码:
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
我们能看见他是在遍历一个handlerMappings
问题2:至于这个handlerMapping是什么呢
是2个我们不认识的东西,至于是什么,现在说了也不知道,我们继续往下走,可以看见图片上1188行代码, 他从这个mapping 里面获取了一个handler 如果获取到了 这个方法就走完了, 不然就下一次循环
问题1解释:
我们先回到刚刚那个问题,这个HandlerMapping 哪里来的呢。 不多说,上图:
我们在源码包的DispatcherServlet.properties文件下会看见, 他定义了图片里的这些属性。 重点放在方框内,第一个属性,就是我们刚看见的HandlerMappings, 也就是说 HandlerMappings也就是他自己事先定义好的呢。至于第二个属性,咱们待会儿见~
也就是说SpringMVC自己自带了2个HandlerMapping 来供我们选择 至于 为什么要有2个呢? 这时候得启动项目从断点的角度来看看了;
我们用2种方式来注册Controller 分别是:
-
作为Bean的形式:
@Component("/test")
public class TesrController implements org.springframework.web.servlet.mvc.Controller{
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("1");
return null;
}
}
-
以Annotation形式:
@Controller
public class AnnotationController {
@RequestMapping("/test2")
public Object test(){
System.out.println("test");
return null;
}
}
我们先用Bean的方式来跑:
视角走到我们的mappedHandler = getHandler(processedRequest);里面
问题2解释:
来,跟着箭头走,我们发现 我们以Bean的形式注册的Controller 可以从这个BeanNameUrlHandlerMapping里面获取到对应的Handler ; 这里 我们是不是对于这个HandlerMapping有了懵懂的了解了?
猜想1:
我们来猜一下 如果是以Annotation的形式注册的Controller的话 就会被第二个HandlerMapping获取到。 至于对不对 这个问题我们先留着。
我们先让代码继续走,会发现 Handler返回出来紧接着会执行下面这个方法,这个方法我们从流程图中可以了解到,就是在找一个适配器。
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
问题3:何为适配器?
我们先来看看他这个方法里面干了啥:
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
if (ha.supports(handler)) {
return ha;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
其实能看见他是从一个handlerAdapters属性里面遍历了我们的适配器 这个handlerAdapters哪来的呢? 跟我们的HandlerMappings一样 在他的配置文件里面有写,就是我们刚刚所说的 待会儿见的那个东西~ 不多说 上图:
问题3解释:
至于什么是适配器,我们结合Handler来讲, 就如我们在最开始的总结时所说的, 一开始只是找到了Handler 现在要执行了,但是有个问题,Handler不止一个, 自然而然对应的执行方式就不同了, 这时候适配器的概念就出来了:对应不同的Handler的执行方案。
当找到合适的适配器的时候, 基本上就已经收尾了,因为后面在做了一些判断之后(判断请求类型之类的),就开始执行了你的Handler了,上代码:
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
这个mv就是我们的ModlAndView 其实执行完这一行 我们的Controller的逻辑已经执行完了, 剩下的就是寻找视图 渲染图的事情了....
我们这里只是使用了Bean的形式执行了一遍流程 假设使用Annotation呢?
SpringMVC BeanName方式和Annotation方式注册Controller源码分析
现在我们来使用Annotation来注册Controller看看。我们这里只看不同的地方。
猜想1证明:
首先在这个HandlerMappings这里之前的那个就不行了 这里采用了另外一个HandlerMapping 其实也就证明了我们的猜想1
然后就是到了我们的适配器了:
这里我们会看到用的是这个适配器 而我们的Bean方式注册的Controller 的话 使用的是另外两个适配器来的,至于有什么区别呢? 我们来看看他执行的时候:
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
//我们的Annotation的形式 是拿到这个handler作为一个HandlerMethod 也就是一个方法对象来执行 这时候我们看看Bean是什么样子的:
@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return ((Controller) handler).handleRequest(request, response);
}
由最开始可以看到 我们如果以Bean的形式注册Controller的话 我们的实现一个Controller的接口 在这里 他把我们的handler强制转换为一个Controller来执行了。
总结
其实我们的SpringMVC关键的概念就在于Handler(处理器) 和Adapter(适配器)
通过一个关键的HandlerMappings 找到合适处理你的Controller的Handler 然后再通过HandlerAdapters找到一个合适的HandlerAdapter 来执行Handler即Controller里面的逻辑。 最后再返回ModlAndView...
2. 分析spring mvc源码
在分析spring mvc源码之前,先看一张图:
请求处理的过程:
1.DispatcherServelt作为前端控制器,拦截request对象。
2.DispatcherServlet接收到request对象之后,查询HandlerMapping,得到一个HandlerExecutionChain对象。
3.DispatcherServlet将Handler对象交由HandlerAdapter(适配器模式的典型应用),调用相应的controller方法。
4.Controller方法返回ModelAndView对象,DispatcherServlet将view交由ViewResolver进行解析,得到相应的视图。用model渲染视图。
5.返回响应结果。
整个过程的流程其实就是DispatcherServelt中doDispatch()方法的调用过程。
protected void doDispatch(HttpServletRequest request,
HttpServletResponse response) throws Exception {
//第一步拦截request对象,doDispatch()方法在doService()方法中被调用,request对象是经过处理的。
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
//和文件的上传和下载有关系,判断请求的类型是否是multipart类型
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
//主要看这里,这里是得到HandlerExecutionChain的方法。关于Handler()方法向下看
mappedHandler = getHandler(processedRequest);
if ((mappedHandler == null) ||
(mappedHandler.getHandler() == null)) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
//这里已经获取到HandlerExecutionChain对象,接下来就要获取HandlerAdapter对象,调用Handler对象的方法。
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler
//有关浏览器缓存的设定(304)
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request,
mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" +
getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(
lastModified) && isGet) {
return;
}
}
//pan'du
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response,
mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//解析视图,数据渲染
applyDefaultViewName(request, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception ex) {
dispatchException = ex;
}
processDispatchResult(processedRequest, response, mappedHandler,
mv, dispatchException);
} catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
} catch (Error err) {
triggerAfterCompletionWithError(processedRequest, response,
mappedHandler, 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);
}
}
}
}
protected HandlerExecutionChain getHandler(HttpServletRequest request)
throws Exception {
//遍历HandlerMappingList对象(存储若干个HandlerMapping对象),不断调用,直到不为空为止,否则返回null
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler map [" + hm +
"] in DispatcherServlet with name '" + getServletName() +
"'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
3. SpringMVC 深度探险
SpringMVC深度探险(一) —— SpringMVC前传
两种截然不同的针对表示层的解决方案的设计思路:
- 以服务器端应用程序为主导来进行框架设计
- 以浏览器页面组件(及其自身的事件触发模型)为主导来进行框架设计
业界对于上述这两种不同的设计模型也赋予了不同的名词定义:前一种被称之为MVC模型;后一种则被称之为组件模型,也有称之为事件模型。
在MVC模型中,浏览器端和服务器端的交互关系非常明确:无论采取什么样的框架,总是以一个明确的URL作为中心,辅之以参数请求。因此,URL看上去就像是一个明文的契约,当然,真正蕴藏在背后的是Http协议。所有的这些东西都被放在了台面上,我们可以非常明确地获取到一次交互中所有的Http信息。这也是MVC模型中最为突出的一个特点。
一些与MVC模型截然不同的特点: 1. 框架通过对HTML进行行为扩展来干预和控制浏览器与服务器的交互过程。 我们可以发现,Tapestry5的请求页面被加入了更多的HTML扩展,这些扩展包括对HTML标签的扩展以及HTML标签中属性的扩展。而这些扩展中,有不少直接干预了浏览器与服务器的交互。例如,上面例子中的t:validate="required,minlength=3"扩展实际上就会被自动映射到服务器端程序中带有@Component(id="password")标注的PasswordField组件上,并在提交时自动进行组件化校验。而当页面上的提交按钮被点击触发时,默认在服务器端的onSuccess方法会形成响应并调用其内部逻辑。 2. 页面组件的实现是整个组件模型的绝对核心 从上述的例子中,我们可以看到组件模型的实现不仅需要服务器端实现,还需要在页面上指定与某个特定组件进行事件绑定。两者缺一不可,必须相互配合,共同完成。因此整个Web程序的交互能力完全取决于页面组件的实现好坏。 3. 页面组件与服务器端响应程序之间的映射契约并不基于Http协议进行 在上面的例子中,从页面组件到服务器端的响应程序之间的映射关系是通过名称契约而定的。而页面上的每个组件可以指定映射到服务器端程序的具体某一个方法。我们可以看到这种映射方式并不是一种基于URL或者Http协议的映射方式,而是一种命名指定的方式。 在组件模型中,浏览器端和服务器端的交互关系并不以一个具体的URL为核心,我们在上述的例子中甚至完全没有看到任何URL的影子。不过这种事件响应式的方式,也提供给我们另外一个编程的思路,而这种基于契约式的请求-响应映射也得到了一部分程序员的喜爱。因而组件模型的粉丝数量也是很多的。 |
https://downpour.iteye.com/blog/1330537