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 三大件( HandlerMapperHandlerAdapterViewResolver
  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. 前端控制器请求 ViewResolver 视图解析器去进行视图解析,根据逻辑视图名来解析真正的视图。
  9. 视图解析器向前端控制器返回 View
  10. 前端控制器进行视图渲染,就是将模型数据(在 ModelAndView 对象中)填充到 request 域前端控制器向用户响应结果

Spring MVC 九大组件

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

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

默认的初始化配置定义文件: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 是配置在表现层框架自己的配置文件中的
    • preHandle :在 Handler 业务逻辑执行之前拦截一次
    • postHandle :在 Handler 逻辑执行完毕但未跳转页面之前拦截一次
    • afterCompletion :在跳转页面之后拦截一次

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"/>
    

参考资料

posted @ 2020-12-05 17:14  流星<。)#)))≦  阅读(124)  评论(0编辑  收藏  举报