SpringMVC之使用与原理
前言
SpringMVC 也叫 Spring web mvc。是 Spring内置的一个MVC框架,在 Spring3.0 后发布。SpringMVC 框架解决了WEB开发中常见的问题(参数接收、文件上传、表单验证等等),而且使用简单,与Spring无 缝集成。支持 RESTful风格的URL请求。采用了松散耦合可插拔组件结构,比其他 MVC 框架更具扩展性 和灵活性。一、初窥门径
在没有使用SpringMVC之前我们都是使用Servlet在做Web开发。但是使用Servlet开发在接收请求参数,数据共享,页面跳转等操作相对比较复杂。servlet是java进行web开发的标准,既然springMVC是对servlet的封装,那么很显然**SpringMVC底层就是Servlet,SpringMVC就是对Servlet进行深层次的封装。**二、小试牛刀
1.引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>springmvckkb</groupId>
<artifactId>com.dmlll.mvc</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.15.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<target>1.8</target>
<source>1.8</source>
</configuration>
</plugin>
<!--tomcat插件-->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>8080</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>
SpringMVC依赖了Spring,如果对版本没有要求,不必再导入Spring
2.创建Spring和SpringMVC的配置文件
2.1 Spring配置文件
<?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
">
<context:component-scan base-package="com.dmlll.dao,com.dmlll.service"/>
</beans>
2.2 SpringMVC配置文件
<?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" xmlns:mc="http://www.springframework.org/schema/mvc"
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
">
<context:component-scan base-package="com.dmlll.controller"/>
<!--开启mvc在注解驱动-->
<mvc:annotation-driven/>
</beans>
3.web.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--spring的配置-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:application.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--SpringMVC的配置-->
<servlet>
<servlet-name>dispatcherServlet</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>dispatcherServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
4.编写Controller
@Controller
public class TestController {
@RequestMapping("hello.do")
public ModelAndView hello(){
String str = "欲练此功,必先自宫";
System.out.println(str);
ModelAndView mv = new ModelAndView();
mv.addObject("word",str);
mv.setViewName("hello");
return mv;
}
}
5.编写个页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
${word}
</body>
</html>
6.启动tomcat
因为配置了插件,可以直接在maven中启动
三、粗通皮毛
1.SpringMVC的工作流程
- 浏览器发送请求,DispatchServlet接收到浏览器发送的请求
<!--这里依赖你的web.xml中配置的
<url-pattern>*.do</url-pattern> 所有带有后缀(.do)的请求经过前端控制器
<url-pattern>/</url-pattern> 所有请求的经过前端控制器
-->
<servlet>
<servlet-name>dispatcherServlet</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>dispatcherServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
- DispatchServlet接收到请求后转交给HandlerMapping
- HandlerMapping根据请求找到对应的处理器,并将其封装成一个处理器执行链(包括处理器Handler、处理器拦截器HandlerInterceptor)返回给DispatchServlet
- DispatchServlet根据Handler找到对应的HandlerAdaptor,HandlerAdaptor经过参数封装/数据格式转换等一系列操作。
- HandlerAdaptor执行Controller中的方法
- Controller返回ModelAndView
- HandlerAdaptor将ModelAndView返回给DispatchServlet
- DispatchServlet将ModelAndView交给ViewResolve进行解析
- ViewResolve将解析后得到的View返回给DispatchServlet
- DispatcherServlet调用视图对象进行渲染,得到响应对象
- 将响应对象返回给浏览器
代码如下(示例):
2.SpringMVC组件
- DispatcherServlet:前端控制器
用户请求的入口控制器,DispatcherServlet是整个流程控制的中心,由他调用其他组件处理用户的请求。DispatcherServlet降低了各组件之间的耦合性。 - HandlerMapping:处理器映射器
HandlerMapping是派发请求的控制器。负责根据用户的请求找到Handler。 - Handler:处理器
Handler即Controller,由程序员根据需求编写 - HandlerAdaptor:处理器适配器
负责调用处理器、传递参数等工作。 - VIewResolver:视图解析器
ViewResolver负责将处理结果生成View视图。ViewResolver首先根据逻辑视图名称(相对)解析成物理视图名称(绝对),在生成View视图对象,最后对View进行渲染,将处理结果展示给用户。
3.web.xml中url-pattern解析
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
该节点一般有两种写法:
- *.xx
在没有特殊要求的情况下,SpringMVC的前端控制器常使用后缀匹配方式 - /
该种方式是让所有的请求都经过前端控制器,去找对应的处理器。但是静态文件不会去编写处理器,所以就需要去配置静态资源的放行
<!--方法1:在SpringMVC.xml中配置-->
<mvc:default-servlet-handler/>
<!--
声明该标签之后,SpringMVC框架会在容器中创建
DefaultServletHttpRequestHandler处理器对象。
该对象会对所有进入前端控制器的URL进行检查。
如果发现是静态资源的请求,就将该请求转交给Web应用服务器处理(如:Tomcat)
一般的服务器都有默认的Servlet。
Tomcat服务器中,有一个专门用于处理静态资源访问的Servlet---->DefaultServlet。位与tomcat安装目录/conf/web.xml中
-->
<!--方法2:在SpringMVC.xml中配置-->
<!--mapping:映射该文件夹中所有文件,location:文件所在位置-->
<mvc:resources mapping="/image/**" location="/image/"/>
<!--
在Spring3.0后,定义了专门处理静态资源请求的处理器ResourceHttpRequestHandler。并添加<mvc:resources/>标签,用于解决静态资源无法访问的问题
-->
4.常用注解
- @RequestMapping
定义了处理器对于请求的映射规则,即请求路径。可以作用与类和方法 - @ResponseBody
将返回的数据结构转换为JSON格式 - @PathVariable
用来获取路径中的参数
http://localhost:8080/user/{id} - @RequestParam
用于获取请求的参数
http://localhost:8080/user?id=1 - @RequestBody
用于POST请求上,接受JSON实体参数 - @ControllerAdvice
Controller增强器,给Controller控制器添加统一的操作或处理
5.中文乱码
Spring对于请求参数中的中文乱码问题,给出了专门的CharacterEncodingFilter类,在web.xml中配置
<filter>
<filter-name>characterEncodingFilter</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>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
可进入该类查看对应的参数:
如果使用的是maven中的tomcat插件,还应该去配置tomcat的字符集
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>8080</port>
<path>/</path>
<uriEncoding>utf-8</uriEncoding>
</configuration>
</plugin>
四、登堂入室
1.处理器接收参数
编写传递参数的页面params.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>处理参数</title>
</head>
<body>
<form action="/params/get1.do">
ID:<input type="text" name="id"><br/>
姓名:<input type="text" name="name"><br/>
地址:<input type="text" name="address"><br/>
<button type="submit">提交</button>
</form>
</body>
</html>
编写请求结束跳转的页面ok.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
${id},${name},${address}
</body>
</html>
编写实体类Person.java
public class Person {
private Integer id;
private String name;
private String address;
//setter/getter/toString
}
编写页面跳转的控制器ParamsController.java
@Controller
@RequestMapping("params")
public class ParamsController {
@RequestMapping("view.do")
public ModelAndView view(){
System.out.println("进入params页面");
return new ModelAndView("params");
}
}
1.1 直接使用方法的参数逐个接收
注意:参数名必须和前端传过来的名称一致
向ParamsController.java中添加方法
@RequestMapping("get1.do")
public ModelAndView get1(Integer id,String name,String address){
System.out.println(id + "---" + name + "---" + address);
ModelAndView mv = new ModelAndView("ok");
mv.addObject("id",id);
mv.addObject("name",name);
mv.addObject("address",address);
return mv;
}
运行:
1.2 使用对象接收多个参数
注意:对象的属性必须和前端传过来的参数名称一致
向ParamsController.java中添加方法
@RequestMapping("get2.do")
public ModelAndView get2(Person person){
System.out.println(person.getId() + "---" + person.getName() + "---" + person.getAddress());
ModelAndView mv = new ModelAndView("ok");
mv.addObject("person",person);
return mv;
}
修改params.jsp中的表单
<form action="/params/get2.do">
ID:<input type="text" name="id"><br/>
姓名:<input type="text" name="name"><br/>
地址:<input type="text" name="address"><br/>
<button type="submit">提交</button>
</form>
修改ok.jsp接收参数的变量
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
${person.id},${person.name},${person.address}
</body>
</html>
运行:
1.3 使用原生HttpServletRequest获取
向ParamsController.java中添加方法
@RequestMapping("get3.do")
public ModelAndView get3(HttpServletRequest request){
String id = request.getParameter("id");
String name = request.getParameter("name");
String address = request.getParameter("address");
System.out.println(id + "---" + name + "---" + address);
ModelAndView mv = new ModelAndView("ok");
//封装成Person
Person person = new Person();
person.setId(Integer.valueOf(id));
person.setName(name);
person.setAddress(address);
mv.addObject("person",person);
return mv;
}
修改params.jsp的form路径
<form action="/params/get3.do">
运行:
1.4 获取日期类型的参数
直接接收会报错
修改Person.java,添加时间属性
private Date date;
修改params.jsp,添加时间选择框
日期:<input type="date" name="date"><br/>
运行:
需要在date属性上添加注解@DateTimeFormat(pattern = “yyyy-MM-dd”)
再次启动:
1.5 获取数组类型的参数
修改ParamsController.java
@RequestMapping("get4.do")
public ModelAndView get4(String[] names){
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("names",names);
modelAndView.setViewName("ok");
return modelAndView;
}
修改params.jsp表单
<form action="/params/get4.do">
姓名1:<input type="text" name="name"><br/>
姓名2:<input type="text" name="name"><br/>
姓名3:<input type="text" name="name"><br/>
<button type="submit">提交</button>
</form>
修改ok.jsp
${name[0]},${name[1]},${name[2]}
运行:
1.6 获取List类型的参数
集合类型不能直接接收
修改ParamsController.java
@RequestMapping("get5.do")
public ModelAndView get5(List<String> name){
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("names",names);
modelAndView.setViewName("ok");
return modelAndView;
}
修改params.jsp
<form action="/params/get5.do">
此时运行:
需要在集合参数上添加@RequestParam()注解
@RequestMapping("get5.do")
public ModelAndView get5(@RequestParam(value = "names") List<String> names){
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("names",names);
modelAndView.setViewName("ok");
return modelAndView;
}
再次启动一切正常。
如果将List<String>换成List<Person>
再次启动
(此时name属性与Person中的name属性对应,如果正常,则只有Person.name有值)
提示List中的name不存在,因此不能直接使用List去接受对象集合,将集合封装成一个对象ListVO
public class ListVO<T> {
private List<T> list;
public List<T> getList() {
return list;
}
public void setList(List<T> list) {
this.list = list;
}
}
修改ParamsController.java
@RequestMapping("get6.do")
public ModelAndView get6(ListVO vo){
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("names",vo.getNameList());
modelAndView.setViewName("ok");
return modelAndView;
}
修改params.jsp
<form action="/params/get6.do">
姓名1:<input type="text" name="nameList[0].name"><br/>
姓名2:<input type="text" name="nameList[1].name"><br/>
姓名3:<input type="text" name="nameList[2].name"><br/>
<button type="submit">提交</button>
</form>
启动程序:
注意:前端参数列表的参数名必须跟该ListVO对象的属性一致
1.7使用URL地址传参
修改ParamController.java
@RequestMapping("get7/{id}.do")
public ModelAndView get7(@PathVariable("id") Integer id){
ModelAndView modelAndView = new ModelAndView("ok");
modelAndView.addObject("id",id);
return modelAndView;
}
修改ok.jsp
${id}
直接在地址栏输入
http://localhost:8080/params/get7/1.do
因为在配置文件中配置的*.do,只有.do为后缀的才能被拦截到,所以采用了这种方式@RequestMapping(“get7/{id}.do”)
2.处理器方法返回值
处理器返回值有四种类型:
- ModelAndView
- String
- 返回自定义类型对象
- 无返回值void
ModelAndView在上一节已经使用。
2.2 返回String
@RequestMapping("get8.do")
public String get8(){
return "ok";
}
如果携带参数可携带HttpServletRequest ,通过原生的方法传递参数
2.3 返回对象类型
2.3.1 返回基本类型
@RequestMapping("get9.do")
@ResponseBody
public String get9(){
return "xiaoming";
}
引入JSON依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.12.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.4</version>
</dependency>
@RequestMapping("get9.do")
@ResponseBody
public Person get9(){
Person p = new Person();
p.setAddress("东莞");
p.setName("超级玛丽");
p.setId(1);
return p;
}
2.4 返回void
使用原生的HttpServletRequest、HttpServletResponse
3. 转发和重定向
3.1 转发
@RequestMapping("get11.do")
public String get11(){
return "forward:/ok.jsp";
}
@RequestMapping("get12.do")
public ModelAndView get12(){
return new ModelAndView("forward:/ok.jsp");
}
3.2 重定向
@RequestMapping("get13.do")
public String get13(){
return "redirect:/ok.jsp";
}
@RequestMapping("get14.do")
public ModelAndView get14(){
return new ModelAndView("redirect:/ok.jsp");
}
一般的重定向不会传递参数,SpringMVC中的重定向可以传递参数,以?key=value的形式拼接
4.异常处理
使用@ExceptionHandler注解,指定为异常处理的方法。
返回值可以是:ModelAndView、String、void
参数可以是:Exception及其子类、HttpServletRequest、HttpServletResponse等,系统会自动为其赋值
4.1 自定义异常类
public class MyException extends Exception{
public MyException() {
}
public MyException(String message) {
super(message);
}
}
ParamsController.java
@RequestMapping("get16.do")
public ModelAndView get16() throws MyException {
throw new MyException("就故意报错");//抛出异常
}
@ExceptionHandler(value= MyException.class)
public ModelAndView get15(Exception ex){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("ok");
modelAndView.addObject("msg",ex.getMessage());
return modelAndView;
}
修改ok.jsp
${msg}
运行
也可以通过@ControllerAdvice注解将异常处理方法统一管理
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = ArithmeticException.class)
public ModelAndView exception01(ArithmeticException ex){
ModelAndView ok = new ModelAndView("ok");
ok.addObject("msg",ex.getMessage());
return ok;
}
@ExceptionHandler(value = Exception.class)
public ModelAndView exception02(Exception ex){
ModelAndView ok = new ModelAndView("ok");
ok.addObject("msg",ex.getMessage());
return ok;
}
}
5. 拦截器
主要作用:拦截用户的请求,进行相应的预处理和后处理
如何实现:实现HandlerInterceptor接口,重写其方法
public class MyInterceptor implements HandlerInterceptor {
//在处理器方法执行之前执行,返回值为true,放行拦截的方法,
//将afterCompletion方法放入一个专门的方法栈等待
//返回值为false,被拦截的方法则不执行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return false;
}
//该方法在处理器方法执行之后,返回结果之前,该方法可以改变处理器方法的处理结果和跳转的页面
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
//preHandle方法返回true时,在处理器方法返回结果之后,执行该方法
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
配置拦截器
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean id="myInterceptor" class="com.dmlll.controller.MyInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
编写拦截器代码
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("预处理-----------");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("处理中-----------");
System.out.println("替换处理器存储的msg");
request.setAttribute("msg","你被替换了");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("后处理-----------");
}
}
编写处理器
@RequestMapping("get17.do")
public String get17(HttpServletRequest request) {
System.out.println("处理器方法执行存储");
request.setAttribute("msg","我是1");
return "ok";
}
6.文件上传和下载
6.1.文件上传
SpringMVC为文件上传提供了直接支持,即通过MultipartResolver实现
Spring中有一个实现类CommonsMultipartResolver
在SpringMVC上下文中默认没有装配MultipartResolver,需要先在配置文件中配置
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>
引入依赖
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
编辑页面进行文件上传
<form action="/params/get18.do/id" method="post" enctype="multipart/form-data">
文件:<input type="file" name="file"><br/>
<button type="submit">提交</button>
</form>
编写处理器
@RequestMapping("get18.do")
public String get18(@RequestParam("file") MultipartFile file,HttpServletRequest request) throws IOException {
String originalFilename = file.getOriginalFilename();//原始文件名
//获取后缀
String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
//组成随机名称
String filename = UUID.randomUUID().toString().replace("-","")+suffix;
request.setAttribute("filename",filename);
//存储路径
String path = request.getServletContext().getRealPath("/upload") + "/";
file.transferTo(new File(path,filename));
System.out.println("上传成功");
return "ok";
}
新建upload文件夹
修改ok.jsp
<img src="upload/${requestScope.filename}">
运行:
6.2 下载
@RequestMapping("get19.do")
public ResponseEntity get19(HttpServletRequest request) throws IOException {
//指定路径
String path = request.getServletContext().getRealPath("/upload")+"/566e00bb19a8430e85beeafd4c27697a.jpg";
//创建响应的头信息
HttpHeaders headers = new HttpHeaders();
//以流的形式打开
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
//以附件形式响应给用户
headers.setContentDispositionFormData("attachment", URLEncoder.encode("566e00bb19a8430e85beeafd4c27697a.jpg","utf-8"));
File file = new File(path);
ResponseEntity responseEntity = new ResponseEntity(FileUtils.readFileToByteArray(file),headers, HttpStatus.CREATED);
return responseEntity;
}
<img src="/upload/566e00bb19a8430e85beeafd4c27697a.jpg">
<a href="/params/get19.do">下载</a>