java mvc 源码分析_java架构之路-(SpringMVC篇)SpringMVC主要流程源码解析(上)源码执行流程...

做过web项目的小伙伴,对于SpringMVC,Struts2都是在熟悉不过了,再就是我们比较古老的servlet,我们先来复习一下我们的servlet生命周期。

servlet生命周期

2896d69355ec27224a6ce55a1cde44aa.png

1)初始化阶段

当客户端向 Servlet 容器发出 HTTP 请求要求访问 Servlet 时,Servlet 容器首先会解析请求,检查内存中是否已经有了该 Servlet 对象,如果有,则直接使用该 Servlet 对象,如果没有,则创建 Servlet 实例对象,然后通过调用 init() 方法实现 Servlet 的初始化工作。需要注意的是,在 Servlet 的整个生命周期内,它的 init() 方法只能被调用一次。

2)运行阶段

这是 Servlet 生命周期中最重要的阶段,在这个阶段中,Servlet 容器会为这个请求创建代表 HTTP 请求的 ServletRequest 对象和代表 HTTP 响应的 ServletResponse 对象,然后将它们作为参数传递给 Servlet 的 service() 方法。service() 方法从 ServletRequest 对象中获得客户请求信息并处理该请求,通过 ServletResponse 对象生成响应结果。在 Servlet 的整个生命周期内,对于 Servlet 的每一次访问请求,Servlet 容器都会调用一次 Servlet 的 service() 方法,并且创建新的 ServletRequest 和 ServletResponse 对象,也就是说,service() 方法在 Servlet 的整个生命周期中会被调用多次。

3)销毁阶段

当服务器关闭或 Web 应用被移除出容器时,Servlet 随着 Web 应用的关闭而销毁。在销毁 Servlet 之前,Servlet 容器会调用 Servlet 的 destroy() 方法,以便让 Servlet 对象释放它所占用的资源。在 Servlet 的整个生命周期中,destroy() 方法也只能被调用一次。需要注意的是,Servlet 对象一旦创建就会驻留在内存中等待客户端的访问,直到服务器关闭或 Web 应用被移除出容器时,Servlet 对象才会销毁。

ee6c291c5563316ef67983c49dd05d5d.png

整个过程是比较复杂的,而且我们的参数是通过问号的形式来传递的,比如http://boke?id=1234,id为1234来传递的,如果我们要http://boke/1234这样来传递参数,servlet是做不到的,我们来看一下我们SpringMVC还有哪些优势。

1.基于注解方式的URL映射。比如http://boke/type/{articleType}/id/{articleId}

2.表单参数自动映射,我们不在需要request.getParament得到参数,参数可以通过name属性来自动映射到我们的控制层下。

3.缓存的处理,SprinMVC提供了缓存来提高我们的效率。

4.全局异常处理,通过过滤器也可以实现,只不过SprinMVC的方法会更简单一些。

5.拦截器的实现,通过过滤器也可以实现,只不过SprinMVC的方法会更简单一些。

6.下载处理

我们来对比一下SprinMVC的流程图。

SprinMVC的流程图

db979e7c7da685b86b7fff99dd5c0f0a.png

下面我们先熟悉一下源码,来个实例,来一个最精简启动SpringMVC。

最精简启动SpringMVC

建立Maven项目就不说了啊,先设置我们的pom文件

javax.servlet

javax.servlet-api

3.1.0

org.springframework

spring-webmvc

4.3.8.RELEASE

再来编写我们的Web.xml

spring mvc

dispatcherServlet

org.springframework.web.servlet.DispatcherServlet

contextConfigLocation

classpath:/spring-mvc.xml

dispatcherServlet

/

我们来简单些一个Controller

packagecom.springmvcbk.controller;importorg.springframework.web.servlet.ModelAndView;importorg.springframework.web.servlet.mvc.Controller;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;public class SpringmvcbkController implementsController {public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throwsException {

ModelAndView modelAndView= newModelAndView();

modelAndView.setViewName("/WEB-INF/page/index.jsp");

modelAndView.addObject("name","张三");returnmodelAndView;

}

}

写一个index.jsp页面吧。

Insert title here

${name}

最后还有我们的spring-mvc.xml

http://www.springframework.org/schema/beans/spring-beans.xsd

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context.xsd

http://www.springframework.org/schema/mvc

http://www.springframework.org/schema/mvc/spring-mvc.xsd">

注意自己的路径啊,走起,测试一下。

4106f508065252db3069f0c4afe3c462.png

