20201205 Spring MVC - 拉勾教育
Spring MVC 应用
三层架构 和 MVC 设计模式
MVC 对应于三层架构中的 表现层
三层架构:
- 表现层
- 业务层
- 持久层
MVC 设计模式:
- Model(模型)
- View(视图)
- Controller(控制器)
Spring MVC 介绍
Spring MVC 本质可以认为是对 Serlvet 的封装,简化了我们 Serlvet 的开发
Spring MVC 请求处理流程 :
开发过程:
- 配置
DispatcherServlet
前端控制器 - 开发处理具体业务逻辑的 Handler(
@Controller
、@RequestMapping
) - XML 配置文件配置 Controller 扫描,配置 Spring MVC 三大件(
HandlerMapper
、HandlerAdapter
、ViewResolver
) - 将 XML 文件路径告诉 Spring MVC (
DispatcherServlet
)
Spring MVC 请求处理流程 :
- 用户发送请求至前端控制器
DispatcherServlet
DispatcherServlet
收到请求调用HandlerMapping
处理器映射器- 处理器映射器根据请求 Url 找到具体的
Handler
(后端控制器),生成处理器对象及处理器拦截器(如果有则生成)一并返回DispatcherServlet
DispatcherServlet
调用HandlerAdapter
处理器适配器去执行Handler
- 处理器适配器执行
Handler
Handler
执行完成给处理器适配器返回ModelAndView
- 处理器适配器向前端控制器返回
ModelAndView
,ModelAndView
是 SpringMVC 框架的一个底层对象,包括Model
和View
- 前端控制器请求
ViewResolver
视图解析器去进行视图解析,根据逻辑视图名来解析真正的视图。 - 视图解析器向前端控制器返回
View
- 前端控制器进行视图渲染,就是将模型数据(在
ModelAndView
对象中)填充到 request 域前端控制器向用户响应结果
Spring MVC 九大组件
定义和初始化方法:org.springframework.web.servlet.DispatcherServlet#initStrategies
HandlerMapping
(处理器映射器)HandlerAdapter
(处理器适配器)HandlerExceptionResolver
(处理器异常解析器)ViewResolver
(视图解析器)RequestToViewNameTranslator
(请求到视图名称翻译器)LocaleResolver
(国际化解析器)ThemeResolver
(主题解析器)MultipartResolver
(文件上传解析器)FlashMapManager
(FlashMap
管理器)
默认的初始化配置定义文件: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 作为方法参数
HttpServletRequest
、HttpServletResponse
、HttpSession
等
-
绑定简单类型参数
- 简单数据类型:八种基本数据类型及其包装类型
- 参数类型推荐使用包装数据类型,因为基础数据类型不可以为
null
- 参数类型推荐使用包装数据类型,因为基础数据类型不可以为
- 整型:
Integer
、int
- 字符串:
String
- 单精度:
Float
、float
- 双精度:
Double
、double
- 布尔型:
Boolean
、boolean
- 对于布尔类型的参数, 请求的参数值为
true
或false
。或者1
或0
- 对于布尔类型的参数, 请求的参数值为
- 简单数据类型:八种基本数据类型及其包装类型
-
绑定 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 中的特定事件,比如
HttpSession
,ServletRequest
的创建和销毁;变量的创建、销毁和修改等。可以在某些动作前后增加处理,实现监控,比如统计在线人数,利用HttpSessionLisener
等。
- 作用一: 做一些初始化工作, web 应用中 Spring 容器启动
- 拦截器(Interceptor):是 SpringMVC、 Struts 等表现层框架自己的,不会拦截 jsp/html/css/image 的访问等,只会拦截访问的控制器方法(Handler)。
- 从配置的角度也能够总结发现: Serlvet 、 Filter 、 Listener 是配置在
web.xml
中的,而 Interceptor 是配置在表现层框架自己的配置文件中的preHandle
:在 Handler 业务逻辑执行之前拦截一次postHandle
:在 Handler 逻辑执行完毕但未跳转页面之前拦截一次afterCompletion
:在跳转页面之后拦截一次
源码参考:
org.springframework.web.servlet.DispatcherServlet#doDispatch
org.springframework.web.servlet.HandlerInterceptor
执行过程:
- 程序先执行
preHandle()
方法,如果该方法的返回值为 true ,则程序会继续向下执行处理器中的方法,否则将不再向下执行。 - 在业务处理器(即控制器 Controller 类)处理完请求后,会执行
postHandle()
方法,然后会通过DispatcherServlet
向客户端返回响应。 - 在
DispatcherServlet
处理完请求后,才会执行afterCompletion()
方法。
多个拦截器的执行流程:
文件上传
<!--⽂件上传所需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 {
}
异常处理
异常处理的三种方式,默认优先级从上到下:
-
在 Controller 内定义
@ExceptionHandler
注解方法,处理Controller
内的异常@ExceptionHandler({ArithmeticException.class}) @ResponseBody public String testEx() { return "testEx"; }
-
@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; } }
-
自定义
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
- 调用
getHandler()
获取到能够处理当前请求的执行链HandlerExecutionChain
(Handler + 拦截器) - 调用
getHandlerAdapter()
获取能够执行 Handler 的适配器 - 适配器调用 Handler 执行
ha.handle
(总会返回一个ModelAndView
对象) - 调用
processDispatchResult()
方法完成视图渲染跳转
SSM 整合
整合目标:
- 数据库连接池以及事务管理都交给 Spring 容器来完成
SqlSessionFactory
对象应该放到 Spring 容器中作为单例对象管理Mapper
动态代理对象交给 Spring 管理,我们从 Spring 容器中直接获得Mapper
的代理对象
-
目录结构
-
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>
-
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>
-
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>
-
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>
-
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>
-
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"/>