文章内容输出来源:拉勾教育Java高薪训练营;
本文为SpringMVC模块学习笔记
简介
SpringMVC是什么?
在说SpringMVC是什么之前,我们先讲一下,系统标准的三层架构:表现层,业务成,持久层。
- 表现层:(WEB层)字面意思就是表现给用户看的一层,他承接着和用户交互的功能,他有包括展示层和控制层。
- 展示层:用户接受用户意图和结果展示
- 控制层:负责接受展示层发过来的请求,转发请求给业务层,获取处理结果转发给展示层。这个流程可以理解为MVC模式。
- 业务层:也就是我们说的Service层。他负责程序具体业务逻辑处理,对外(web层)提供接口调用,依赖持久层。
- 持久层:也即是我们所的dao层(data access object),负责数据持久化,主要和数据库交互,基本的增删改查都通过他。
上面说的程序开发的三层架构,其中表现层用到了MVC模式:
MVC 全名是 Model View Controller,是模型(model)-视图(view)-控制器(controller) 的缩写, 是一种用于设计创建 Web 应用程序表现层的模式。MVC 中每个部分各司其职:
- Model(模型):包括业务模型和数据模型,用于数据传输;
- View(视图):通常指JSP或者HTML,对于后端来说,整个前端工程都是View,用于数据的展示和与用户交互。
- Controller(控制器):具体处理用户交互的一层,用于接收视图传过来的用户请求,转发请求到对应的业务层处理,返回处理结果。
上面说了系统的三层架构和MVC模式,那个SpringMVC是什么呢?他其实是MVC模式的一种实现,用于系统三层架构中的表现层。
SpringMVC 已经成为目前最主流的 MVC 框架之一,并且随着 Spring3.0 的发布,全面超越Struts2, 成为最优秀的 MVC 框架。
他与Struts2对比有什么优势呢?
servlet、struts实现接口、springmvc中要让一个java类能够处理请求只需要添加注解就ok
SpringMVC通过一套注解,让一个简单的 Java 类成为处理请求的控制器,而无须实现任何接口。同时它还支持 RESTful 编程⻛格的请求。
Spring MVC 本质可以认为是对servlet的封装,简化了我们serlvet的开发。
SpringMVC和Spring什么关系?
SpringMVC是Spring家族下一个WEB项目,用来实现MVC模式,它依赖Spring的IOC和AOP特性。
MVC容器和IOC容器什么关系?
MVC容器其实也是一个IOC容器,它独立与Spring项目配置的IOC容器。MVC容器可以获取到IOC容器管理的Bean,但是IOC容器获取不到MVC容器管理的Bean,他们关系可以认为是父子容器,MVC容器是子,IOC容器是父。
SpringMVC工作原理
整体工作流程:
流程说明:
- 用户发送请求至前端控制器DispatcherServlet
- DispatcherServlet收到请求调用HandlerMapping处理器映射器
- 处理器映射器根据请求Url找到具体的Handler(后端控制器),生成处理器对象及处理器拦截器(如果有则生成)一并返回DispatcherServlet
- DispatcherServlet调用HandlerAdapter处理器适配器去调用Handler
- 处理器适配器执行Handler
- Handler执行完成给处理器适配器返回ModelAndView
- 处理器适配器向前端控制器返回 ModelAndView,ModelAndView是SpringMVC框架的一个底层对象,包括 Model 和 View
- 前端控制器请求视图解析器去进行视图解析,根据逻辑视图名来解析真正的视图。
- 视图解析器向前端控制器返回View
- 前端控制器进行视图渲染,就是将模型数据(在ModelAndView对象中)填充到 request 域
- 前端控制器向用户响应结果
SPringMVC 9大组件
- HandlerMapping (处理器映射器)
用于根据当前请求uri
查找对应的Handler
处理器的,处理器具体表现可以是类,也可以是方法。他返回的是Handler
和Interceptor
连 - HandlerAdapter(处理器适配器)
它是一个适配器,用于适配不同形式的Handler
。Handler
的形式是任意的(类,方法,方法参数不同),但是Handler
从Servlet
获取请求是固定的doService(HttpServletRequest req,HttpServletResponse resp)
形式,所以需要HandlerAdapter
来让固定的Servlet
请求适应不同的Handler
。 - ViewResolver(视图解析器)
用于将String类型的视图名和Locale解析为View类型的视图,只有一个resolveViewName()方法。从方法的定义可以看出,Controller层返回的String类型视图名 viewName 最终会在这里被解析成为View。View是用来渲染⻚面的,也就是说,它会将程序返回的参数和数据填入模板中,生成html文件。默认情况下,Spring MVC会自动为我们配置一个 InternalResourceViewResolver,是针对 JSP 类型视图的。ViewResolver 在这个过程主要完成两件事情:找到渲染所用的模板和所用的技术(如JSP)并填入参数。 - RequestToViewNameTranslator
它的作用是从请求中获取 ViewName.因为 ViewResolver 根据 ViewName 查找 View,但有的 Handler 处理完成之后,没有设置 View,也没有设置 ViewName, 便要通过这个组件从请求中查找 ViewName。 - LocaleResolver
ViewResolver 组件的 resolveViewName 方法需要两个参数,一个是视图名,一个是 Locale。 LocaleResolver 用于从请求中解析出 Locale,比如中国 Locale 是 zh-CN,用来表示一个区域。这 个组件也是 i18n 的基础。 - ThemeResolver
ThemeResolver 组件是用来解析主题的。主题是样式、图片及它们所形成的显示效果的集合。 - MultipartResolver
MultipartResolver 用于上传请求,通过将普通的请求包装成 MultipartHttpServletRequest 来实 现。MultipartHttpServletRequest 可以通过 getFile() 方法 直接获得文件。如果上传多个文件,还 可以调用 getFileMap()方法得到Map<FileName,File>这样的结构,MultipartResolver 的作用就 是封装普通的请求,使其拥有文件上传的功能。 - FlashMapManager
FlashMap 用于重定向时的参数传递。FlashMapManager 就是用来管理 FalshMap 的。
使用实战
注意:spring-mvc的配置可以和spring的配置文件在一起,这样其实就是,将所有Bean都交由IOC容器管理,如果将mvc配置独立出来,可以将contoller
单独交由MVC容器管理。
项目整合
这里在spring已经整合基础上讲解
- 引入依赖坐标
- 添加
springmvc.xml
配置文件 - 配置
web.xml
启动入口 - 编写Controller
引入依赖坐标:
<!--spring-mvc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<!--jsp-api&servlet-api-->
<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>
添加springmvc.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:mvc="http://www.springframework.org/schema/mvc"
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/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
">
<!--扫描controller-->
<context:component-scan base-package="com.wjy.ssm.controller"/>
<!--配置springmvc注解驱动,自动注册合适的组件handlerMapping和handlerAdapter-->
<mvc:annotation-driven/>
</beans>
配置web.xml
启动入口
<!--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>
编写Controller
// 表示当前类是Controller类,交由容器管理
@Controller
// RequestMapping的值最终会作为url发布
@RequestMapping("/base")
public class LoginContoller {
// 发布地址: localhost:port/base/login
@RequestMapping("/login")
public ModelAndView login(String username, String password, HttpSession session) {
final String admin = "admin";
if (admin.equals(username) && admin.equals(password)) {
// 登录成功,服务器存储登录状态
session.setAttribute("login", username);
return new ModelAndView("redirect:/resume/list");
}
return new ModelAndView("index");
}
// 发布地址: localhost:port/base/logout
@RequestMapping("/logout")
public ModelAndView logout(HttpSession session) {
session.invalidate();
return new ModelAndView("index");
}
}
使用servlet容器部署后,就可以通过RequestMapping
指定的地址访问了。
url-pattern配置说明
SpringMVC本质上是一个,Servlet,他需要配置url-pattern指定哪些path下请求被SpringMVC处理
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<!--
方式一:带后缀,比如*.action *.do *.aaa
该种方式比较精确、方便,在以前和现在企业中都有很大的使用比例
方式二:/ 不会拦截 .jsp,但是会拦截.html等静态资源(静态资源:除了servlet和jsp之外的js、css、png等)
为什么配置为/ 会拦截静态资源???
因为tomcat容器中有一个web.xml(父),你的项目中也有一个web.xml(子),是一个继承关系
父web.xml中有一个DefaultServlet, url-pattern 是一个 /
此时我们自己的web.xml中也配置了一个 / ,覆写了父web.xml的配置
为什么不拦截.jsp呢?
因为父web.xml中有一个JspServlet,这个servlet拦截*.jsp文件,而我们并没有覆写这个配置,
所以springmvc此时不拦截jsp,jsp的处理交给了tomcat
方式三:/* 拦截所有,包括.jsp
-->
<!--拦截匹配规则的url请求,进入springmvc框架处理-->
<url-pattern>/</url-pattern>
</servlet-mapping>
静态资源访问配置
使用SpringMVC提供的Handler解析转发静态资源请求
- 配置SpringMVC的url-pattern为
/
拦截除了*.jsp
的所有请求 - 配置文件添加静态资源处理
Handler
<!--静态资源配置--> <!-- 原理:添加该标签配置之后,会在SpringMVC上下文中定义一个DefaultServletHttpRequestHandler对象 这个对象如同一个检查人员,对进入DispatcherServlet的url请求进行过滤筛查,如果发现是一个静态资源请求 那么会把请求转由web应用服务器(tomcat)默认的DefaultServlet来处理,如果不是静态资源请求,那么继续由 SpringMVC框架处理 --> <mvc:default-servlet-handler/>
使用SpringMVC管理静态资源
- 配置url-pattern为
/*
拦截所有请求 - 将所有静态资源放到
webapp
下一个目录,如webapp/WEB-INF/static
- 配置文件添加配置
<!-- location:服务器存放文件路径 mapping:用户请求路径 如下配置,用户对 localhost:port/static/...下的所有请求,都会转发到对应的服务器/WEB-INF/static/...路径下 --> <mvc:resources location="/WEB-INF/static/" mapping="/static/**"/>
传统Controller跳转页面方式
- 通过
ModelAndView
方式 - 直接
Controller
返回页面名称字符串
本质上是使用InternalResourceViewResolver
视图解析器,跳转页面,可以通过配置文件,配置InternalResourceViewResolver
的具体参数,如果视图前缀后缀
<!--视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/"/>
<property name="suffix" value=".jsp"/>
</bean>
传统Controller往JSP页面传输数据
- 通过形参声明
Model,ModelMap,Map,ModelAndView
对象,然后调用他们提供的方法addXXX(String,Object)
等,可以将数据放入Request
作用域中。 - JSP页面通过
EL
表达式从Request
作用域取出数据使用。
web参数绑定
参数绑定只需要,在@RequestMapping
修饰的方法上声明形参就可以收到web传递的参数
简单参数绑定
简单数据类型:八种基本数据类型及其包装类型
参数类型推荐使用包装数据类型,因为基础数据类型不可以为null
整型:Integer、int
字符串:String
单精度:Float、float
双精度:Double、double
布尔型:Boolean、boolean (对于布尔类型的参数,请求的参数值为true或false。或者1或0)
绑定简单数据类型参数,只需要直接声明形参即可(形参参数名和传递的参数名要保持一 致,建议 使用包装类型,当形参参数名和传递参数名不一致时可以使用@RequestParam注解进行 手动映射)
pojo类接收参数
url中id=1&username=zhangsan
会注入到user
Controller:
/*
* SpringMVC接收pojo类型参数 url:/demo/handle04?id=1&username=zhangsan
*
* 接收pojo类型参数,直接形参声明即可,类型就是Pojo的类型,形参名无所谓
* 但是要求传递的参数名必须和Pojo的属性名保持一致
*/
@RequestMapping("/handle04")
public ModelAndView handle04(User user) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("success");
return modelAndView;
}
实体类
public class User {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
指定参数接收别名
通过@RequestParam
注解指定参数别名,下面例子,ids=1
会注入到id
形参中
/*
* SpringMVC 接收简单数据类型参数 url:/demo/handle03?ids=1
*/
@RequestMapping("/handle03")
public ModelAndView handle03(@RequestParam("ids") Integer id,Boolean flag) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("success");
return modelAndView;
}
日期类型参数接收(定制类型参数接收)
使用自定义类型转换器处理日期类型,同时也可以使用自定义类型转换器处理自定义类型
- 创建类型转换器
- 配置文件中配置注册类型转换器
- 从行参中获取转换后的类型参数
- 创建类型转换器
/**= * 自定义类型转换器 * S:source,源类型 * T:target:目标类型 */ public class DateConverter implements Converter<String, Date> { @Override public Date convert(String source) { // 完成字符串向日期的转换 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); try { Date parse = simpleDateFormat.parse(source); return parse; } catch (ParseException e) { e.printStackTrace(); } return null; } }
- 注册类型转换器
<!-- 自动注册最合适的处理器映射器,处理器适配器(调用handler方法) --> <mvc:annotation-driven conversion-service="conversionServiceBean"/> <!--注册自定义类型转换器--> <bean id="conversionServiceBean" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <set> <bean class="com.lagou.edu.converter.DateConverter"></bean> </set> </property> </bean>
- 后台Handler方法
输出/** * 绑定日期类型参数 * 定义一个SpringMVC的类型转换器 接口,扩展实现接口接口,注册你的实现 * @param date * @return */ @RequestMapping("/handle06") public voie handle06(Date date) { System.out.println(date); }
Tue Sep 22 00:00:00 CST 2020
- 前端jsp
<a href="/demo/handle06?birthday=2020-09-22">点击测试</a>
Servlet对象获取方式
获取Servlet
对象,只需要在Handler
方法行参中声明想要获取的对象即可,SpringMVC会将Servlet
对象注入;
@RequestMapping("/test")
public ModelAndView test(HttpServletRequest request, HttpServletResponse response, HttpSession session) {
session.invalidate();
return new ModelAndView("index");
}
以上代码中request,response,session
即为Servlet
对象
REST风格支持
获取URL路径参数
使用@PathVariable
注解获取URL路径参数
/**
* /demo/handle/15 这个请求接收后,id的值为15
**/
@RequestMapping(value = "/handle/{id}")
public String handleDelete(@PathVariable("id") Integer id) {
return "success";
}
Handler
处理指定类型请求
通过@RequestMapping(method = {RequestMethod.GET})
的method属性指定特定类型请求处理,只可以为:
public enum RequestMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;
private RequestMethod() { /* compiled code */ }
}
以下Handler
只会拦截DELETE
请求
@RequestMapping(value = "/handle/{id}",method = {RequestMethod.DELETE})
public String handleDelete(@PathVariable("id") Integer id) {
return "success";
}
JSON格式数据交互支持
- 引入依赖包
<!--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-->
- 使用注解指定获取JSON数据
@RequestBody
指定参数从请求体中获取数据,数据格式为JSON字符串,并序列化为pojo类@ResponseBody
指定返回值序列化为JSON字符串,并response到web页面
示例
- 前端代码
// 发送ajax请求 $.ajax({ url: '/demo/handle07', type: 'POST', data: '{"id":"1","name":"李四"}', contentType: 'application/json;charset=utf-8', dataType: 'json', success: function (data) { alert(data.name); } })
- 后台Handler方法
// 添加@ResponseBody之后,不再走视图解析器那个流程,而是等同于response直接输出数据 @RequestMapping("/handle07") public @ResponseBody User handle07(@RequestBody User user) { // 业务逻辑处理,修改name为张三丰 user.setName("张三丰"); return user; }
- 最终,后端会收到数据为
{"id":"1","name":"李四"}
的User对象,返回页面数据为{"id":"1","name":"张三丰"}
的JSON对象
其他特性
拦截器
监听器、过滤器和拦截器对比
- Servlet:处理Request请求和Response响应
- 过滤器(Filter):在
Servlet
执行之前执行,如果配置为/*可以对所有的资源访问(servlet、js/css静态资源等)进行过滤处理 - 监听器(Listener):实现了
javax.servlet.ServletContextListener
接口的服务器端组件,它随 Web应用的启动而启动,只初始化一次,然后会一直运行监视,随Web应用的停止而销毁- 作用一:做一些初始化工作,web应用中spring容器启动ContextLoaderListener
- 作用二:监听web中的特定事件,比如HttpSession,ServletRequest的创建和销毁;变量的创建、 销毁和修改等。可以在某些动作前后增加处理,实现监控,比如统计在线人数,利用 HttpSessionLisener等。
- 拦截器(Interceptor):SpringMVC提供的特性,不会拦截 jsp/html/css/image的访问等,只拦截Handler。
拦截器执行流程
Handler
执行前执行一次,如果返回true
则进行下一步,否则直接返回Handler
执行完成后执行一次- 然后到前端页面前执行一次(与上一次之间,还有视图渲染操作如:JSP渲染)
多个拦截器执行流程
会基于配置文件配置执行:
- preHandler:顺序执行(与配置文件配置顺序一致)
- postHandler:倒序执行
- afterCompletion:倒序执行
文件上传支持
- 引入依赖
- 注册文件解析器
- 编写代码
引入依赖
<!--文件上传所需坐标-->
<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">
<!--设置上传文件大小上限,单位是字节,-1代表没有限制也是默认的-->
<property name="maxUploadSize" value="5000000"/>
</bean>
编写代码
- 前端代码
<!-- 1 method="post" 2 enctype="multipart/form-data" 3 type="file" --> <form method="post" enctype="multipart/form-data" action="/demo/upload"> <input type="file" name="uploadFile"/> <input type="submit" value="上传"/> </form>
- 后端代码
其中@RequestMapping(value = "/upload") public ModelAndView upload(MultipartFile uploadFile,HttpSession session) }
uploadFile
就是本次上传文件使用uploadFile.transferTo(new File(服务器目录,文件名));
保存文件到服务器
全局Controller异常处理
可以通过指定行参来获取Servlet
对象
@ControllerAdvice
public class GlobalExceptionResolver {
/**
* 传统处理方式
* @param exception 拦截异常
* @param response HttpServletResponse对象
* @return
*/
@ExceptionHandler(ArithmeticException.class)
public ModelAndView handleException(ArithmeticException exception, HttpServletResponse response) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("msg", exception.getMessage());
modelAndView.setViewName("error");
return modelAndView;
}
/**
* restful处理方式
* @param exception 拦截异常
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseBody
public Object handleException(Exception exception) {
return exception.getMessage();
}
}
安全的重定向参数传递方式Flash属性
我们普通重定向传递参数方式为:
return "redirect:handle01?name=" + name;
但是上面方式是基于GET
传参,明文,有长度限制,安全性不高,此时我们可以使用SpringMVC提供的flash属性来在上下文传递参数。框架会在session中记录该属性值,当 跳转到⻚面之后框架会自动删除flash属性。
@RequestMapping("/handle01")
public ModelAndView handle01(@ModelAttribute("name") String name) {
// 封装了数据和页面信息的 ModelAndView
ModelAndView modelAndView = new ModelAndView();
// 视图信息(封装跳转的页面信息) 逻辑视图名
modelAndView.setViewName("success");
return modelAndView;
}
@RequestMapping("/handleRedirect")
public String handleRedirect(String name,RedirectAttributes redirectAttributes) {
// addFlashAttribute方法设置了一个flash类型属性,该属性会被暂存到session中,在跳转到页面之后该属性销毁
redirectAttributes.addFlashAttribute("name",name);
return "redirect:handle01";
}
以上代码,最终在handle01
跳转的success
页面内能从request
中获取name
属性;
在handle01
方法的行参中也可以获取name
参数。
乱码问题解决
- Post请求乱码,web.xml中加入过滤器
<!--springmvc提供的针对post请求的编码过滤器--> <filter> <filter-name>encoding</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <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"/>