栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

十万字解析SpringMVC使用

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

十万字解析SpringMVC使用

Java Web三层架构:表示层 业务层 持久层 利用HTTP协议完成浏览器与服务器之间的交互
下一步:考虑功能扩展问题,将模式和流程进行封装,提高代码的重用率

一、SpringMVC简介 1、什么是MVC

MVC是一种软件架构的思想,将软件按照模型、视图、控制器来划分

M:Model,模型层,指工程中的JavaBean,作用是处理数据

JavaBean分为两类:

  • 一类称为实体类Bean:专门存储业务数据的,如 Student、User 等
  • 一类称为业务处理 Bean:指 Service 或 Dao 对象,专门用于处理业务逻辑和数据访问。

V:View,视图层,指工程中的html或jsp等页面,作用是与用户进行交互,展示数据

C:Controller,控制层,指工程中的servlet,作用是接收请求和响应浏览器

MVC的工作流程:
用户通过视图层发送请求到服务器,在服务器中请求被Controller接收,Controller调用相应的Model层处理请求,处理完毕将结果返回到Controller,Controller再根据请求处理的结果找到相应的View视图,渲染数据后最终响应给浏览器

2、什么是SpringMVC

SpringMVC是Spring的一个后续产品,是Spring的一个子项目

SpringMVC 是 Spring 为表示层开发提供的一整套完备的解决方案。在表述层框架历经 Strust、WebWork、Strust2 等诸多产品的历代更迭之后,目前业界普遍选择了 SpringMVC 作为 Java EE 项目表述层开发的首选方案。

注:三层架构分为表述层(或表示层)、业务逻辑层、数据访问层,表述层表示前台页面和后台servlet

登录成功:重定向
登录失败:请求转发

3、SpringMVC的特点
  • Spring 家族原生产品,与 IOC 容器等基础设施无缝对接
  • 基于原生的Servlet,通过了功能强大的前端控制器DispatcherServlet,对请求和响应进行统一处理
  • 表述层各细分领域需要解决的问题全方位覆盖,提供全面解决方案
  • 代码清新简洁,大幅度提升开发效率
  • 内部组件化程度高,可插拔式组件即插即用,想要什么功能配置相应组件即可
  • 性能卓著,尤其适合现代大型、超大型互联网项目要求
二、HelloWorld 1、开发环境

IDE:idea 2019.2

构建工具:maven3.5.4

服务器:tomcat7

Spring版本:5.3.1 建议不要用 5之前的版本

2、创建maven工程(引入依赖)

maven有三种打包方式:
默认打包方式:jar
如果要将当前工程转换为web工程:war包 ,并且添加web模块

a>添加web模块 b>打包方式:war c>引入依赖

    
    
        org.springframework
        spring-webmvc
        5.3.1
    

    
    
        ch.qos.logback
        logback-classic
        1.2.3
    

   
    
    
        javax.servlet
        javax.servlet-api
        3.1.0
        provided 
    
  

    
    
    
        org.thymeleaf
        thymeleaf-spring5
        3.0.12.RELEASE
    

注:由于 Maven 的传递性,我们不必将所有需要的包全部配置依赖,而是配置最顶端的依赖,其他靠传递性导入。

3、配置web.xml(注册前端控制器)

注册SpringMVC的前端控制器DispatcherServlet

为什么要注册?因为浏览器不能访问到一个类,要访问需要设置匹配路径

a>默认配置方式

