Spring MVC 是 Spring 提供的一个基于 MVC 设计模式的轻量级 Web 开发框架,本质上相当于 Servlet。
Spring MVC 是结构最清晰的 Servlet+JSP+JavaBean 的实现,是一个典型的教科书式的 MVC 构架,不像 Struts 等其它框架都是变种或者不是完全基于 MVC 系统的框架。
Spring MVC配置
Spring MVC 是基于 Servlet 的,DispatcherServlet 是整个 Spring MVC 框架的核心,主要负责截获请求并将其分派给相应的处理器处理。所以配置 Spring MVC,首先要定义 DispatcherServlet。跟所有 Servlet 一样,用户必须在 web.xml 中进行配置。
1)定义DispatcherServlet
在开发 Spring MVC 应用时需要在 web.xml 中部署 DispatcherServlet,代码如下:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <display-name>springMVC</display-name> <!-- 部署 DispatcherServlet --> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 表示容器再启动时立即加载servlet --> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <!-- 处理所有URL --> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
Spring MVC 初始化时将在应用程序的 WEB-INF 目录下查找配置文件,该配置文件的命名规则是“servletName-servlet.xml”,例如 springmvc-servlet.xml。
也可以将 Spring MVC 的配置文件存放在应用程序目录中的任何地方,但需要使用 servlet 的 init-param 元素加载配置文件,通过 contextConfigLocation 参数来指定 Spring MVC 配置文件的位置,示例代码如下。
<!-- 部署 DispatcherServlet --> <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-servlet.xml</param-value> </init-param> <!-- 表示容器再启动时立即加载servlet --> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
此处使用 Spring 资源路径的方式进行指定,即 classpath:springmvc-servlet.xml
。
上述代码配置了一个名为“springmvc”的 Servlet。该 Servlet 是 DispatcherServlet 类型,它就是 Spring MVC 的入口,并通过 <load-on-startup>1</load-on-startup>
配置标记容器在启动时就加载此 DispatcherServlet,即自动启动。然后通过 servlet-mapping 映射到“/”,即 DispatcherServlet 需要截获并处理该项目的所有 URL 请求。
2)创建Spring MVC配置文件
在 WEB-INF 目录下创建 springmvc-servlet.xml 文件,如下所示。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" 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"> <!-- LoginController控制器类,映射到"/login" --> <bean name="/login" class="net.biancheng.controller.LoginController"/> <!-- LoginController控制器类,映射到"/register" --> <bean name="/register" class="net.biancheng.controller.RegisterController"/> </beans>
3. 创建Controller
在 src 目录下创建 net.biancheng.controller 包,并在该包中创建 RegisterController 和 LoginController 两个传统风格的控制器类(实现 Controller 接口),分别处理首页中“注册”和“登录”超链接的请求。
Controller 是控制器接口,接口中只有一个方法 handleRequest,用于处理请求和返回 ModelAndView。
RegisterController 的具体代码如下。
package controller; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.Controller; public class LoginController implements Controller { public ModelAndView handleRequest(HttpServletRequest arg0, HttpServletResponse arg1) throws Exception { return new ModelAndView("/WEB-INF/jsp/register.jsp"); } }
LoginController 的具体代码如下。
package controller; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.Controller; public class RegisterController implements Controller { public ModelAndView handleRequest(HttpServletRequest arg0, HttpServletResponse arg1) throws Exception { return new ModelAndView("/WEB-INF/jsp/login.jsp"); } }
4. 创建View
index.jsp 代码如下。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> 未注册的用户,请 <a href="${pageContext.request.contextPath }/register"> 注册</a>! <br /> 已注册的用户,去 <a href="${pageContext.request.contextPath }/login"> 登录</a>! </body> </html>
在 WEB-INF 下创建 jsp 文件夹,将 login.jsp 和 register.jsp 放到 jsp 文件夹下。login.jsp 代码如下。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> 登录页面! </body> </html>
register.jsp 代码如下。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> <body> 注册页面! </body> </html> </head>
将 springmvcDemo 项目部署到 Tomcat 服务器,首先访问 index.jsp 页面,如下图所示。
Controller及RequestMapping注解
Controller注解
@Controller 注解用于声明某类的实例是一个控制器。例如,在 net.biancheng.controller 包中创建控制器类 IndexController,示例代码如下。
package net.biancheng.controller; import org.springframework.stereotype.Controller; @Controller public class IndexController { // 处理请求的方法 }
Spring MVC 使用扫描机制找到应用中所有基于注解的控制器类,所以,为了让控制器类被 Spring MVC 框架扫描到,需要在配置文件中声明 spring-context,并使用 <context:component-scan/>
元素指定控制器类的基本包(请确保所有控制器类都在基本包及其子包下)。
例如,在 springmvcDemo 应用的配置文件 springmvc-servlet.xml 中添加以下代码:
<!-- 使用扫描机制扫描控制器类,控制器类都在net.biancheng.controller包及其子包下 --><context:component-scan base-package="net.biancheng.controller" />
RequestMapping注解
一个控制器内有多个处理请求的方法,如 UserController 里通常有增加用户、修改用户信息、删除指定用户、根据条件获取用户列表等。每个方法负责不同的请求操作,而 @RequestMapping 就负责将请求映射到对应的控制器方法上。
在基于注解的控制器类中可以为每个请求编写对应的处理方法。使用 @RequestMapping 注解将请求与处理方法一 一对应即可。
@RequestMapping 注解可用于类或方法上。用于类上,表示类中的所有响应请求的方法都以该地址作为父路径。
@RequestMapping 注解常用属性如下。
1. value 属性
value 属性是 @RequestMapping 注解的默认属性,因此如果只有 value 属性时,可以省略该属性名,如果有其它属性,则必须写上 value 属性名称。如下。
@RequestMapping(value="toUser")@RequestMapping("toUser")
value 属性支持通配符匹配,如 @RequestMapping(value="toUser/*") 表示 http://localhost:8080/toUser/1 或 http://localhost:8080/toUser/hahaha 都能够正常访问。
2. path属性
path 属性和 value 属性都用来作为映射使用。即 @RequestMapping(value="toUser") 和 @RequestMapping(path="toUser") 都能访问 toUser() 方法。
path 属性支持通配符匹配,如 @RequestMapping(path="toUser/*") 表示 http://localhost:8080/toUser/1 或 http://localhost:8080/toUser/hahaha 都能够正常访问。
3. name属性
name属性相当于方法的注释,使方法更易理解。如 @RequestMapping(value = "toUser",name = "获取用户信息")。
4. method属性
method 属性用于表示该方法支持哪些 HTTP 请求。如果省略 method 属性,则说明该方法支持全部的 HTTP 请求。
@RequestMapping(value = "toUser",method = RequestMethod.GET) 表示该方法只支持 GET 请求。也可指定多个 HTTP 请求,如 @RequestMapping(value = "toUser",method = {RequestMethod.GET,RequestMethod.POST}),说明该方法同时支持 GET 和 POST 请求。
5. params属性
params 属性用于指定请求中规定的参数,代码如下。
@RequestMapping(value = "toUser",params = "type")public String toUser() { return "showUser";}
以上代码表示请求中必须包含 type 参数时才能执行该请求。即 http://localhost:8080/toUser?type=xxx 能够正常访问 toUser() 方法,而 http://localhost:8080/toUser 则不能正常访问 toUser() 方法。
@RequestMapping(value = "toUser",params = "type=1")public String toUser() { return "showUser";}
以上代码表示请求中必须包含 type 参数,且 type 参数为 1 时才能够执行该请求。即 http://localhost:8080/toUser?type=1 能够正常访问 toUser() 方法,而 http://localhost:8080/toUser?type=2 则不能正常访问 toUser() 方法。
6. header属性
header 属性表示请求中必须包含某些指定的 header 值。
@RequestMapping(value = "toUser",headers = "Referer=http://www.xxx.com") 表示请求的 header 中必须包含了指定的“Referer”请求头,以及值为“http://www.xxx.com”时,才能执行该请求。
7. consumers属性
consumers 属性用于指定处理请求的提交内容类型(Content-Type),例如:application/json、text/html。如 @RequestMapping(value = "toUser",consumes = "application/json")。
8. produces属性
produces 属性用于指定返回的内容类型,返回的内容类型必须是 request 请求头(Accept)中所包含的类型。如 @RequestMapping(value = "toUser",produces = "application/json")。
除此之外,produces 属性还可以指定返回值的编码。如 @RequestMapping(value = "toUser",produces = "application/json,charset=utf-8"),表示返回 utf-8 编码。
使用 @RequestMapping 来完成映射,具体包括 4 个方面的信息项:请求 URL、请求参数、请求方法和请求头。
重定向和转发
Spring MVC 请求方式分为转发、重定向 2 种,分别使用 forward 和 redirect 关键字在 controller 层进行处理。
在 Spring MVC 框架中,控制器类中处理方法的 return 语句默认就是转发实现,只不过实现的是转发到视图。示例代码如下:
@RequestMapping("/register") public String register() { return "register"; //转发到register.jsp }
在 Spring MVC 框架中,重定向与转发的示例代码如下:
package net.biancheng.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("/index") public class IndexController { @RequestMapping("/login") public String login() { //转发到一个请求方法(同一个控制器类可以省略/index/) return "forward:/index/isLogin"; } @RequestMapping("/isLogin") public String isLogin() { //重定向到一个请求方法 return "redirect:/index/isRegister"; } @RequestMapping("/isRegister") public String isRegister() { //转发到一个视图 return "register"; } }
拦截器
在 Spring MVC 框架中定义一个拦截器需要对拦截器进行定义和配置,主要有以下 2 种方式。
-
通过实现 HandlerInterceptor 接口或继承 HandlerInterceptor 接口的实现类(例如 HandlerInterceptorAdapter)来定义;
-
通过实现 WebRequestInterceptor 接口或继承 WebRequestInterceptor 接口的实现类来定义。
配置:
<!-- 配置拦截器 --> <mvc:interceptors> <!-- 配置一个全局拦截器,拦截所有请求 --> <bean class="net.biancheng.interceptor.TestInterceptor" /> <mvc:interceptor> <!-- 配置拦截器作用的路径 --> <mvc:mapping path="/**" /> <!-- 配置不需要拦截作用的路径 --> <mvc:exclude-mapping path="" /> <!-- 定义<mvc:interceptor>元素中,表示匹配指定路径的请求才进行拦截 --> <bean class="net.biancheng.interceptor.Interceptor1" /> </mvc:interceptor> <mvc:interceptor> <!-- 配置拦截器作用的路径 --> <mvc:mapping path="/gotoTest" /> <!-- 定义在<mvc:interceptor>元素中,表示匹配指定路径的请求才进行拦截 --> <bean class="net.biancheng.interceptor.Interceptor2" /> </mvc:interceptor> </mvc:interceptors>
在上述示例代码中,元素说明如下。
-
mvc:interceptors:该元素用于配置一组拦截器。
-
<bean>:该元素是 mvc:interceptors 的子元素,用于定义全局拦截器,即拦截所有的请求。
-
mvc:interceptor:该元素用于定义指定路径的拦截器。
-
mvc:mapping:该元素是 mvc:interceptor 的子元素,用于配置拦截器作用的路径,该路径在其属性 path 中定义。path 的属性值为
/**
时,表示拦截所有路径,值为/gotoTest
时,表示拦截所有以/gotoTest
结尾的路径。如果在请求路径中包含不需要拦截的内容,可以通过 mvc:exclude-mapping 子元素进行配置。
文件上传
Spring MVC 框架的文件上传基于 commons-fileupload 组件,并在该组件上做了进一步的封装,简化了文件上传的代码实现,取消了不同上传组件上的编程差异
在 Spring MVC 中实现文件上传十分容易,它为文件上传提供了直接支持,即 MultpartiResolver 接口。MultipartResolver 用于处理上传请求,将上传请求包装成可以直接获取文件的数据,从而方便操作。
MultpartiResolver 接口有以下两个实现类:
-
StandardServletMultipartResolver:使用了 Servlet 3.0 标准的上传方式。
-
CommonsMultipartResolver:使用了 Apache 的 commons-fileupload 来完成具体的上传操作。
Maven 项目在 pom.xml 文件中添加以下依赖。
纯文本复制 <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> </dependency> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.2.2</version> </dependency>
文件下载
Spring MVC 文件下载的实现方法和实现过程;文件下载有以下两种实现方法:
-
通过超链接实现下载:实现简单,但暴露了下载文件的真实位置,并且只能下载 Web 应用程序所在目录下的文件,WEB-INF 目录除外。
-
利用程序编码实现下载:增强安全访问控制,可以下载除 Web 应用程序所在目录以外的文件,也可以将文件保存到数据库中。
利用程序编码实现下载需要设置以下两个报头:
-
Web 服务器需要告诉浏览器其所输出内容的类型不是普通文本文件或 HTML 文件,而是一个要保存到本地的下载文件,这需要设置 Content-Type 的值为 application/x-msdownload。
-
Web 服务器希望浏览器不直接处理相应的实体内容,而是由用户选择将相应的实体内容保存到一个文件中,这需要设置 Content-Disposition 报头。
该报头指定了接收程序处理数据内容的方式,在 HTTP 应用中只有 attachment 是标准方式,attachment 表示要求用户干预。在 attachment 后面还可以指定 filename 参数,该参数是服务器建议浏览器将实体内容保存到文件中的文件名称。
设置报头的示例如下:
response.setHeader("Content-Type", "application/x-msdownload"); response.setHeader("Content-Disposition", "attachment;filename="+filename);
/** * 执行下载 */ @RequestMapping("down") public String down(@RequestParam String filename, HttpServletRequest request, HttpServletResponse response) { String aFilePath = null; // 要下载的文件路径 FileInputStream in = null; // 输入流 ServletOutputStream out = null; // 输出流 try { // 从workspace\.metadata\.plugins\org.eclipse.wst.server.core\ // tmp0\wtpwebapps下载 aFilePath = request.getServletContext().getRealPath("uploadfiles"); // 设置下载文件使用的报头 response.setHeader("Content-Type", "application/x-msdownload"); response.setHeader("Content-Disposition", "attachment; filename=" + toUTF8String(filename)); // 读入文件 in = new FileInputStream(aFilePath + "\\" + filename); // 得到响应对象的输出流,用于向客户端输出二进制数据 out = response.getOutputStream(); out.flush(); int aRead = 0; byte b[] = new byte[1024]; while ((aRead = in.read(b)) != -1 & in != null) { out.write(b, 0, aRead); } out.flush(); in.close(); out.close(); } catch (Throwable e) { e.printStackTrace(); } logger.info("下载成功"); return null; }