美文网首页
springMVC源码分析.md

springMVC源码分析.md

作者: 游骑爬爱折腾 | 来源:发表于2019-10-31 15:27 被阅读0次

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

    • http请求,数据进servlet
    • servlet的输入是HttpServletRequest,里面封装了请求的上下文
    • servlet的输出是HttpServletResponse,输出的是文本数据或二进制数据。
      没错,servlet就是做了这些事情。

    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!");
          }
    
    • 我们要手动解析httprequest了
    • 我们要手动映射Model对象了
    • 我们要手动输出响应了,如果是JSON格式的还好,如果要返回HTML,这个怎么搞?
    • 如果要统一处理参数,这个相对要麻烦很多了
    • 如果要统一处理异常,这个也许会好一点,通过filter搞。
    • 没有springmvc的拦截器,我们只能通过filter去搞一些前置和后置工作了
    • 我们要手动维护url和handler之间的关系了

    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目录结构简介如下:

    • config: 和spring紧密结合的类,主要是支持各种Bean的解析工作,如XML配置和注解配置。
    • handler: 具体干活的,如URL路由,异常处理等。
    • i18n:国际化的,忽略
    • mvc:做各种忙前忙后的工作,这种工具,是通过spring-web:4.3.5.RELAESE来开展开的。
    • view: 支持不同的模板引擎,如freemarker,jsp,thymeleaf等

    这是干活的包,@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大组件有:

    • initMultipartResolver(context); 如果是上传的文件,在这里绑定文件到request里面
    • initLocaleResolver(context); 国际化相关的
    • initThemeResolver(context); 国际化相关的
    • initHandlerMappings(context); springMVC要面向不同的URL,handleringMapping是用于路由的,通过 url来查找对应的控制器。每个handler其实就是控制器下面的方法。即标注为@RequestMapping的东西。初始化的时候,肯定会用通过@RequestMapping来生成一个维护URL和method的Map结构的。
    • initHandlerAdapters(context); 从名字上看,它就是一个适配器。因为SpringMVC中的Handler可以是任意的形式,只要能处理请求就ok,但是Servlet需要的处理方法的结构却是固定的,都是以request和response为参数的方法。如何让固定的Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapter要做的事情。 小结:Handler是用来干活的工具;HandlerMapping用于根据需要干的活找到相应的工具;HandlerAdapter是使用工具干活的人。
    • initHandlerExceptionResolvers(context); 如果出现了异常怎么办?HandlerExceptionResolvers来帮忙,想想咱们以前用过的@ControllerAdvice,@RestControllerAdvice,在初始化的时候,会根据这2个注解,来维护内部的异常处理解析器关系的。
    • initRequestToViewNameTranslator(context); handler返回的是一个字符串,如何从字符串找到对应的页面模板文件,RequestToViewNameTranslator就是干这种活的。
    • initViewResolvers(context); ViewResolver将页面模板文件,用Model里面的数据结合,渲染输出页面。
    • initFlashMapManager(context); 用来管理FlashMap的,FlashMap主要用在redirect中传递参数。

    如果没有外部指定,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()的处理逻辑如下:

    • 看是不是有文件上传进来了
    • 通过request,来查表,找到对应干活的handler.
    • 拿到handler之后,来获取HandlerAdapter,由HandlerAdapter由干具体的活,这时候,handlerAdapter还没有干活哦
    • 执行拦截器的前置拦截,还记得preHander么,在这里拦,如果被拦截了,就直接返回了。
    • HandlerAdapter要执行主体业务逻辑了
    • 执行拦截器的后置拦截,还记得postHander么
    • 检查是否有异常,如果有异常,则通过异常解析器,进行异常处理,还记录@ControllerAdvice,@RestControllerAdvice么
    • 如果没有异常,那么,如果返回HTML页面,则渲染视图;如果返回josn数据,则序列化返回结果。

    主业务流程如下图所示。


    主业务流程.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 总结

    • springMVC在初始化的时候,和beanFactory进行了很多的交互,通过beanFactory提供的各种便利,整合各种handler.

    • http请求处理的各种阶段,在每个阶段,都有大量的责任链应用,这应该会继续给我们一些启示。

    7 问题

    • 用户的HTTP请求到达SpringMVC后,发生了什么事情?
    • @RequestMapping,@Controller,@ResponseBody,@RequestParam,@Valid等等,这些东西是如何工作的?
    • 返回的对象,是如何转化为josn格式的
    • url是如何关联到controller的?
    • 返回的路径,是如何转换为HTML的?
    • url后面的parameter,是如何自动转化为对象的?
    • Servlet是输入是url和parameter,输出是字节流,为什么springmvc走了这么多道流程
    • springMVC的9大组件是什么?
    • Filter是如何集成的
    • HandlerInterceptor和Filter的区别? https://www.cnblogs.com/junzi2099/p/8022058.html
    • @RestContoller,@ResonponseBody视图是怎么渲染的呢?

    相关文章

      网友评论

          本文标题:springMVC源码分析.md

          本文链接:https://www.haomeiwen.com/subject/iidtbctx.html