SpringMVC工作原理

平常我们开发时可能都是直接搭建springboot项目,然后写controller就可以直接调用了,如下:

@Controller   // 或者restcontroller
public class UserController {
    // 正常使用@Autowired注入:
    @Autowired
    UserService userService;
    // 处理一个URL映射:
    @GetMapping("/")
    public ModelAndView index() {
        ...
    }
    ...
}

这篇文章就是要探讨,其内部实现的原理,我们要探讨一下三个问题:

  1. Controller注解为什么可以映射实现如@WebServlet的功能
  2. Spring是如何工作的
  3. SpringBoot是如何将spring和tomcat集成的

先说springMVC

一般我们都知道,MVC分别指Model、View、Controller,这里主要讲Controller的过程,ModelAndView限于篇幅就不展开讲了。
Spring的做法其实很简单,就是通过一个能接受所有请求的Servlet,然后内部通过一些配置,来实现Mapping,所以我们一般都能看到,配置spring从如下代码开始(web.xml):


    dispatcher
    org.springframework.web.servlet.DispatcherServlet



    dispatcher
    /*

上下文加载

但仅仅配置这些,依然不行,读者可以尝试运行一下,不出意外会报错:

 Could not open ServletContext resource [/WEB-INF/dispatcher-servlet.xml]

这是因为我们没有配置spring的上下文,学过springIOC都知道,spring核心就是容器装载bean然后依赖注入,但现在我们没有声明这个容器的位置,于是,spring会默认加载${servlet-name}-servlet.xml,这里我们是dispatcher-servlet.xml,但我们没有创建,所以会报错。解决方法就是去创建并声明一个上下文,配置方法有以下几种:

  1. 创建一个dispatcher-servlet.xml,并在这里注入bean

  2. 自己自定xml,也可以配置多个,如下:

    
        contextConfigLocation
        classpath:applicationContext.xml,;classpath:bean.xml
    
    
  3. 可以使用基于注解的java配置类,这个就像springIOC那样,有多种声明容器的方法,这里同样可以不使用默认的XmlWebApplicationContext,配置方法如下

    
        contextClass
        org.springframework.web.context.support.AnnotationConfigWebApplicationContext
    
    
        contextConfigLocation
        com.cq.AppConfig
    
    

    深入源码也可以看到DispatcherServlet继承自FrameworkServlet,而它有多个参数,我们上面定义的init-param元素,就是配置它的参数,而他包含一个contextClass属性:

    public static final Class DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
    /** WebApplicationContext implementation class to create. */
    private Class contextClass = DEFAULT_CONTEXT_CLASS;
    

上面的配置我们使用注解配置上下文的方式取代了原先xml的配置。

ContextLoaderListener

除了DispatcherServlet的上下文,我们另外还可以在它的上一级配置上下文,也就是通常说的主上下文或者根上下文,说白了,DispatcherServlet里的上下文只有在对应的Servlet中有效,而ContextLoaderListener中配置的上下文对于所有的Servlet都有效,也可以不配置:


 
   org.springframework.web.context.ContextLoaderListener
 


 contextConfigLocation
 /WEB-INF/spring/applicationContext.xml

DispatcherServlet处理请求

DispatcherServlet继承机构如下:


DispatcherServlet继承结构.PNG

图上可以看到, DispatcherServlet继承了HttpServlet,HttpServlet的实现就是所有的入口都是service(request,response)方法,内部实现各种doGet、doPost方法。
因此DispatcherServlet处理请求的过程也是一样,只不过覆盖了HttpServlet的一些方法如下:

   @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);  // 如果是patch和null的请求则走这里
       }
       else {
           super.service(request, response);   // 其他请求依然走httpServlet父类的方法
       }
   }

httpServlet的service()方法这里就不介绍了,主要是一些校验,最终会调用各种doXXX()方法,而FrameworkServlet中doXXX系列实现中,都调用了上面提到的processRequest方法:

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;

    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    LocaleContext localeContext = buildLocaleContext(request);

    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

    initContextHolders(request, localeContext, requestAttributes);

    try {
        doService(request, response);
    }
    catch (ServletException | IOException ex) {
        failureCause = ex;
        throw ex;
    }
    catch (Throwable ex) {
        failureCause = ex;
        throw new NestedServletException("Request processing failed", ex);
    }

    finally {
        resetContextHolders(request, previousLocaleContext, previousAttributes);
        if (requestAttributes != null) {
            requestAttributes.requestCompleted();
        }
        logResult(request, response, failureCause, asyncManager);
        publishRequestHandledEvent(request, response, startTime, failureCause);
    }
}

从代码中可以看到,这个函数的核心是调用doService方法,doService又去调用doDispatch方法,其之前和之后则是获取上下文参数以及解除上下文与线程的绑定,由于源码篇幅比较长,这里选取几行重要代码

mappedHandler = getHandler(processedRequest); // 获取url请求对应的执行器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// 获取适配器
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

以上过程就是从HandlerMapping里取出对应的handler,而handlerMapping就是我们配置的几个Controler中获取对应的进行处理,具体流程比较复杂,这里不展开讲了。

不使用web.xml

既然Servelet3.0已经升级为无需配置web.xml,那我们使用springmvc的方式肯定也跟着进步,怎么配置呢,只需要实现WebApplicationInitializer即可,WebApplicationInitializer这个类有多个子类,也是抽象类,我们可以选择其中一个比如AbstractAnnotationConfigDispatcherServletInitializer,也就是注解配置类

public class ServletTestWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
   @Override
   protected Class[] getRootConfigClasses() {
       return new Class[0];        // 根上下文
   }

   @Override
   protected Class[] getServletConfigClasses() {
       return new Class[]{AppConfig.class};        // servlet 上下文
   }

   @Override
   protected String[] getServletMappings() {
       return new String[]{"/*"};      // servlet上下文路径
   }
}

WebApplicationInitializer接口只有一个方法onStartup(ServletContext servletContext),会在容器启动的时候执行,原因是SpringServletContainerInitializer类实现了ServletContainerInitializer,这个是javax.servlet包中的类,也就是容器启动的时候会调用,该方法有两个参数,一个是Set,一个是容器上下文ServletContext。Set的值由类头部的注解 @HandlesTypes(WebApplicationInitializer.class)控制,函数里面会执行 WebApplicationInitializer的onStartup方法

ServletContainerInitializer类要求,继承这个接口并发挥作用必须在jar文件的 META-INF/services目录下声明一个文件,文件的名字是这个接口的完全限定类名称(这个类必须能被找到)。所以在META-INF可以找到这个声明

你可能感兴趣的:(SpringMVC工作原理)