SpringMVC是怎么工做的,SpringMVC的工做原理

SpringWeb MVC 是怎么工做的,SpringMVC的原理,SpringMVC源码 分析。java

介绍

SpringWeb MVC是Spring Framework中的一部分,当咱们须要使用spring框架建立web应用的时候就须要引入springweb mvc。对于程序员来讲,咱们只须要增长@Controller ,@RequestMapping注解而后,浏览器中的请求就会到达springweb应用。咱们只须要在 controller中编写相关逻辑便可。然而,请求是在哪里接收的?@Controller ,@RequestMapping注解作了什么?本文咱们来探讨一下。程序员

从一个项目开始

本文假定你已经能熟练的使用springmvc。为了展开后续的讨论,假设咱们新建了一个spring-mvc-demo的项目。并由此项目来展开讨论。项目中有一个控制器,代码以下:web

@Controller
@RequestMapping("/app")
public class AppController {
  @RequestMapping(method=RequestMethod.GET,value="/hello")
    public @ResponseBody String hello(HttpServletRequest request,String name) {
    return "Hello,"+name;
  }
}

控制器写好以后,咱们将项目打车war包,放入tomcat容器中,并使用8080端口启动tomcat,运行项目,而后在浏览器中输入http://localhost:8080/app/hello?name=world.spring

咱们在浏览器中能够看到:Hello,world的输出。编程

咱们先记住这个例子,下面咱们带着一些疑问继续看,这个请求是怎么被接收到的?请求是怎么交给AppController处理的?api

Servlet是Java Web应用的基石

当你在浏览器中输入 http://loalhost:8080/ ,按下enter建,而后请求命中了服务器,这是怎么发生的?又如何从这个请求中获得浏览器中可见的页面?
本例中,咱们给出的是一个简单的spring-mvc应用,并放入了tomcat中(springboot 内嵌tomcat启动其实也是同样的)。 Tomcat 是一个servlet容器,这点我想每一个Java程序员都十分清楚,咱们在没有使用spring-mvc以前,就是使用servlet+jsp来开发web应用。浏览器

因为Tomcat是一个web容器,每个发送给Tomcat服务器的HTTP请求天然会被一个Java Servlet处理。因此,SpringMvc 一定有一个servlet,SpringWeb应用的入口一定是一个Servlet,这应该不难想到。spring-mvc

简单来讲,Servlet是任何Java Web应用的核心组件(除非你不用servlet规范,好比你使用netty)。Servlet它是低层次的,而且不会像MVC那样强加于特定的编程模式。它只是可让你写一个偶一个Servlet,一个HTTP Servlet能够接受一个HTTP请求,而后处理它,并返回一个响应。tomcat

而springmvc 就是使用了一个大的servlet,下面咱们就来讲这个大的servlet。

DispatcherServlet是Spring MVC的核心

上面咱们已经提到Servlet 是Java web应用的基石Spring应用入口一定是一个Servlet,这个Servlet 其实就是DispatcherServlet

做为WEB应用的开发人员,咱们真正想作的是抽象出如下繁琐和模板化的任务,并专一于有用的业务逻辑:

  • 映射一个HTTP请求到某个处理方法。

  • 将HTTP请求数据,和头信息转换成数据对象(DTO / domain object)。

  • 模型 - 视图 - 控制器 之间的交互。

  • 从DTO,域对象等生成响应

Spring DispatcherServlet提供了这些。它是Spring Web MVC框架的核心, 这个核心组件接收全部请求到您的应用程序。
DispatcherServlet具备很强的可扩展性。 例如,它容许您插入不一样的现有或新适配器以执行大量任务:

  • 将请求映射到应该处理它的类或方法(HandlerMapping接口的实现)
  • 使用特定模式处理请求,例如常规servlet,更复杂的MVC工做流或者POJO bean中的方法(HandlerAdapter接口的实现)
  • 经过名称解析视图,容许您使用不一样的模板引擎,XML,XSLT或任何其余视图技术(ViewResolver接口的实现)
  • 经过使用默认的Apache Commons文件上传实现或编写本身的MultipartResolver来解析multipart请求
  • 使用任何LocaleResolver实现解决语言环境,包括Cookie,会话,Accept HTTP标头或用于肯定用户指望的语言环境的任何其余方式