此配置作用下,SpringMVC的配置文件默认位于WEB-INF下,默认名称为-servlet.xml,例如,以下配置所对应SpringMVC的配置文件位于WEB-INF下,文件名为springMVC-servlet.xml


    springMVC
    org.springframework.web.servlet.DispatcherServlet


    springMVC
     
    /

  • /所匹配的请求可以是/login或.html或.js或.css方式的请求路径,不包括.jsp
  • ModelAndView mav = new ModelAndView(); //向请求域共享数据 mav.addObject("testScope", "hello,ModelAndView"); //设置视图,实现页面跳转 mav.setViewName("success"); return mav;//必须作为该方法的返回值返回 } 2.2、使用Model
    @RequestMapping("/testModel")
    public String testModel(Model model){
        model.addAttribute("testScope", "hello,Model");
        return "success";
    }
    
    2.3、使用map
    @RequestMapping("/testMap")
    public String testMap(Map map){
        map.put("testScope", "hello,Map");
        return "success";
    }
    
    2.4、使用ModelMap
    @RequestMapping("/testModelMap")
    public String testModelMap(ModelMap modelMap){
        modelMap.addAttribute("testScope", "hello,ModelMap");
        return "success";
    }
    
    3、Model、ModelMap、Map的关系

    toString:

    {testScope=hello,Model}
    {testScope=hello,Map}
    {testScope=hello,ModelMap}
    

    利用反射xxx.getClass().getName();
    发现是这三个类通过同一个类进行实例化

    Model、ModelMap、Map类型的参数其实本质上都是 BindingAwareModelMap 类型实例化的

    public interface Model{}
    public class ModelMap extends linkedHashMap {}
    public class ExtendedModelMap extends ModelMap implements Model {}
    public class BindingAwareModelMap extends ExtendedModelMap {}
    

    toString:
    {testScope=hello,Model}
    {testScope=hello,Map}
    {testScope=hello,ModelMap}

    不管用的是什么方式,最终都要把数据封装到ModelAndView中

    4、向session域共享数据

    建议使用Servlet原生API

    @RequestMapping("/testSession")
    public String testSession(HttpSession session){
        session.setAttribute("testSessionScope", "hello,session");
        return "success";
    }
    
    
    
    
        
        Title
    
    
    

    5、向application域共享数据(ServletContext)
    @RequestMapping("/testApplication")
    public String testApplication(HttpSession session){
    	ServletContext application = session.getServletContext();
        application.setAttribute("testApplicationScope", "hello,application");
        return "success";
    }
    
    
    
    
        
        Title
    
    
    

    六、SpringMVC的视图

    SpringMVC中的视图是View接口,视图的作用渲染数据,将模型Model中的数据展示给用户

    SpringMVC视图的种类很多,默认有转发视图和重定向视图

    • 当工程引入jstl的依赖,转发视图会自动转换为JstlView
    • 若使用的视图技术为Thymeleaf,在SpringMVC的配置文件中配置了Thymeleaf的视图解析器,由此视图解析器解析之后所得到的是ThymeleafView
    • 视频p44,源码这里没看太懂,有机会继续,orz笨蛋饼干终于会打断点了




    return视图的名字完全决定了视图的类型(查看View详细信息,则可看到View对应类型)

    1、ThymeleafView

    当控制器方法中所设置的视图名称没有任何前缀时,此时的视图名称会被SpringMVC配置文件中所配置的视图解析器SpringResourceResolver解析,视图名称拼接视图前缀和视图后缀所得到的最终路径,会通过转发的方式实现跳转

    @RequestMapping("/testHello")
    public String testHello(){
        return "hello";
    }
    
    2、转发视图

    SpringMVC中默认的转发视图是InternalResourceView,如果没有配置Thymeleaf默认使用此视图

    SpringMVC中创建转发视图的情况:

    当控制器方法中所设置的视图名称以"forward:"为前缀时,创建InternalResourceView视图,此时的视图名称不会被SpringMVC配置文件中所配置的视图解析器解析,而是会将前缀"forward:"去掉,剩余部分作为最终路径通过转发的方式实现跳转

    例如"forward:/",“forward***/employee”
    themeleaf本身就是通过转发访问的,所以基本不用

    @RequestMapping("/testForward")
    public String testForward(){
        return "forward:/testHello";
    }
    
    3、重定向视图

    SpringMVC中默认的重定向视图是RedirectView

    当控制器方法中所设置的视图名称以"redirect:"为前缀时,创建RedirectView视图,此时的视图名称不会被SpringMVC配置文件中所配置的视图解析器解析,而是会将前缀"redirect:"去掉,剩余部分作为最终路径通过重定向的方式实现跳转

    例如"redirect:/",“redirect***/employee”

    @RequestMapping("/testRedirect")
    public String testRedirect(){
        return "redirect:/testHello";
    }
    
    • 重定向不能访问WEB-INF下的内容,所以如果要访问,必须通过转发,重定向到的是请求,而不是一个具体的页面

    注:

    重定向视图在解析时,会先将redirect:前缀去掉,然后会判断剩余部分是否以/开头,若是则会自动拼接上下文路径

    转发和重定向区别:
    转发:

    1. 浏览器地址栏没有变化
    2. 浏览器发送一次请求
    3. 他们共享request域中的数据
    4. 可以转发到WEB-INF目录下,浏览器不能访问/WEB-INF/form.html
    5. 不可以访问工程以外的资源
      eg.不能跳到百度

    重定向:

    1. 浏览器地址栏会发生变化
    2. 浏览器发送两次请求
    3. 不共享Request域中的数据。两次请求,获得的是不同的Request对象
    4. 不能访问WEB-INF下的资源,WEB-INF浏览器无法直接访问
    5. 可以访问工程外的资源
    4、视图控制器view-controller

    当控制器方法中,没有其他请求过程的处理,仅仅用来实现页面跳转,即只需要设置视图名称时,可以将处理器方法使用view-controller标签进行表示

     @RequestMapping("/testView")
    public String index(){
            //返回视图名称,就会被配置的视图解析器进行解析(+前缀+后缀)就是最终要访问的页面
            return "success";
     }
    
    在springMVC.xml配置文件中添加
    
    
    

    注:

    当SpringMVC中设置任何一个view-controller时,其他控制器中的请求映射将全部失效,此时需要在SpringMVC的核心配置文件中设置开启mvc注解驱动的标签:

    p47 7:40 其他功能

    在ThemeleafViewResolver出现之前,如果视图使用的是jsp,SpringMVC如何解析jsp视图?
    如果什么都不加/使用forward:,采用InternalResoureViewResolver

        
            
            
        
    

    jsp在WEB-INF外,可以直接访问,所以不需要再设置访问首页的请求映射,
    Tomcat默认访问index.jsp,Tomcat的web.xml里有,作用于部署到Tomcat的所有工程有效
    当前工程的web.xml只针对当前工程有效

    
            index.html
            index.html
            index.jsp
        
    
     @RequestMapping("/success")
    public String index(){
            //返回视图名称,就会被配置的视图解析器进行解析(+前缀+后缀)就是最终要访问的页面
            return "success";
     }
    
    <%--jsp指令,用来设置当前页面中的一些信息--%>
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    
    
        Title
    
    
    <%--EL表达式,通过访问属性的方法访问--%>
      success.jsp
    
    
    
    

    forward:redirect:一般在前缀后缀不符合时使用

    七、RESTful

    1、RESTful简介

    是一种软件架构的风格
    REST:Representational State Transfer,表现层(前端视图页面+后端控制层)资源(工程部署到服务器,工程中的内容都叫资源,资源状态即资源的表现形式不同)状态转移(将服务器上资源发送给客户端)。

    a>资源

    资源是一种看待服务器的方式,即,将服务器看作是由很多离散的资源组成。每个资源是服务器上一个可命名的抽象概念。因为资源是一个抽象的概念,所以它不仅仅能代表服务器文件系统中的一个文件、数据库中的一张表等等具体的东西,可以将资源设计的要多抽象有多抽象,只要想象力允许而且客户端应用开发者能够理解。与面向对象设计类似,资源是以名词为核心来组织的,首先关注的是名词。一个资源可以由一个或多个URI来标识。URI既是资源的名称,也是资源在Web上的地址。对某个资源感兴趣的客户端应用,可以通过资源的URI与其进行交互。

    b>资源的表述

    资源的表述是一段对于资源在某个特定时刻的状态的描述。可以在客户端-服务器端之间转移(交换)。资源的表述可以有多种格式,例如HTML/XML/JSON/纯文本/图片/视频/音频等等。资源的表述格式可以通过协商机制来确定。请求-响应方向的表述通常使用不同的格式。

    c>状态转移

    状态转移说的是:在客户端和服务器端之间转移(transfer)代表资源状态的表述。通过转移和操作资源的表述,来间接实现操作资源的目的。

    相同的请求地址(当前要操作的资源),不同的请求方式(当前操作资源的方式)

    三、8.SpringMVC支持路径中的占位符:不以问号进行传参而用斜线拼接在请求地址中

    ​ 2、RESTful的实现

    具体说,就是 HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETe。

    它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE 用来删除资源。

    REST 风格提倡 URL 地址使用统一的风格设计,从前到后各个单词使用斜杠分开,不使用问号键值对方式携带请求参数,而是将要发送给服务器的数据作为 URL 地址的一部分,以保证整体风格的一致性。

    操作传统方式REST风格
    查询操作getUserById?id=1user/1–>get请求方式
    保存操作saveUseruser–>post请求方式
    删除操作deleteUser?id=1user/1–>delete请求方式
    更新操作updateUseruser–>put请求方式
    @RequestMapping(value="/user",method=RequestMethod.GET)
    public String getAllUser(){
        System.out.println("查询所有用户信息");
        return "success";
    }
    @RequestMapping(value="/user/{id}",method=RequestMethod.GET)
    public String getUserById(){
        System.out.println("根据id查询用户信息");
        return "success";
    }
    
    
    
    
    
    
        
        Title
    
    
      查询所有用户信息
      根据id查询用户信息
     
    
    
    
    3、HiddenHttpMethodFilter

    由于浏览器只支持发送get和post方式的请求(不是则默认按照get方法处理),那么该如何发送put和delete请求呢?

    SpringMVC 提供了 HiddenHttpMethodFilter 帮助我们将 POST 请求转换为 DELETE 或 PUT 请求

    HiddenHttpMethodFilter 处理put和delete请求的条件:

    a>当前请求的请求方式必须为post

    b>当前请求必须传输请求参数_method

    满足以上条件,HiddenHttpMethodFilter 过滤器就会将当前请求的请求方式转换为请求参数_method的值,因此请求参数_method的值才是最终的请求方式

    在web.xml中注册HiddenHttpMethodFilter
    有多个过滤器时,过滤器的执行顺序由filtermapping顺序决定,编码过滤器要写到最前,设置编码之前不能获取任何的请求参数,只要获取,设置编码就没有任何效果了

    
        HiddenHttpMethodFilter
        org.springframework.web.filter.HiddenHttpMethodFilter
    
    
        HiddenHttpMethodFilter
        
        @Override
        protected Class[] getRootConfigClasses() {
            return new Class[]{SpringConfig.class};
        }
    
        
        @Override
        protected Class[] getServletConfigClasses() {
            return new Class[]{WebConfig.class};
        }
    
        
        @Override
        protected String[] getServletMappings() {
            return new String[]{"/"};
        }
    
        
        @Override
        protected Filter[] getServletFilters() {
            CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
            encodingFilter.setEncoding("UTF-8");
            encodingFilter.setForceRequestEncoding(true);
            HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
            return new Filter[]{encodingFilter, hiddenHttpMethodFilter};
        }
    }
    
    2、创建SpringConfig配置类,代替spring的配置文件
    @Configuration
    public class SpringConfig {
    	//ssm整合之后,spring的配置信息写在此类中
    }
    
    3、创建WebConfig配置类,代替SpringMVC的配置文件
    1. 扫描组件
    2. 视图解析器
    3. view-controller
    4. default-servlet-headler静态资源处理
    5. MVC注解驱动
    6. 文件上传解析器
    7. 异常处理 bean/configureHandlerExceptionResolvers
    8. ]
    9. 拦截器
    @Configuration//将当前类标识为一个配置类
    
    @ComponentScan("com.atguigu.mvc.controller")//扫描组件
    
    @EnableWebMvc//开启MVC注解驱动
    //web工程初始化类,用来代替web.xml
    public class WebConfig implements WebMvcConfigurer {
    
        //使用默认的servlet处理静态资源  4.default-servlet-headler静态资源处理
        @Override
        public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
            configurer.enable();
        }
    
        //6.配置文件上传解析器
        @Bean
        public CommonsMultipartResolver multipartResolver(){
            return new CommonsMultipartResolver();
        }
    
        //8.配置拦截器
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            FirstInterceptor firstInterceptor = new FirstInterceptor();
            registry.addInterceptor(firstInterceptor).addPathPatterns("
        
        //配置异常映射
        
        //Bean从里到外创建
        //1.配置生成模板解析器
        @Bean
        public ITemplateResolver templateResolver() {
            //IOC容器在spring中的实现
            //1.Application---Java工程
            //2.WebApplication---Web工程
            WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
            // ServletContextTemplateResolver需要一个ServletContext作为构造参数,可通过WebApplicationContext 的方法获得
            ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(
                    webApplicationContext.getServletContext());
            templateResolver.setPrefix("/WEB-INF/templates/");
            templateResolver.setSuffix(".html");
            templateResolver.setCharacterEncoding("UTF-8");
            templateResolver.setTemplateMode(TemplateMode.HTML);
            return templateResolver;
        }
    
        //2.生成模板引擎并为模板引擎注入模板解析器,参数进行自动装配
        @Bean
        public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
            SpringTemplateEngine templateEngine = new SpringTemplateEngine();
            templateEngine.setTemplateResolver(templateResolver);
            return templateEngine;
        }
    
        //3.生成视图解析器并未解析器注入模板引擎
        @Bean
        public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
            ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
            viewResolver.setCharacterEncoding("UTF-8");
            viewResolver.setTemplateEngine(templateEngine);
            return viewResolver;
        }
    
    
    }
    
    4、测试功能
    @RequestMapping("/")
    public String index(){
        return "index";
    }
    
    十三、SpringMVC执行流程 1、SpringMVC常用组件
    • DispatcherServlet:前端控制器,不需要工程师开发,由框架提供

    作用:统一处理请求和响应,整个流程控制的中心,由它调用其它组件处理用户的请求

    • HandlerMapping:处理器映射器@RequestMapping,不需要工程师开发,由框架提供

    作用:根据请求的url、method等信息查找Handler,即控制器方法

    • Handler:处理器,即控制器@Control,需要工程师开发

    作用:在DispatcherServlet的控制下Handler对具体的用户请求进行处理

    • HandlerAdapter:处理器适配器,不需要工程师开发,由框架提供

    作用:通过HandlerAdapter对处理器(控制器方法)进行执行

    • ViewResolver:视图解析器,不需要工程师开发,由框架提供

    作用:进行视图解析,得到相应的视图,例如:ThymeleafView、InternalResourceView、RedirectView

    • View:视图

    作用:将模型数据通过页面展示给用户

    2、DispatcherServlet初始化过程

    DispatcherServlet 本质上是一个 Servlet,所以天然的遵循 Servlet 的生命周期。所以宏观上是 Servlet 生命周期来进行调度。

    a>初始化WebApplicationContext

    所在类:org.springframework.web.servlet.frameworkServlet

    protected WebApplicationContext initWebApplicationContext() {
        WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        WebApplicationContext wac = null;
    
        if (this.webApplicationContext != null) {
            // A context instance was injected at construction time -> use it
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                if (!cwac.isActive()) {
                    // The context has not yet been refreshed -> provide services such as
                    // setting the parent context, setting the application context id, etc
                    if (cwac.getParent() == null) {
                        // The context instance was injected without an explicit parent -> set
                        // the root application context (if any; may be null) as the parent
                        cwac.setParent(rootContext);
                    }
                    configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }
        if (wac == null) {
            // No context instance was injected at construction time -> see if one
            // has been registered in the servlet context. If one exists, it is assumed
            // that the parent context (if any) has already been set and that the
            // user has performed any initialization such as setting the context id
            wac = findWebApplicationContext();
        }
        if (wac == null) {
            // No context instance is defined for this servlet -> create a local one
            // 创建WebApplicationContext
            wac = createWebApplicationContext(rootContext);
        }
    
        if (!this.refreshEventReceived) {
            // Either the context is not a ConfigurableApplicationContext with refresh
            // support or the context injected at construction time had already been
            // refreshed -> trigger initial onRefresh manually here.
            synchronized (this.onRefreshMonitor) {
                // 刷新WebApplicationContext
                onRefresh(wac);
            }
        }
    
        if (this.publishContext) {
            // Publish the context as a servlet context attribute.
            // 将IOC容器在应用域共享
            String attrName = getServletContextAttributeName();
            getServletContext().setAttribute(attrName, wac);
        }
    
        return wac;
    }
    
    b>创建WebApplicationContext

    所在类:org.springframework.web.servlet.frameworkServlet

    protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
        Class contextClass = getContextClass();
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException(
                "Fatal initialization error in servlet with name '" + getServletName() +
                "': custom WebApplicationContext class [" + contextClass.getName() +
                "] is not of type ConfigurableWebApplicationContext");
        }
        // 通过反射创建 IOC 容器对象
        ConfigurableWebApplicationContext wac =
            (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    
        wac.setEnvironment(getEnvironment());
        // 设置父容器
        wac.setParent(parent);
        String configLocation = getContextConfigLocation();
        if (configLocation != null) {
            wac.setConfigLocation(configLocation);
        }
        configureAndRefreshWebApplicationContext(wac);
    
        return wac;
    }
    
    c>DispatcherServlet初始化策略

    frameworkServlet创建WebApplicationContext后,刷新容器,调用onRefresh(wac),此方法在DispatcherServlet中进行了重写,调用了initStrategies(context)方法,初始化策略,即初始化DispatcherServlet的各个组件

    所在类:org.springframework.web.servlet.DispatcherServlet

    protected void initStrategies(ApplicationContext context) {
       initMultipartResolver(context);
       initLocaleResolver(context);
       initThemeResolver(context);
       initHandlerMappings(context);
       initHandlerAdapters(context);
       initHandlerExceptionResolvers(context);
       initRequestToViewNameTranslator(context);
       initViewResolvers(context);
       initFlashMapManager(context);
    }
    
    3、DispatcherServlet调用组件处理请求 a>processRequest()

    frameworkServlet重写HttpServlet中的service()和doXxx(),这些方法中调用了processRequest(request, response)

    所在类:org.springframework.web.servlet.frameworkServlet

    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()是一个抽象方法,在DispatcherServlet中进行了重写
            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);
        }
    }
    
    b>doService()

    所在类:org.springframework.web.servlet.DispatcherServlet

    @Override
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        logRequest(request);
    
        // Keep a snapshot of the request attributes in case of an include,
        // to be able to restore the original attributes after the include.
        Map attributesSnapshot = null;
        if (WebUtils.isIncludeRequest(request)) {
            attributesSnapshot = new HashMap<>();
            Enumeration attrNames = request.getAttributeNames();
            while (attrNames.hasMoreElements()) {
                String attrName = (String) attrNames.nextElement();
                if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
                    attributesSnapshot.put(attrName, request.getAttribute(attrName));
                }
            }
        }
    
        // 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());
    
        if (this.flashMapManager != null) {
            FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
            if (inputFlashMap != null) {
                request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
            }
            request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
            request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
        }
    
        RequestPath requestPath = null;
        if (this.parseRequestPath && !ServletRequestPathUtils.hasParsedRequestPath(request)) {
            requestPath = ServletRequestPathUtils.parseAndCache(request);
        }
    
        try {
            // 处理请求和响应
            doDispatch(request, response);
        }
        finally {
            if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
                // Restore the original attribute snapshot, in case of an include.
                if (attributesSnapshot != null) {
                    restoreAttributesAfterInclude(request, attributesSnapshot);
                }
            }
            if (requestPath != null) {
                ServletRequestPathUtils.clearParsedRequestPath(request);
            }
        }
    }
    
    c>doDispatch()

    所在类:org.springframework.web.servlet.DispatcherServlet

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        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) {
                    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 (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }
    			
                // 调用拦截器的preHandle()
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }
    
                // Actually invoke the handler.
                // 由处理器适配器调用具体的控制器方法,最终获得ModelAndView对象
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
    
                applyDefaultViewName(processedRequest, mv);
                // 调用拦截器的postHandle()
                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);
                }
            }
        }
    }
    
    d>processDispatchResult()
    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
                                       @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
                                       @Nullable Exception exception) throws Exception {
    
        boolean errorView = false;
    
        if (exception != null) {
            if (exception instanceof ModelAndViewDefiningException) {
                logger.debug("ModelAndViewDefiningException encountered", exception);
                mv = ((ModelAndViewDefiningException) exception).getModelAndView();
            }
            else {
                Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
                mv = processHandlerException(request, response, handler, exception);
                errorView = (mv != null);
            }
        }
    
        // Did the handler return a view to render?
        if (mv != null && !mv.wasCleared()) {
            // 处理模型数据和渲染视图
            render(mv, request, response);
            if (errorView) {
                WebUtils.clearErrorRequestAttributes(request);
            }
        }
        else {
            if (logger.isTraceEnabled()) {
                logger.trace("No view rendering, null ModelAndView returned.");
            }
        }
    
        if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            // Concurrent handling started during a forward
            return;
        }
    
        if (mappedHandler != null) {
            // Exception (if any) is already handled..
            // 调用拦截器的afterCompletion()
            mappedHandler.triggerAfterCompletion(request, response, null);
        }
    }
    
    4、SpringMVC的执行流程
    1. 用户向服务器发送请求,请求被SpringMVC 前端控制器 DispatcherServlet捕获。
    2. DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI),判断请求URI对应的映射:

    a) 不存在

    i. 再判断是否配置了mvc:default-servlet-handler

    ii. 如果没配置,则控制台报映射查找不到,客户端展示404错误

    iii. 如果有配置,则访问目标资源(一般为静态资源,如:JS,CSS,HTML),找不到客户端也会展示404错误

    b) 存在则执行下面的流程

    1. 根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain执行链对象的形式返回。
    2. DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。
    3. 如果成功获得HandlerAdapter,此时将开始执行拦截器的preHandler(…)方法【正向】
    4. 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)方法,处理请求。在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:

    a) HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息

    b) 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等

    c) 数据格式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等

    d) 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中

    1. Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象。
    2. 此时将开始执行拦截器的postHandle(…)方法【逆向】。
    3. 根据返回的ModelAndView(此时会判断是否存在异常:如果存在异常,则执行HandlerExceptionResolver进行异常处理)选择一个适合的ViewResolver进行视图解析,根据Model和View,来渲染视图。
    4. 渲染视图完毕执行拦截器的afterCompletion(…)方法【逆向】。
    5. 将渲染结果返回给客户端。
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/630688.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号