这样我们最精简的SpringMVC就配置完成了。讲一下这段代码是如何执行的,上面图我们也看到了,请求过来优先去找我们的dispatchServlet,也就是我们Spring-MVC.xml配置文件,通过name属性来找的。找到我们对应的类,我们的继承我们的Controller接口来处理我们的请求,也就是图中的3,4,5步骤。然后再把结果塞回给dispatchServlet。返回页面,走起。

这个是我们表层的理解,后续我们逐渐会深入的,我们再来看另外一种实现方式。

packagecom.springmvcbk.controller;importorg.springframework.web.HttpRequestHandler;importjavax.servlet.ServletException;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;public class SpringmvcbkController2 implementsHttpRequestHandler {public void handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throwsServletException, IOException {

httpServletRequest.setAttribute("name","李斯");

httpServletRequest.getRequestDispatcher("/WEB-INF/page/index.jsp").forward(httpServletRequest,httpServletResponse);

}

}

33434c30587a46ae932c880cafb9a8b4.png

这种方式也是可以的。

整个过程是如何实现的?

1.dispatchServlet 如何找到对应的Control?

2.如何执行调用Control 当中的业务方法?

在面试中要回答好上述问题,就必须得弄清楚spring mvc 的体系组成。

spring mvc 的体系组成

只是举了几个例子的实现,SpringMVC还有很多的实现方法。我们来看一下内部都有什么核心的组件吧。

HandlerMapping->url与控制器的映谢

HandlerAdapter->控制器执行适配器

ViewResolver->视图仓库

view->具体解析视图

HandlerExceptionResolver->异常捕捕捉器

HandlerInterceptor->拦截器

稍后我们会逐个去说一下这些组件,我们看一下我们的UML类图吧,讲解一下他们之间是如果先后工作调用的。

193ce31ac1670caa56c81024d406723e.png 图没上色,也没写汉字注释,看着有点蒙圈....我来说一下咋回事。HTTPServlet发出请求,我们的DispatcherServlet拿到请求去匹配我们的HandlerMapping,经过HandlerMapping下的HandlerExecutionChain,HandlerInterceptor生成我们的Handl,返回给DispatcherServlet,拿到了Handl,给我们的Handl传递给HandlerAdapter进行处理,得到我们的View再有DispatcherServlet传递给ViewResolver,经由View处理,返回response请求。

57c167675c9ae4a1a4f2692040abc16a.png

我们先来看看我们的Handler是如何生产的。

Handler

7991b8cdabb93c6663ef9edd7676cb7f.png

这个是SpringMVC自己的继承UML图,最下层的两个是我们常用的,一个是通过name来注入的,一个是通过注解的方式来注入的,他是通过一系列的HandlerInterceptor才生成我们的Handler。

目前主流的三种mapping 如下

1. SimpleUrlHandlerMapping:基于手动配置url与control映谢

2.BeanNameUrlHandlerMapping:  基于ioc name 中已 "/" 开头的Bean时行 注册至映谢.

3.RequestMappingHandlerMapping:基于@RequestMapping注解配置对应映谢

另外两个不说了,太常见不过了。我们来尝试自己配置一个SimpleUrlHandlerMapping

http://www.springframework.org/schema/beans/spring-beans.xsd

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context.xsd

http://www.springframework.org/schema/mvc

http://www.springframework.org/schema/mvc/spring-mvc.xsd">

hello2

注意SimpleUrlHandlerMapping是没有/的,而我们的BeanNameUrlHandlerMapping必须加/的。

我们来走一下动态代码,只看取得Handler这段,(初始化的阶段可以自己研究一下)

1 /**

2 * Return the HandlerExecutionChain for this request.3 *

Tries all handler mappings in order.4 *@paramrequest current HTTP request5 *@returnthe HandlerExecutionChain, or {@codenull} if no handler could be found6 */

7 protected HandlerExecutionChain getHandler(HttpServletRequest request) throwsException {8 for (HandlerMapping hm : this.handlerMappings) {9 if(logger.isTraceEnabled()) {10 logger.trace(11 "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");12 }13 HandlerExecutionChain handler =hm.getHandler(request);14 if (handler != null) {15 returnhandler;16 }17 }18 return null;19 }

我们找到我们的DispatcherServlet类的getHandler方法上。在源码的1150行,也就是我上图的第7行。打个断点。优先遍历我们handlerMappings集合,找到以后去取我们的handler。

HandlerExecutionChain handler = hm.getHandler(request);方法就是获得我们的Handler方法,这里只是获得了一个HandlerExecutionChain执行链,也就是说我们在找到handler的前后都可能做其它的处理。再来深入一下看getHandler方法。

