20201205 Spring MVC

Spring MVC 应用

三层架构 和 MVC 设计模式

img

MVC 对应于三层架构中的 表现层

三层架构:

  • 表现层
  • 业务层
  • 持久层

MVC 设计模式:

  • Model(模型)
  • View(视图)
  • Controller(控制器)

Spring MVC 介绍

Spring MVC 本质可以认为是对 Serlvet 的封装,简化了我们 Serlvet 的开发

Spring MVC 请求处理流程 :

img

开发过程:

  1. 配置 DispatcherServlet 前端控制器
  2. 开发处理具体业务逻辑的 Handler(@Controller@RequestMapping
  3. XML 配置文件配置 Controller 扫描,配置 Spring MVC 三⼤件
  4. 将 XML ⽂件路径告诉 Spring MVC (DispatcherServlet

Spring MVC 请求处理流程 :

img

  1. 用户发送请求至前端控制器 DispatcherServlet
  2. DispatcherServlet 收到请求调用 HandlerMapping 处理器映射器
  3. 处理器映射器根据请求 Url 找到具体的 Handler(后端控制器),生成处理器对象及处理器拦截器(如果 有则生成)⼀并返回 DispatcherServlet
  4. DispatcherServlet 调用 HandlerAdapter 处理器适配器去调⽤ Handler
  5. 处理器适配器执⾏ Handler
  6. Handler 执⾏完成给处理器适配器返回 ModelAndView
  7. 处理器适配器向前端控制器返回 ModelAndViewModelAndView 是 SpringMVC 框架的⼀个底层对象,包括 ModelView
  8. 前端控制器请求视图解析器去进行视图解析,根据逻辑视图名来解析真正的视图。
  9. 视图解析器向前端控制器返回 View
  10. 前端控制器进行视图渲染,就是将模型数据(在 ModelAndView 对象中)填充到 request 域前端控制器向用户响应结果

Spring MVC 九⼤组件

定义和初始化方法:org.springframework.web.servlet.DispatcherServlet#initStrategies

  • HandlerMapping(处理器映射器)
  • HandlerAdapter(处理器适配器)
  • HandlerExceptionResolver(处理器异常解析器)
  • ViewResolver(视图解析器)
  • RequestToViewNameTranslator(请求到视图名称翻译器)
  • LocaleResolver(国际化解析器)
  • ThemeResolver(主题解析器)
  • MultipartResolver(文件上传解析器)
  • FlashMapManagerFalshMap 管理器)

默认的初始化配置定义文件:DispatcherServlet.properties

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

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.method.annotation.RequestMappingHandlerMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,
	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

请求参数绑定

绑定简单数据类型参数,只需要直接声明形参即可(形参参数名和传递的参数名要保持⼀致,建议使用包装类型,当形参参数名和传递参数名不⼀致时可以使用 @RequestParam 注解进行手动映射

  • 默认支持 Servlet API 作为方法参数

    • HttpServletRequestHttpServletResponseHttpSession
  • 绑定简单类型参数

    • 简单数据类型:八种基本数据类型及其包装类型
      • 参数类型推荐使用包装数据类型,因为基础数据类型不可以为 null
    • 整型: Integerint
    • 字符串: String
    • 单精度: Floatfloat
    • 双精度: Doubledouble
    • 布尔型: Booleanboolean
      • 对于布尔类型的参数, 请求的参数值为 truefalse。或者 10
  • 绑定 Pojo 类型参数

    • 要求传递的参数名必须和 Pojo 的属性名保持⼀致
  • 绑定 Pojo 包装对象参数

    /demo/handle05?user.id=1&user.username=zhangsan
    
    public class QueryVo {
        private String mail;
        private String phone;
    }
    
    @RequestMapping("/handle05")
    public ModelAndView handle05(QueryVo queryVo) {
    
    }
    
  • 绑定日期类型参数(需要配置自定义类型转换器)

对 Restful 风格请求⽀持

  • REST(英文: Representational State Transfer,简称 REST)描述了⼀个架构样式的网络系统
  • 资源 表现层 状态转移
  • REST 并没有⼀个明确的标准,而更像是⼀种设计的风格。
<input type="hidden" name="_method" value="put"/>

@RequestMapping(value = "/handle/{id}/{name}",method = {RequestMethod.PUT})
public ModelAndView handlePut(@PathVariable("id") Integer id,@PathVariable("name") String username) {
}

<!--配置springmvc请求⽅式转换过滤器,会检查请求参数中是否有_method参数,如果有就
按照指定的请求⽅式进⾏转换-->
<filter>
    <filter-name>hiddenHttpMethodFilter</filter-name>
    <filterclass>org.springframework.web.filter.HiddenHttpMethodFilter</filterclass>
</filter>
<filter-mapping>
    <filter-name>hiddenHttpMethodFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Spring MVC ⾼级技术

拦截器(Inteceptor)使用

  • Servlet:处理 Request 请求和 Response 响应
  • 过滤器(Filter):对 Request 请求起到过滤的作用,作用在 Servlet 之前,如果配置为 /* 可以对所有的资源访问(servlet、 js/css 静态资源等)进行过滤处理
  • 监听器(Listener):实现了 javax.servlet.ServletContextListener 接口的服务器端组件,它随 Web应用的启动而启动,只初始化⼀次,然后会⼀直运行监视,随 Web 应用的停止而销毁
    • 作用一: 做一些初始化⼯作, web 应用中 Spring 容器启动 ContextLoaderListener
    • 作用二: 监听 web 中的特定事件,比如 HttpSessionServletRequest 的创建和销毁;变量的创建、销毁和修改等。可以在某些动作前后增加处理,实现监控,比如统计在线⼈数,利用 HttpSessionLisener 等。
  • 拦截器(Interceptor):是 SpringMVC、 Struts 等表现层框架自己的,不会拦截 jsp/html/css/image 的访问等,只会拦截访问的控制器⽅法(Handler)。
  • 从配置的角度也能够总结发现: serlvet 、 filter 、 listener 是配置在 web.xml 中的,而 interceptor 是配置在表现层框架自己的配置文件中的
    • 在 Handler 业务逻辑执行之前拦截一次
    • 在 Handler 逻辑执行完毕但未跳转页面之前拦截一次
    • 在跳转页面之后拦截一次

img

源码参考:

  • org.springframework.web.servlet.DispatcherServlet#doDispatch
  • org.springframework.web.servlet.HandlerInterceptor

执行过程:

  1. 程序先执行 preHandle() 方法,如果该方法的返回值为 true ,则程序会继续向下执行处理器中的方法,否则将不再向下执行。
  2. 在业务处理器(即控制器 Controller 类)处理完请求后,会执行 postHandle() 方法,然后会通过 DispatcherServlet 向客户端返回响应。
  3. DispatcherServlet 处理完请求后,才会执行 afterCompletion() 方法。

多个拦截器的执行流程:

img

文件上传

<!--⽂件上传所需jar坐标-->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.1</version>
</dependency>



<!--配置⽂件上传解析器, id是固定的multipartResolver-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!--设置上传⼤⼩,单位字节-->
    <property name="maxUploadSize" value="1000000000"/>
</bean>



@RequestMapping("upload")
public String upload(MultipartFile uploadFile, HttpServletRequest request) throws IOException {

}

异常控制

异常处理的三种方式,默认优先级从上到下:

  1. 在 Controller 内定义 @ExceptionHandler 注解方法,处理 Controller 内的异常

    @ExceptionHandler({ArithmeticException.class})
    @ResponseBody
    public String testEx() {
        return "testEx";
    }
    
  2. @ControllerAdvice 全局异常处理

    // 可以让我们优雅的捕获所有Controller对象handler⽅法抛出的异常
    @ControllerAdvice
    public class GlobalExceptionResolver {
    
        @ExceptionHandler(ArithmeticException.class)
        public ModelAndView handleException(ArithmeticException exception, HttpServletResponse response) {
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.addObject("msg",exception.getMessage());
            modelAndView.setViewName("error");
            return modelAndView;
        }
    
    }
    
  3. 自定义 HandlerExceptionResolver

    可以通过设置 Order 来自定义在 DispatcherServlet#handlerExceptionResolvers 中的位置,默认为最后,如果设置了 order 小于 0,则最先处理

DispatcherServlet 九大组件之一:DispatcherServlet#handlerExceptionResolvers

获取异常处理方法:

  • org.springframework.web.servlet.DispatcherServlet#processDispatchResult
    • org.springframework.web.servlet.DispatcherServlet#processHandlerException
      • 遍历 handlerExceptionResolvers,处理异常

初始化 handlerExceptionResolvers 的默认策略 DispatcherServlet.properties 中:

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,
	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,
	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

解析 mvc:annotation-driven 标签时,会在容器中增加三个 HandlerExceptionResolver

  • org.springframework.web.servlet.config.MvcNamespaceHandler#init

    • org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser#parse

      • ExceptionHandlerExceptionResolver

        • order 为 0
      • ResponseStatusExceptionResolver

        • order 为 1
      • DefaultHandlerExceptionResolver

        • order 为 2

Controller 内的 @ExceptionHandler 注解方法和 @ControllerAdvice 全局异常处理与 ExceptionHandlerExceptionResolver 相关

  • org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#getExceptionHandlerMethod

ExceptionHandlerExceptionResolver 关联 @ControllerAdvice 方法:

  • org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#exceptionHandlerAdviceCache
  • org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#initExceptionHandlerAdviceCache

基于 Flash 属性的跨重定向请求数据传递

return "redirect:handle01?name=" + name;



/**
 * SpringMVC 重定向时参数传递的问题
 * 转发:A 找 B 借钱400,B没有钱但是悄悄的找到C借了400块钱给A
 *      url不会变,参数也不会丢失,一个请求
 * 重定向:A 找 B 借钱400,B 说我没有钱,你找别人借去,那么A 又带着400块的借钱需求找到C
 *      url会变,参数会丢失需要重新携带参数,两个请求
 */

@RequestMapping("/handleRedirect")
public String handleRedirect(String name,RedirectAttributes redirectAttributes) {

    //return "redirect:handle01?name=" + name;  // 拼接参数安全性、参数长度都有局限
    // addFlashAttribute方法设置了一个flash类型属性,该属性会被暂存到session中,在跳转到页面之后该属性销毁
    redirectAttributes.addFlashAttribute("name",name);
    return "redirect:handle01";

}

手写 MVC 框架

Spring MVC 源码深度剖析

核心方法:org.springframework.web.servlet.DispatcherServlet#doDispatch

  1. 调用 getHandler() 获取到能够处理当前请求的执行链 HandlerExecutionChain(Handler + 拦截器)
  2. 调用 getHandlerAdapter() 获取能够执行 Handler 的适配器
  3. 适配器调用 Handler 执行 ha.handle (总会返回一个 ModelAndView 对象)
  4. 调用 processDispatchResult() 方法完成视图渲染跳转

SSM 整合

整合目标:

  • 数据库连接池以及事务管理都交给 Spring 容器来完成
  • SqlSessionFactory 对象应该放到 Spring 容器中作为单例对象管理
  • Mapper 动态代理对象交给 Spring 管理,我们从 Spring 容器中直接获得 Mapper 的代理对象
  1. 目录结构

    img
  2. POM

    <dependencies>
    
            <!--spring相关 START-->
    
            <!--SpringMVC-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>5.1.12.RELEASE</version>
            </dependency>
    
    
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.1.12.RELEASE</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
                <version>5.1.12.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-tx</artifactId>
                <version>5.1.12.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aop</artifactId>
                <version>5.1.12.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>1.8.9</version>
            </dependency>
            <!--spring相关 END-->
    
            <!--mybatis START-->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.4.5</version>
            </dependency>
            <!--mybatis与spring的整合包-->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis-spring</artifactId>
                <version>2.0.3</version>
            </dependency>
            <!--mybatis END-->
    
            <!--数据库相关 START-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.46</version>
            </dependency>
            <!--druid连接池-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.1.21</version>
            </dependency>
            <!--数据库相关 END-->
    
            <!--jsp-api&servlet-api START-->
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>jsp-api</artifactId>
                <version>2.0</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <version>3.1.0</version>
                <scope>provided</scope>
            </dependency>
            <!--页面使用jstl表达式-->
            <dependency>
                <groupId>jstl</groupId>
                <artifactId>jstl</artifactId>
                <version>1.2</version>
            </dependency>
            <dependency>
                <groupId>taglibs</groupId>
                <artifactId>standard</artifactId>
                <version>1.1.2</version>
            </dependency>
            <!--jsp-api&servlet-api END-->
    
            <!--json数据交互所需jar START-->
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-core</artifactId>
                <version>2.9.0</version>
            </dependency>
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
                <version>2.9.0</version>
            </dependency>
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-annotations</artifactId>
                <version>2.9.0</version>
            </dependency>
            <!--json数据交互所需jar END-->
    
            <!--test START-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-test</artifactId>
                <version>5.1.12.RELEASE</version>
            </dependency>
            <!--junit-->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <scope>test</scope>
            </dependency>
            <!--test END-->
        </dependencies>
    
  3. web.xml

    <!DOCTYPE web-app PUBLIC
            "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
            "http://java.sun.com/dtd/web-app_2_3.dtd" >
    
    <web-app>
        <display-name>Archetype Created Web Application</display-name>
    
    
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath*:applicationContext*.xml</param-value>
        </context-param>
        <!--spring框架启动-->
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
    
    
        <!--springmvc启动-->
        <servlet>
            <servlet-name>springmvc</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath*:springmvc.xml</param-value>
            </init-param>
            <load-on-startup>1</load-on-startup>
        </servlet>
    
    
        <servlet-mapping>
            <servlet-name>springmvc</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    </web-app>
    
  4. applicationContext-dao.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd">
    
        <!--数据库连接池以及事务管理都交给Spring容器来完成-->
    
        <!--引入外部资源文件-->
        <context:property-placeholder location="classpath:jdbc.properties"/>
    
        <!--第三方jar中的bean定义在xml中-->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="${jdbc.driver}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
        </bean>
    
    
        <!--
            SqlSessionFactory对象应该放到Spring容器中作为单例对象管理
            原来mybaits中sqlSessionFactory的构建是需要素材的:SqlMapConfig.xml中的内容
        -->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <!--别名映射扫描-->
            <property name="typeAliasesPackage" value="com.lagou.edu.pojo"/>
            <!--数据源dataSource-->
            <property name="dataSource" ref="dataSource"/>
        </bean>
    
    
        <!--Mapper动态代理对象交给Spring管理,我们从Spring容器中直接获得Mapper的代理对象-->
        <!--扫描mapper接口,生成代理对象,生成的代理对象会存储在ioc容器中-->
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <!--mapper接口包路径配置-->
            <property name="basePackage" value="com.lagou.edu.mapper"/>
            <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        </bean>
    
    </beans>
    
  5. applicationContext-service.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/tx
           http://www.springframework.org/schema/tx/spring-tx.xsd
    ">
    
        <!--包扫描 Service-->
        <context:component-scan base-package="com.lagou.edu.service"/>
    
        <!--事务管理-->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"/>
        </bean>
    
        <!--事务管理注解驱动-->
        <tx:annotation-driven transaction-manager="transactionManager"/>
    
    </beans>
    
  6. springmvc.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns:context="http://www.springframework.org/schema/context"
           xmlns:mvc="http://www.springframework.org/schema/mvc"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns="http://www.springframework.org/schema/beans"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/mvc
           http://www.springframework.org/schema/mvc/spring-mvc.xsd
    ">
    
        <!--包扫描 Controller-->
        <context:component-scan base-package="com.lagou.edu.controller"/>
    
        <!--配置springmvc注解驱动,自动注册合适的组件 handlerMapping 和 handlerAdapter -->
        <mvc:annotation-driven/>
    
    </beans>
    
  7. jdbc.properties

    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/bank
    jdbc.username=root
    jdbc.password=123456
    

乱码问题解决

  • Post 请求乱码, web.xml 中加入过滤器

    <!-- 解决post乱码问题 -->
    <filter>
        <filter-name>encoding</filter-name>
        <filter-class>
            org.springframework.web.filter.CharacterEncodingFilter
        </filter-class>
        <!-- 设置编码参是UTF8 -->
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encoding</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
  • Get 请求乱码(Get 请求乱码需要修改 tomcat 下 server.xml 的配置)

    <Connector URIEncoding="utf-8" connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>
    

参考资料

原文地址:https://www.cnblogs.com/huangwenjie/p/14090296.html