处理HTTP请求

首先,让咱们来追踪一个简单的HTTP请求到达controller中的方法,而后返回到 浏览器/客户端的处理过程。
DispatcherServlet 有一个很长的继承关系。它的继承关系是这样的:

GenericServlet <- HttpServlet <- HttpServletBean <- FreamworkServlet <- DispatcherServlet

GenericServlet

GenericServlet是Servlet规范的一部分,它并不直接关注HTTP。它定义了一个service()方法用来接收传递过来的请求,并产生响应。(这里的请求和响应不是指HTTP请求)

public abstract void service(ServletRequest req, ServletResponse res) 

  throws ServletException, IOException;

注意,这里的参数中的ServletRequest,ServletResponse并非和HTTP协议绑定的,Http有具体协议Servlet。

HttpServlet

顾名思义,HttpServlet类就是规范中定义的基于HTTP的Servlet实现。

更实际的说,HttpServlet是一个具备service()方法实现的抽象类,它经过HTTP方法类型分割请求,大体以下所示:

protected void service(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {

    String method = req.getMethod();
    if (method.equals(METHOD_GET)) {
        // ...
        doGet(req, resp);
    } else if (method.equals(METHOD_HEAD)) {
        // ...
        doHead(req, resp);
    } else if (method.equals(METHOD_POST)) {
        doPost(req, resp);
        // ...
    }

根据请求的不一样, get,post方法会分别被不一样方法处理。

HttpServletBean

上面咱们展现过 DispatcherServlet的继承关系,在这个继承链中,HttpServletBean是进入spring的第一个层次。从HttpServletBean开始往下的几个servlet都是spring中的类。HttpServletBean 就是一个servlet,它继承自HttpServlet,就像是咱们在使用servlet+jsp开发时候定义的servlet同样。

根据servlet的生命周期咱们知道,servlet会被容器初始化,初始化时候,其init()方法会被调用。在springmvc框架中 HttpServletBean使用从web.xml或WebApplicationInitializer收到的servlet init-param值来注入bean的属性。
在请求应用程序的状况下,为这些特定的HTTP请求调用doGet(),doPost()等方法。

FrameworkServlet

FrameworkServlet将Servlet功能与Web应用程序上下文集成,实现ApplicationContextAware接口。但它也可以自行建立Web应用程序上下文。
正如上面所说,FrameworkServlet的超类HttpServletBean将init-param注入为bean属性。因此,若是servlet的contextClass init-param中提供了context类的名字,那么这个context类的实例将会被建立,用做应用的context。不然,将会使用XmlWebApplicationContext做为默认的。

DispatcherServlet: 统一请求处理

有了上面铺垫,咱们这里能够开始关键的内容,即DispatcherServlet统一请求处理。在Springmvc的项目中,咱们一般会在web.xml配置一个servlet,以下:

<servlet>
        <servlet-name>spring</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring/spring-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

上面咱们提到,DispatcherServlet继承关系,其父类中正儿八经的servlet类是HttpServletBean这个servlet类是应用启动入口。其生命周期的第一阶段init()方法完成初始化工做。

doService()方法设置请求信息

DispatcherServlet 初始化以后,即可以工做了。当请求到达之时,会调用其doService()方法。doService()方法的代码以下:

@Override
    protected void doService(HttpServletRequest request, HttpServletResponse response) 
     throws Exception {
    // 删除一下‘非核心代码’方便查看
        // Make framework objects available to handlers and view objects.
        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
        request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());      
    try {
            doDispatch(request, response);
        }
        finally {
            if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
                return;
            }
            // Restore the original attribute snapshot, in case of an include.
            if (attributesSnapshot != null) {
                restoreAttributesAfterInclude(request, attributesSnapshot);
            }
        }

  }

能够看到,doService()方法先设置一些 request信息,这些信息在后续的处理过程当中会用到。设置完后,它调用doDispatch() 方法。

doDispatch()方法分发请求