这时会调用AbstractHandlerMapping类的getHandler方法,然后优先去AbstractUrlHandlerMapping的getHandlerInternal取得handler

1 /**

2 * Look up a handler for the URL path of the given request.3 *@paramrequest current HTTP request4 *@returnthe handler instance, or {@codenull} if none found5 */

6 @Override7 protected Object getHandlerInternal(HttpServletRequest request) throwsException {8 String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);//取得路径

9 Object handler = lookupHandler(lookupPath, request);//拿着路径去LinkedHashMap查找是否存在

10 if (handler == null) {11 //We need to care for the default handler directly, since we need to12 //expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.

13 Object rawHandler = null;14 if ("/".equals(lookupPath)) {15 rawHandler =getRootHandler();16 }17 if (rawHandler == null) {18 rawHandler =getDefaultHandler();19 }20 if (rawHandler != null) {21 //Bean name or resolved handler?

22 if (rawHandler instanceofString) {23 String handlerName =(String) rawHandler;24 rawHandler =getApplicationContext().getBean(handlerName);25 }26 validateHandler(rawHandler, request);27 handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);28 }29 }30 if (handler != null &&logger.isDebugEnabled()) {31 logger.debug("Mapping [" + lookupPath + "] to " +handler);32 }33 else if (handler == null &&logger.isTraceEnabled()) {34 logger.trace("No handler mapping found for [" + lookupPath + "]");35 }36 returnhandler;37 }

得到request的路径,带着路径去我们已经初始化好的LinkedHashMap查看是否存在。

/*** Look up a handler instance for the given URL path.

*

Supports direct matches, e.g. a registered "/test" matches "/test",

* and various Ant-style pattern matches, e.g. a registered "/t*" matches

* both "/test" and "/team". For details, see the AntPathMatcher class.

*

Looks for the most exact pattern, where most exact is defined as

* the longest path pattern.

*@paramurlPath URL the bean is mapped to

*@paramrequest current HTTP request (to expose the path within the mapping to)

*@returnthe associated handler instance, or {@codenull} if not found

*@see#exposePathWithinMapping

*@seeorg.springframework.util.AntPathMatcher*/

protected Object lookupHandler(String urlPath, HttpServletRequest request) throwsException {//Direct match?

Object handler = this.handlerMap.get(urlPath);//拿着路径去LinkedHashMap查找是否存在

if (handler != null) {//Bean name or resolved handler?

if (handler instanceofString) {

String handlerName=(String) handler;

handler=getApplicationContext().getBean(handlerName);

}

validateHandler(handler, request);return buildPathExposingHandler(handler, urlPath, urlPath, null);

}//Pattern match?

List matchingPatterns = new ArrayList();for (String registeredPattern : this.handlerMap.keySet()) {if(getPathMatcher().match(registeredPattern, urlPath)) {

matchingPatterns.add(registeredPattern);

}else if(useTrailingSlashMatch()) {if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {

matchingPatterns.add(registeredPattern+"/");

}

}

}

String bestMatch= null;

Comparator patternComparator =getPathMatcher().getPatternComparator(urlPath);if (!matchingPatterns.isEmpty()) {

Collections.sort(matchingPatterns, patternComparator);if(logger.isDebugEnabled()) {

logger.debug("Matching patterns for request [" + urlPath + "] are " +matchingPatterns);

}

bestMatch= matchingPatterns.get(0);

}if (bestMatch != null) {

handler= this.handlerMap.get(bestMatch);if (handler == null) {if (bestMatch.endsWith("/")) {

handler= this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1));

}if (handler == null) {throw newIllegalStateException("Could not find handler for best pattern match [" + bestMatch + "]");

}

}//Bean name or resolved handler?

if (handler instanceofString) {

String handlerName=(String) handler;

handler=getApplicationContext().getBean(handlerName);

}

validateHandler(handler, request);

String pathWithinMapping=getPathMatcher().extractPathWithinPattern(bestMatch, urlPath);//There might be multiple 'best patterns', let's make sure we have the correct URI template variables//for all of them

Map uriTemplateVariables = new LinkedHashMap();for(String matchingPattern : matchingPatterns) {if (patternComparator.compare(bestMatch, matchingPattern) == 0) {

Map vars =getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);

Map decodedVars =getUrlPathHelper().decodePathVariables(request, vars);

uriTemplateVariables.putAll(decodedVars);

}

}if(logger.isDebugEnabled()) {

logger.debug("URI Template variables for request [" + urlPath + "] are " +uriTemplateVariables);

}returnbuildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);

}//No handler found...

