springMVC源码分析.md

2019-10-31  本文已影响0人  游骑爬爱折腾

1 http请求过来了,经过springmvc发生了哪些事情?

2 如果没有springmvc,我们应该如何手写代码呢?

看看原始的Servlet编程模型:

public class HelloServlet extends HttpServlet {

 @Override
 protected void service(HttpServletRequest req, HttpServletResponse res)
   throws ServletException, IOException {
   res.getWriter().println("Hello World!");
 }

  public void doGet(HttpServletRequest request,
             HttpServletResponse response)
     throws ServletException, IOException{
        res.getWriter().println("Hello World!");
     }
     
   public void doPost(HttpServletRequest request,
              HttpServletResponse response)
      throws ServletException, IOException{
         res.getWriter().println("Hello World!");
      }

3 有了springmvc,我们将有什么收获呢?

3.1 自动帮你填充HttpServletRequest参数

/**
* springMvc会自动给request赋值
* 如:
  POST /login?mobile=1234567890&passowrd=123456
  自动把url里面的全部上下文都入到request对象中
**/
public String demo(HttpServletRequest request){
    String mobile = request.getParameter("mobile");
    String password = request.getParameter("password");
    
    return "demo";
}

3.2 自动帮你填充普通变量

简单变量,可以自动赋值,省去从request里面取参数。

如请求:
POST /user?id=12&name=heha
springMVC自动把url查询参数绑定id绑定到控制器的demo方法id入参上,自动把url里的查询参数name绑定到demo方法的入参name上。

public String demo(long id,String name){}

3.3 自动帮你填充对象

如下所示,springmvc会自动将User对象赋值,如果User是一个复杂的表单对象,想想能省多少事。

如请求 POST /user?id=12&name=hhhaaa,springMVC自动把请求的路径参数绑定到demo方法的入参user对象中。

public String demo(User user){}

3.4 自动帮你转换响应

如果是返回的是对象,而且不是ModelAndView对象,则SpringMVC推断返回的是json数据,会自动转换为json数据。

Map autoConvertMaptoJson(HttpServletRequest request){}

User autoConvertObjectToJson(HttpServletRequest request){}

如果返回的是String,那么,SpringMVC会推断是不是返回页面,如果是返回页面,那么springMVC会先找到路径,然后根据返回值拼接模板文件。根据模板文件的类型,找到视图解析器,
通过调用解析器,将模板文件渲染,输出到response的writer里面。
如:

String index(){
    return "index";
}

3.5 自动异常处理

通过@ControllerAdvice,@RestControllerAdvice注解,可以处理全局的异常。

如果不需要返回json数据,而是返回页面,可以这么搞。

@ExceptionHandler(value = MyException.class)
public ModelAndView myErrorHandlerOfHtml(MyException ex) {
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.setViewName("error");
    modelAndView.addObject("code", ex.getCode());
    modelAndView.addObject("msg", ex.getMsg());
    modelAndView.addObject("extra",ex.getExtra());
    return modelAndView;
}

如果要返回json数据,则更简单了。

@ExceptionHandler(value = MyException.class)
public Map myErrorHandlerOfJson(MyException ex) {
    Map result = new HashMap();
    result.put("code", ex.getCode());
    result.put("msg", ex.getMsg());
    result.put("extra",ex.getExtra());
    return result;
}

3.6 丰富的拦截器

拦截器有白名单和黑名单配置,通过黑白名单机制,灵活路由。

3.7 自定义转换参数

参考前面的参数绑定,可以实现更复杂的参数绑定功能,将一些不存在的参数自动绑定到handler方法的入参中。

3.8 支持多种视图

json,jsp,FreeMarker,thymeleaf等,统统支持。

4 那么,springMvc是如何具备这种能力的呢?

springMVC通过整合9大组件,来实现对外提供url路由,参数绑定,业务处理前后责任链式打点拦截,业务处理,结果返回,异常结果处理,视图渲染等工作。

4.1 springMVC源码目录结构简介

以spring-webmvc-4.3.5.RELEASE为例,反编译如下。


source.png

spring-webmvc是springMVC的一部分,还有大量框架代码在spring-web里面实现。
spring-webmvc目录结构简介如下:

这是干活的包,@Controller和@RestController就是在这里调用的。


handler.png

这个包里的业务逻辑,好多是调用spring-web:4.3.5.RELEASE实现的。


mvc.png
这是view包,用于渲染和种模板引擎用的。
view.png

DispatchServlet是核心调度类,所有的请求,都会类DispatchServlet类处理。
DispatchServlet有2大功能:
- 初始化
- 处理http请求

4.2 springMVC初始化

springMVC环境初始化时,会初始化9大组件。


初始化DispatchServlet.png

容器初始时,会调用OnRefresh()接口,在OnRefresh()里面,执行初始化。初始化的9大组件有:

如果没有外部指定,springMVC会执行默认初始化测试,读取同目录下的DispatcherServet.properties文件,完成初始化工作。

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
    org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
    org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
    org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
    org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
    org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

初始化9大组件的过程,非常复杂,也非常精彩,里面用到了spring bean非常多的功能,有兴趣的可以详细看看。

4.3 处理HTTP请求

springMVC环境初始化完成后,可以接受外界请求了,这时候,HPPT请求实际上请求是通过Servlet进来的。然后通过模板方法的模式,在DispatcherServlet的doDispatch()方法中,执行实际的处理逻辑。

doDispatch()的处理逻辑如下:

主业务流程如下图所示。


主业务流程.png

网上有图,盗图一张。

4.3.1 HandlerMapping

url和拦截器的绑定关系,在初始化阶段完成了,所以呢?初始化很慢的。debug的时候就能发现,debug太慢了。 拦截器只认request,不认各种解析后的参数。

url路由.png

4.3.2 HandlerAdapter

HandlerAdapter处理主体业务逻辑,这里面内容太多了。HandlerAdapter简直是个大管家。它要:

- handler干活前,需要将干活的参数准备好,将url里面的东西,转化为Controller的方法里面的入参。

- handler干活前,需要执行前置的工作,如@ModelAttribute注解的东西,将数据放model里面。

- handler干完活后,还需要执行后置工作,对返回值进行处理。

从HandlerAdapter的初始化就能看到这些。如下图所示。

requestMapping初始化.png

我们在浏览器中,输入URL看看

http://localhost:8080/rest/demo?user=jpnie&age=18

在这里,我们看看参数是如何绑定的。

4.3.2.1 拿handlerAdapter, 找项目经理

handlerAdapter是项目经理,负责各路资源调度。handler是民工,只负责根据method的参数,执行业务逻辑,然后再返给handlerAdapter去整合。

拿handlerAdapter.png

4.3.2.2 输入参数绑定

invokeForRequest()里面,会拿httprequest,去获取方法参数,即getMethodArgumetnValues(),将request里面的参数中,提取出方法参数,如前面所述的简单变量,复杂对象等。

参数绑定.png
参数绑定2.png
参数绑定成功.png

4.3.2.3 返回值处理

controller的method方法,返回的一般是String,Map或者是PO对象,也可能是ModelAndView.HandlerAdapter通过handler拿到返回值的,可以对返回值进行加工处理。如下图所示。

返回值处理链路.png

在这里,对返回值进行链式调用处理。责任链模板经典应用。

返回值处理链路2.png

返回值的类型实在是太多了,所以有很多的returnValueHandlers,根据返回值的类型,来处理相应返回值。


返回值处理链路3.png

如果是返回json之类的,则可以直接往response里面写响应数据了。写响应数据的时候,会调用不同的MessageConvert。如下图所示。


返回值处理链路4.png

这时候,总体流程基本上是结束了。如下图所示。


基本结束.png

4.4 异常处理

异常处理,是非常有用的功能,异常处理对后台服务至关重要。

我们来个异常DEMO看看。

handler的源码如下:

