1. 概述
- SpringMVC 是 Spring 的 Web 模块。
- Spring 为展现层提供的基于 MVC 设计理念的优秀的 Web 框架,是目前最主流的 MVC 框架之一。
- Spring3.0 后全面超越 Struts2,成为最优秀的 MVC 框架。
- Spring MVC 通过一套 MVC 注解,让 POJO(Plain Old Java Object) 成为处理请求的控制器,而无须实现任何接口。
- 支持 REST 风格的 URL 请求。
- 采用了松散耦合可插拔组件结构,比其他 MVC 框架更具扩展性和灵活性。
2. HelloWorld
2.1 步骤
- 创建 Web 工程
- 导包
- SpringMVC 是 Spring 的 Web 模块;所有模块的运行都是依赖核心模块[IOC 模块]
commons-logging-1.1.3.jar // 日志 spring-aop-4.0.0.RELEASE.jar // 支持注解 // 核心 ↓ spring-beans-4.0.0.RELEASE.jar spring-context-4.0.0.RELEASE.jar spring-core-4.0.0.RELEASE.jar spring-expression-4.0.0.RELEASE.jar
- Web 模块
spring-web-4.0.0.RELEASE.jar spring-webmvc-4.0.0.RELEASE.jar
- SpringMVC 是 Spring 的 Web 模块;所有模块的运行都是依赖核心模块[IOC 模块]
- 写配置文件
- web.xml
<!-- SpringMVC 思想:有一个前端控制器能拦截所有请求,并智能派发 这个前端控制器是一个 Servlet,应该在 web.xml 中配置这个 Servlet 来拦截所有请求 --> <!-- The front controller of this Spring Web application, responsible for handling all application requests --> <servlet> <servlet-name>springDispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <!-- contextConfigLocation 指定 SpringMVC 配置文件的位置 --> <param-name>contextConfigLocation</param-name> <param-value>classpath:springMVC.xml</param-value> </init-param> <!-- Servlet 启动加载,原本是在第 1 次访问创建对象。设置该子标签后, Servlet 会在 Server 启动时创建对象。值越小,优先级越高,越先创建对象 --> <load-on-startup>1</load-on-startup> </servlet> <!-- Map all requests to the DispatcherServlet for handling --> <servlet-mapping> <servlet-name>springDispatcherServlet</servlet-name> <!-- '/*' 和 '/' 都是拦截所有请求。 但是 '/*' 范围更广,会拦截到 *.jsp 页面,一旦拦截,jsp 页面就无法显示。 '/' 也会拦截所有请求,但不会拦截 jsp。 --> <url-pattern>/</url-pattern> </servlet-mapping>
- springMVC.xml (创建 Spring Bean Configuration File,即 Web 层的 Spring 容器)
<!-- 扫描组件 --> <context:component-scan base-package="cn.edu.nuist"></context:component-scan> <!-- 配置一个视图解析器,能帮我们拼接页面地址 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/pages/"></property> <property name="suffix" value=".jsp"></property> </bean>
- web.xml
- 编写测试代码
- index.jsp
<a href="hello">HelloWorld!</a>
- success.jsp
<body>SUCCESS!</body>
- MyFirstController
@Controller // 告诉 SpringMVC 这是一个处理器,可以处理请求 public class MyFirstController { /* * '/' 代表从当前项目下开始,处理当前项目下的 hello 请求 */ @RequestMapping("/hello") public String myFirstRequest() { System.out.println("请求收到了,正在处理中..."); /* * [视图解析器] 会自动拼串: * <property name="prefix" value="/WEB-INF/pages/"> * <property name="suffix" value=".jsp"> * → /WEB-INF/pages/[方法返回值].jsp */ // return "/WEB-INF/pages/success.jsp"; return "success"; } }
- index.jsp
2.2 细节
2.2.1 运行流程
- 客户端点击超链接会发送
http://localhost:8080/21_SpringMVC/hello
请求 - 来到 tomcat 服务器,SpringMVC 的「前端控制器 DispatcherServlet」收到请求
- 查看请求地址和 @RequestMapping 标注的哪个匹配,来找使用哪个类的哪个方法来处理请求
- 前端控制器找到了 {目标处理器类&目标处理方法},直接利用反射执行目标方法
- 方法执行完成以后会有一个返回值,SpringMVC 认为这个返回值就是要去的页面地址
- 拿到方法返回值后,用「视图解析器」拼串得到完整的页面地址
- 拿到页面地址后,前端控制器帮我们转发到指定页面
2.2.2 @RequestMapping 简述
告诉 SpringMVC 这个方法用来处理什么请求。
@RequestMapping("/hello")
这个 '/' 可以省略,即使省略,也是默认从当前项目下开始。不过习惯加上。
2.2.3 配置文件的默认位置
可以看出,如果不指定配置文件位置,SpringMVC 默认会去找如下文件:
所以,若不想指定配置文件位置,就在 web 应用的 WEB-INF 目录下,指定配置文件名为 <servlet-name>-servlet.xml
即可。
2.2.4 核心控制器的 url-pattern
Tomcat Server 的 web.xml 中有一个 DefaultServlet,其 url-pattern 的配置是 /
,The default servlet for all web applications, that serves static resources. → DefaultServlet 就负责找到并返回静态资源。
「前端控制器」的 url-pattern 也配置为了 /
,这就相当于把 DefaultServlet 禁用了。此时,若再去访问一个静态资源(除去 Jsp 和 Servlet 外,其余都是静态资源)时,就会让「前端控制器」来处理该请求,而「前端控制器」就会去比较哪个方法的 @RequestMapping
与请求对应,肯定找不到,所以最终返回 404。
为什么 Jsp 能访问?
- 服务器的 web.xml 中还有一个 JspServlet,其 url-pattern 配置为
*.jsp
。The JSP page compiler and execution servlet, which is the mechanism used by Tomcat to support JSP pages. Traditionally, this servlet is mapped to the URL pattern "*.jsp".
- 因为我们项目自身 web.xml 中并没有覆盖服务器的 JspServlet 的配置;所以,JspServlet 能正常处理 Jsp 请求。
- 若把「前端控制器」的 url-pattern 配置为了
/*
,这时,连 Jsp 也访问不到了。
- 我们把项目的 url-pattern 配置为
/
也是为了迎合后来的 REST 风格的 URL 地址。
@RequestMapping
Spring MVC 使用 @RequestMapping 注解为控制器指定可以处理哪些 URL 请求。
我们把项目的 url-pattern 配置为 /
也是为了迎合后来的 REST 风格的 URL 地址。
3. @RequestMapping
Spring MVC 使用 @RequestMapping 注解为控制器指定可以处理哪些 URL 请求。
3.1 注解标注在类上
在控制器的类定义及方法定义处都可标注 @RequestMapping。
- 类定义处:提供初步的请求映射信息。相对于 WEB 应用的根目录
- 方法处:提供进一步的细分映射信息。相对于类定义处的 URL。若类定义处未标注 @RequestMapping,则方法处标记的 URL 相对于 WEB 应用的根目录
3.2 注解中的属性
标准的 HTTP 请求报头:
属性之间是与的关系,联合使用多个条件可让请求映射更加精确化。
3.2.1 映射请求路径
DispatcherServlet 截获请求后,就通过将请求 URL 与控制器上 @RequestMapping 注解的 value 属性值进行比对以此确定请求所对应的处理方法。
3.2.2 映射请求方法
public enum RequestMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
}
若不是按规定的方式请求:405 - Request method 'GET' not supported
3.2.3 映射请求参数
params 规定请求携带的请求参数,支持简单的表达式 (不按要求的直接 404),参数规则间用 ,
隔开。
params={"username"} 请求必须携带名为 username 的参数
params={"!username"} 请求不能携带名为 username 的参数
params={"username=admin"} 请求必须携带名为 username 且值为 admin 的参数
params={"username!=admin"} 请求必须携带名为 username 但值不能是 admin 的参数 // 不带该参数也OK
举例:{"param1=value1", "param2"}
请求必须包含名为 param1 和 param2 的两个请求参数,且 param1 参数的值必须为 value1。
3.2.4 映射请求头
headers 规定请求头,使用方式和 params 类似 (不按要求的也是直接 404)。
3.3 Ant 风格的 URL
Ant 风格资源地址支持 3 种匹配符:
? 匹配文件名中的一个字符(0个/多个都不行)
* 匹配文件名中的任意字符或者一层路径(0层/多层都不行)
** 匹配多层路径
@RequestMapping 还支持 Ant 风格的 URL
/user/*/createUser 匹配 /user/aaa/createUser、/user/bbb/createUser 等 URL
/user/**/createUser 匹配 /user/createUser、/user/aaa/bbb/createUser 等 URL
/user/createUser?? 匹配 /user/createUseraa、/user/createUserbb 等 URL
3.4 @PathVariable
带占位符的 URL 是 Spring3.0 新增的功能,该功能在 SpringMVC 向 REST 目标挺进发展过程中具有里程碑的意义。
通过 @PathVariable 可以将 URL 中占位符参数绑定到控制器处理方法的形参中:URL 中的 {xxx} 占位符可以通过 @PathVariable("xxx") 绑定到操作方法的形参中。
4. REST
4.1 REST 风格
- 简述
- 示例(URL 起名方式:
/资源名/资源标识符
)
- 【总结】URL 定位资源,用 HTTP 动词(GET,POST,DELETE,PUT)描述操作。
- 看 URL 就知道要什么
- 看 HTTP Method 就知道干什么
- 看 HTTP StatusCode 就知道结果如何
4.2 HiddenHttpMethodFilter
浏览器 form 表单只支持 GET 与 POST 请求,而 DELETE、PUT 等 method 并不支持,Spring3.0 添加了这个过滤器,可以将这些请求转换为标准的 HTTP 方法,使得支持 GET、POST、PUT 与 DELETE 请求。
public class HiddenHttpMethodFilter extends OncePerRequestFilter {
public static final String DEFAULT_METHOD_PARAM = "_method";
private String methodParam = DEFAULT_METHOD_PARAM;
public void setMethodParam(String methodParam) {
Assert.hasText(methodParam, "'methodParam' must not be empty");
this.methodParam = methodParam;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response
, FilterChain filterChain) throws ServletException, IOException {
String paramValue = request.getParameter(this.methodParam);
if ("POST".equals(request.getMethod()) && StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
HttpServletRequest wrapper = new HttpMethodRequestWrapper(request, method);
filterChain.doFilter(wrapper, response);
} else {
filterChain.doFilter(request, response);
}
}
private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
private final String method;
public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
super(request);
this.method = method;
}
@Override
public String getMethod() {
return this.method;
}
}
}
5. 请求处理
- Spring MVC 通过分析处理方法的签名,HTTP 请求信息绑定到处理方法的相应形参中。
- Spring MVC 对控制器处理方法签名的限制是很宽松的,几乎可以按喜欢的任何方式对方法进行签名。
- 必要时可以对方法及方法形参标注相应的注解(@PathVariable 、@RequestParam、@RequestHeader 等)
- Spring MVC 框架会将 HTTP 请求的信息绑定到相应的方法形参中,并根据方法的返回值类型做出相应的后续处理
5.1 @RequestParam
- 在处理方法形参处使用 @RequestParam 可以把请求参数传递给请求方法
- 注解属性
- value 参数名
- required 是否必须。默认为 true,表示请求参数中必须包含对应的参数;若不存在,将抛出异常
- defaultValue 默认值,当没有传递参数时使用该值
5.2 @RequestHeader
- 使用 @RequestHeader 绑定请求报头的属性值
- 请求头包含了若干个属性,服务器可据此获知客户端的信息,通过 @RequestHeader 即可将请求头中的属性值绑定到处理方法的形参中
- 注解属性同 @RequestParam
5.3 @CookieValue
使用 @CookieValue 绑定请求中的 Cookie 值到处理方法的某个形参。
5.4 POJO 作为参数
使用 POJO 对象绑定请求参数值时,Spring MVC 会按请求参数名和 POJO 属性名进行自动匹配,自动为该对象填充属性值。支持级联属性。如:dept.deptId、dept.address.tel 等。
5.5 ServletAPI 作为参数
MVC 的 Handler 方法可以接受如下 ServletAPI 类型的参数(源码参考:AnnotationMethodHandlerAdapter)
- HttpServletRequest
- HttpServletResponse
- HttpSession
- java.security.Principal
- Locale
- InputStream
- OutputStream
- Reader
- Writer
5.6 乱码问题
5.6.1 GET 乱码解决
5.6.2 POST 乱码解决
配置字符编码过滤器。但要注意:当有多个 Filter 时,一般都会将字符编码 Filter 置首!因为 CharacterEncodingFilter 必须要在第一次获取请求参数之前设置才有意义。
假设现在配了俩过滤器:HiddenHttpMethodFilter 和 CharacterEncodingFilter。如果先配置了 HiddenHttpMethodFilter,而其 doFilter()
中调用的 request.getParameter(this.methodParam)
会使得字符编码过滤器失效。所以,一般在 web.xml 中都会先配置字符编码过滤器。
public class CharacterEncodingFilter extends OncePerRequestFilter {
private String encoding;
// 是否强制 ServletResponse 的编码格式和 ServletRequest 的编码格式一样
private boolean forceEncoding = false; // false 就只是设置了 request 的编码格式
public void setEncoding(String encoding) {
this.encoding = encoding;
}
public void setForceEncoding(boolean forceEncoding) {
this.forceEncoding = forceEncoding;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
response, FilterChain filterChain) throws ServletException, IOException {
if (this.encoding != null && (this.forceEncoding
|| request.getCharacterEncoding() == null)) {
// 设置请求编码
request.setCharacterEncoding(this.encoding);
if (this.forceEncoding) {
// 设置响应编码
response.setCharacterEncoding(this.encoding);
}
}
filterChain.doFilter(request, response);
}
}