doService()方法最终调用了doDispatch(),看名知意,这个方法是作分发工做的。其代码以下:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) 
       throws Exception {
        //删除一些代码方便阅读
        HandlerExecutionChain mappedHandler = null;
        try {
            ModelAndView mv = null;
            Exception dispatchException = null;

            try {
                // 删除一些代码方便阅读
              mappedHandler = getHandler(processedRequest, false);
              HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
                try {
                    // Actually invoke the handler.
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                }
                finally {
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
                }
                applyDefaultViewName(request, mv);
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;  // 这里捕获了异常TypeMismatchException
            }
            processDispatchResult(processedRequest, response, mappedHandler, mv,                                                                            dispatchException);
        }
        catch (Exception ex) {
        }
        finally {
            // 删除一些代码
        }
}

这个方法主要做用就是找到合适的 handler 来处理请求。handler一般是一个某个类型的对象,而且不限定特定的接口。所以spring须要找到这个handler的适配器。这个Handler一般是一个HandlerMethod实例,

为了找到与请求匹配handler,spring须要从已注册的HandlerMapping接口实现类里边去找。这个查找过程就是在上面的getHandler() 方法完成获得的是一个HandlerExecutionChain。 这里体现了责任链模式

这个getHandler() 会遍历一个HandlerMapping的map。因为咱们通常都使用注解形式:@Controller,@RequestMapping注解。所以这里找到HandlerMapping实现就是RequestMappingHandlerMapping

HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

getHandlerAdapter()方法找到最终的handler适配器,找到的适配器就是RequestMappingHandlerAdapter,(由于咱们使用的是@RequestMapping注解形式)。

本例中,咱们定义了AppController 的hello()方法,并用@Controller,@RequestMapping对其分别进行注解,所以这里获得的适配器HandlerAdapter 所适配HandlerMethod就是 AppController 的hello()方法的 。

HandlerAdapter处理请求

上面经过 肯定了HandlerAdapter以后,就要执行handle() 方法了,即上面代码中,try语句块里边的ha.handle()。handle()方法定义为:

ModelAndView handle(HttpServletRequest request, 
                     HttpServletResponse response, 
                     Object handler) throws Exception;

这个方法有两种处理方式:

  • 本身将数据写到response,而后return null

  • 返回一个ModelAndView 对象,让DispatcherServlet本身来处理后面渲染工做。

HandlerAdapter有多重类型,例如

SimpleControllerHandlerAdapter处理spring mvc 的controller实例(注意,不要把这里的controller实例和@Controller注解POJO混淆,这里controller 指的是org.springframework.web.servlet.mvc.Controller ),并返回ModelAndView,代码以下:

public ModelAndView handle(HttpServletRequest request, 
  HttpServletResponse response, Object handler) throws Exception {
    return ((Controller) handler).handleRequest(request, response);
}

SimpleServletHandlerAdapter 适配的是 Servlet做为request handler的状况,Servlet是不知道MovelAndView的,因此,它的方法并不负责渲染页面,所以没有返回ModelAndView,只是返回null:

public ModelAndView handle(HttpServletRequest request, 
  HttpServletResponse response, Object handler) throws Exception {
    ((Servlet) handler).service(request, response);
    return null;
}

RequestMappingHandlerAdapter 就是咱们上面提到的,用来处理@Controller和@RequestMapping注解的handler。

渲染视图

handle()方法调用以后, DispatcherServlet 能够获得一个ModelAndView,固然也多是null。对于ModelAndView不为null的时候,DispatcherServlet 将会调用render()方法。ModelAndView中可能已经包含了一个view或者只是一个view的名字。若是controller方法指定的是一个字符串形式的视图名字,那么就须要进行试图查找:

for (ViewResolver viewResolver : this.viewResolvers) {
    View view = viewResolver.resolveViewName(viewName, locale);
    if (view != null) {
        return view;
    }
}

render()方法完成以后,最终的HTML页面会被发送至浏览器端。

固然,springmvc不只能渲染出页面,也能够返回JSON形式或者XML形式。这种状况controller方法通常都是由@RequestBody标注的。这种状况就须要 HttpMessageConverter,例如渲染JSON的时候可使用Jackson包,咱们要返回的对象将由,MappingJackson2HttpMessageConverter来转换。


到此,咱们就大概说完了springmvc的整个流程。因此,springmvc其实就是一个大的Servlet,接收请求,分发执行请求,咱们的每个controller中的方法都是一个handler,而后最终渲染视图。

spring源码分析400异常处理流程及解决方法
spring如何启动的?这里结合spring源码描述了启动过程

相关文章
相关标签/搜索