    @RequestMapping("/rest/exception")
    public String restExceptionDemo() throws Exception {
        throw new Exception("exception demo");
    }

自定义异常处理器的源码如下:

@RestControllerAdvice
public class ExceptionHandlerDemo {
    @ResponseBody
    @ExceptionHandler
    public Map<String,String> handlerException(Exception ex){
        Map map = new HashMap();
        map.put("code", -200);
        map.put("msg", ex.getMessage());
        return map;
    }
}

在浏览器中输入:

http://localhost:8080/rest/exception

会进行异常处理流程。
restExceptionDemo()返回的异常,会传入到异常处理流程中。如果异常不为null,则在processHandlerException()方法中处理异常。如下图所示。


异常处理.png

异常处理器有多个,所以需要进行异常处理器路由。


异常处理2.png
异常处理器由成功后,执行咱们自定义的逻辑。
异常处理3.png

4.5 页面HTTP响应是如何处理的呢

页面响应,总体流程和json类型差不多,不同在于,页面类型,有很多模板引擎,需要通过model去结合模板,通过模板引擎进行渲染。

看个例子,源码如下:

    @RequestMapping("/index")
    public String index(HttpServletRequest request, HttpServletResponse response,Model model){
        model.addAttribute("hello","SPRING MVC DEMO ");
        return "index";
    }

在浏览器中输入:

http://localhost:8080/index

进入断点,看看springmvc的执行逻辑。

收到http请求后,执行Servlet的doService()接口服务。


收到http请求.png

看,看来了咱们的网页应用业务逻辑。


网站应用.png

咱们的网页应用返回的是html类型,通过"index"去找模板文件.


找视图.png 去找视图文件啦.png

拿到模板文件"index.html",内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<p>hello,spring mvc ${hello}</p>
</body>
</html>

找到视图文件后,拿model去填充index.html,渲染输出。

渲染视图.png

5 思维导图

springMVC注解

SpringMVC.png

DispatcherServlet分解

SpringMVC (1).png

6 总结

7 问题

上一篇下一篇

猜你喜欢

热点阅读