return null;

}

到这里其实我们就可以得到我们的Handler了,但是SpringMVC又经过了buildPathExposingHandler处理,经过HandlerExecutionChain,看一下是否需要做请求前处理,然后得到我们的Handler。得到Handler以后也并没有急着返回,又经过了一次HandlerExecutionChain处理才返回的。

95c2830f8ea20297da9f7c5df57452aa.png

图中我们可以看到去和回来的时候都经过了HandlerExecutionChain处理的。就这样我们的handler就得到了。注意的三种mapping的方式可能有略微差异,但不影响大体流程。

HandlerAdapter

拿到我们的Handler,我们该查我们的HandlerAdapter了,也就是我们的适配器。我们回到我们的DispatchServlet类中

/*** Return the HandlerAdapter for this handler object.

*@paramhandler the handler object to find an adapter for

*@throwsServletException if no HandlerAdapter can be found for the handler. This is a fatal error.*/

protected HandlerAdapter getHandlerAdapter(Object handler) throwsServletException {for (HandlerAdapter ha : this.handlerAdapters) {if(logger.isTraceEnabled()) {

logger.trace("Testing handler adapter [" + ha + "]");

}if(ha.supports(handler)) {returnha;

}

}throw new ServletException("No adapter for handler [" + handler +

"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");

}

还是我们的循环调用,我们的适配器有四种,分别是AbstractHandlerMethodAdapter,HTTPRequestHandlerAdapter,SimpleControllerHandlerAdapter,SimpleServletHandlerAdapter

0c80c547322066eefa4dfba30a9a4855.png

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());方法开始处理我们的请求,返回ModelAndView。

返回以后,我们交给我们的ViewResolver来处理。

ViewResolver

db719c003b7abadafa27b05e8e75cecd.png

ContentNegotiatingViewResolver下面还有很多子类,我就不展示了。 选择对应的ViewResolver解析我们的ModelAndView得我到我们的view进行返回。

说到这一个请求的流程就算是大致结束了。我们来看两段核心的代码。

/*** Process the actual dispatching to the handler.

*

The handler will be obtained by applying the servlet's HandlerMappings in order.

* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters

* to find the first that supports the handler class.

*

All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers

* themselves to decide which methods are acceptable.

*@paramrequest current HTTP request

*@paramresponse current HTTP response

*@throwsException in case of any kind of processing failure*/

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throwsException {

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.

mappedHandler =getHandler(processedRequest);if (mappedHandler == null || mappedHandler.getHandler() == null) {

noHandlerFound(processedRequest, response);return;

}//Determine handler adapter for the current request.

HandlerAdapter ha =getHandlerAdapter(mappedHandler.getHandler());//Process last-modified header, if supported by the handler.

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;

}

}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);

}

}

}

}

这个是DispatchServlet类里面doDispatch方法,也就是我们请求来的时候进行解析的方法。

/*** Render the given ModelAndView.

*

This is the last stage in handling a request. It may involve resolving the view by name.

*@parammv the ModelAndView to render

*@paramrequest current HTTP servlet request

*@paramresponse current HTTP servlet response

*@throwsServletException if view is missing or cannot be resolved

*@throwsException if there's a problem rendering the view*/

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throwsException {//Determine locale for request and apply it to the response.

Locale locale = this.localeResolver.resolveLocale(request);

response.setLocale(locale);

View view;if(mv.isReference()) {//We need to resolve the view name.

view =resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);if (view == null) {throw new ServletException("Could not resolve view with name '" + mv.getViewName() +

"' in servlet with name '" + getServletName() + "'");

}

}else{//No need to lookup: the ModelAndView object contains the actual View object.

view =mv.getView();if (view == null) {throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +

"View object in servlet with name '" + getServletName() + "'");

}

}//Delegate to the View object for rendering.

if(logger.isDebugEnabled()) {

logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");

}try{if (mv.getStatus() != null) {

response.setStatus(mv.getStatus().value());

}

view.render(mv.getModelInternal(), request, response);

}catch(Exception ex) {if(logger.isDebugEnabled()) {

logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +getServletName()+ "'", ex);

}throwex;

}

}

这个是DispatchServlet类里面render方法,也就是我们处理完成要返回时的方法。大家有兴趣的可以逐行逐步的去走下流程。里面东西也不少的,这里就不一一讲解了。

2b99df70d7bf6f0b384c85405edf3cd0.png

d9c39a7c7db5895c14a4166dd0f7a376.png

最进弄了一个公众号,小菜技术,欢迎大家的加入

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值