1. 概述
- Spring 为展现层提供的基于 MVC 设计理念的优秀的 Web 框架,是目前最主流的 MVC 框架之一。
- Spring3.0 后全面超越 Struts2,成为最优秀的 MVC 框架。
- Spring MVC 通过一套 MVC 注解,让 POJO 成为处理请求的控制器,而无须实现任何接口。
- 支持 REST 风格的 URL 请求。
- 采用了松散耦合可插拔组件结构,比其他 MVC 框架更具扩展性和灵活性。
1.1 HelloWorld例子
- 配 置 DispatcherServlet :DispatcherServlet 默 认 加 载
/WEB- INF/<servletName-servlet>.xml
的 Spring 配置文件, 启动 WEB 层的 Spring 容器。可以通过 contextConfigLocation 初始化参数自定义配置文件的位置和名称。
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext-mvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
配置applicationContext-mvc.xml
- 配置自动扫描的包
<context:component-scan base-package="com.chen.demo"/>
- 配置视图解析器:把controller方法返回值解析为实际的地址
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/view/"/> <property name="suffix" value=".jsp"/> </bean>
创建处理请求的controller
@Controller public class HelloWorld { @RequestMapping("/hello") public String hello(){ System.out.println("hello world!"); return "success"; } }
2. 使用 @RequestMapping 映射请求
2.1 概述
- Spring MVC 使用 @RequestMapping 注解为控制器指定可以处理哪些 URL 请求。
- 在控制器的类定义及方法定义处都可标注
@RequestMapping
- 类定义处:提供初步的请求映射信息。相对于 WEB 应用的根目录
- 方法处:提供进一步的细分映射信息;相对于类定义处的 URL。若类定义处未标注
@RequestMapping
,则方法处标记的 URL 相对于 WEB 应用的根目录- DispatcherServlet 截获请求后,就通过控制器上
@RequestMapping
提供的映射信息确定请求所对应的处理方法。
- controller代码如下
@RequestMapping("/springMVC")
@Controller
public class RequestMappingDemo {
private static final String SUCCESS = "success";
@RequestMapping("/testRequestMapping")
public String testRequestMapping() {
System.out.println("testRequestMapping");
return SUCCESS;
}
}
2.2. 请求方式、请求头和请求参数
@RequestMapping
除了可以使用请求 URL 映射请求外,还可以使用请求方法、请求参数及请求头映射请求。@RequestMapping
的value
、method
、params
及headers
分别表示请求 URL、请求方法、请求参数及请求头的映射条件,他们之间是与的关系,联合使用多个条件可让请求映射更加精确化。- params 和 headers支持简单的表达式
- param1: 表示请求必须包含名为 param1 的请求参数。
- !param1: 表示请求不能包含名为 param1 的请求参数。
- param1 != value1: 表示请求包含名为 param1 的请求参数,但其值不能为 value1。
- {“param1 = value1”,”param2”}: 请求必须包含名为 param1 和param2 的两个请求参数,且 param1 参数的值必须为 value1。
- 映射请求方式的方法
@RequestMapping(value = "/testMethod", method = RequestMethod.POST)
public String testMethod() {
System.out.println("testMethod");
return SUCCESS;
}
- 映射请求头和请求参数的方法
@RequestMapping(value = "/testParamsAndHeaders", params = {"userName", "age!=10"},
headers = {"Accept-Language=zh-CN,zh;q=0.8"})
public String testParamsAndHeaders() {
System.out.println("testParamsAndHeaders");
return SUCCESS;
}
2.3. @RequestMapping 支持 Ant 风格通配符
- Ant 风格资源地址支持 3 种匹配符:
?
:匹配文件名中的一个字符*
:匹配文件名中的任意字符**
:**
匹配多层路径
- 处理方法代码
@RequestMapping("/testAntPath/*/abc")
public String testAntPath() {
System.out.println("testAntPath");
return SUCCESS;
}
2.4. @PathVariable 映射 URL 绑定的占位符
- 带占位符的 URL 是 Spring3.0 新增的功能,该功能在 SpringMVC 向 REST 风格目标挺进发展过程中具有里程碑的意义
- 通过 @PathVariable 可以将 URL 中占位符参数绑定到控制器处理方法的入参中:URL 中的 {xxx} 占位符可以通过 @PathVariable(“xxx”) 绑定到操作方法的入参中。
- 处理方法代码
@RequestMapping("/testPathVariable/{id}")
public String testPathVariable(@PathVariable(value = "id") Integer id){
System.out.println("testPathVariable id:"+id);
return SUCCESS;
}
3. REST
3.1. 概述
REST:即 Representational State Transfer。(资源)表现层状态转化。是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。
资源(Resources):网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的存在。可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的
URI 。要获取这个资源,访问它的URI就可以,因此 URI即为每一个资源的独一无二的识别符。表现层(Representation):把资源具体呈现出来的形式,叫做它的表现层 (Representation)。比如,文本可以用 txt 格式表现,也可以用 HTML 格式、XML 格式、JSON格式表现,甚至可以采用二进制格式。
状态转化(State Transfer):每发出一个请求,就代表了客户端和服务器的一次交互过程。HTTP协议,是一个无状态协议,即所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生” 状态转化”(State Transfer)。而这种转化是建立在表现层之上的,所以就是 “表现层状态转化”。具体说,就是
3.2. HiddenHttpMethodFilter
浏览器 form 表单只支持 GET 与 POST 请求,而 DELETE、PUT 等 method 并不支持,Spring3.0 添加了一个过滤器,可以将这些请求转换为标准的 http 方法,使得支持 GET、POST、PUT 与DELETE 请求。
在web.xml中配置
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
3.3. 发送GET、POST、PUT和DELETE请求
- 1.配置HiddenHttpMethodFilter。
- 2.发送POST请求。
- 3.需要在发送POST请求时携带一个name为
_method
的隐藏域,value为PUT
或DELETE
,如<input type="hidden" name="_method" value="PUT"/>
。
- 处理方法代码
@RequestMapping(value = "/testRest/{id}",method = RequestMethod.GET)
public String testRestGET(@PathVariable Integer id){
System.out.println("testRest GET : "+id);
return SUCCESS;
}
@RequestMapping(value = "/testRest",method = RequestMethod.POST)
public String testRestPOST(){
System.out.println("testRest POST ");
return SUCCESS;
}
@RequestMapping(value = "/testRest/{id}",method = RequestMethod.PUT)
public String testRestPut(@PathVariable Integer id){
System.out.println("testRest PUT : "+id);
return SUCCESS;
}
@RequestMapping(value = "/testRest/{id}",method = RequestMethod.DELETE)
public String testRestDelete(@PathVariable Integer id){
System.out.println("testRest DELETE : "+id);
return SUCCESS;
}
- Jsp 代码
<%--GET--%>
<a href="${pageContext.request.contextPath}/springMVC/testRest/1">test REST GET</a>
<br/>
<%--POST--%>
<form action="${pageContext.request.contextPath}/springMVC/testRest" method="post">
<input type="submit" value="test Rest POST">
</form>
<br/>
<%--PUT--%>
<form action="${pageContext.request.contextPath}/springMVC/testRest/1" method="post">
<input type="hidden" name="_method" value="PUT"/>
<input type="submit" value="test Rest PUT">
</form>
<br/>
<%--DELETE--%>
<form action="${pageContext.request.contextPath}/springMVC/testRest/1" method="post">
<input type="hidden" name="_method" value="DELETE"/>
<input type="submit" value="test Rest DELETE">
</form>
4. 将请求信息绑定到处理方法的相应入参中
4.1. 使用@RequestParam 绑定请求参数
- 在处理方法入参处使用 @RequestParam 可以把请求参数传递给请求方法
- value:参数名
- required:是否必须。默认为 true, 表示请求参数中必须包含对应的参数,若不存在,将抛出异常
- 处理方法代码
@RequestMapping(value = "/testRequestParam")
public String testRequestParam(@RequestParam(value = "userName")String userName,
@RequestParam(value = "age",required = false,defaultValue ="0")int age){
System.out.println("testRequestParam userName = "+userName+" age = "+age);
return SUCCESS;
}
4.2. 使用 @RequestHeader 绑定请求头的属性值
请求头包含了若干个属性,服务器可据此获知客户端的信息,通过 @RequestHeader 即可将请求头中的属性值绑定到处理方法的入参中
处理方法代码
@RequestMapping(value = "/testRequestHeader")
public String testRequestHeader(@RequestHeader(value = "Accept-Language")String al){
System.out.println("testRequestHeader Accept-Language = "+al);
return SUCCESS;
}
4.3. @CookieValue 绑定请求中的Cookie值
@CookieValue可以让处理方法入参绑定某个Cookie值
处理方法代码
@RequestMapping("/testCookieValue")
public String testCookieValue(@CookieValue("JSESSIONID")String sessionId){
System.out.println("testCookieValue sessionId"+sessionId);
return SUCCESS;
}
5. 使用POJO对象绑定请求参数值
Spring MVC 会按请求参数名和 POJO 属性名进行自动匹配,自动为该对象填充属性值。支持级联属性。 如:dept.deptId、dept.address.tel 等。
POJO类 - User
public class User {
private int id;
private String userName;
private String password;
private String email;
private int age;
private Address address;
//省略setter、getter、constructor和toString
...
}
- POJO类 - Address
public class Address {
private String province;
private String city;
//省略setter、getter、constructor和toString
...
}
- JSP代码
<form action="${pageContext.request.contextPath}/springMVC/testPojo" method="post">
userName:<input name="userName" type="text"/><br/>
password:<input name="password" type="password"/><br/>
email:<input name="email" type="text"/><br/>
age:<input name="age" type="text"/><br/>
city:<input name="address.city" type="text"/><br/>
province:<input name="address.province" type="text"/><br/>
<input type="submit" value="Submit">
</form>
- 处理方法代码
@RequestMapping("/testPojo")
public String testPojo(User user){
System.out.println("testPojo "+user);
return SUCCESS;
}
6. 使用 Servlet API 作为入参
- MVC的handler方法支持 原生ServletAPI 作为目标方法的参数
- HttpServletRequest
- HttpServletResponse
- HttpSession
- java.security.Principal
- Locale
- InputStream
- OutputStream
- Reader
- Writer
- 处理方法代码
@RequestMapping("/testServletAPI")
public String testServletAPI(HttpServletRequest request,
HttpServletResponse response){
System.out.println("testServletAPI "+request+" , "+response);
return SUCCESS;
}
7. 处理模型数据
7.1. 概述
- Spring MVC 提供了以下几种途径输出模型数据:
- ModelAndView: 处理方法返回值类型为 ModelAndView时, 方法体即可通过该对象添加模型数据。
- Map 及 Model: 入参为
org.springframework.ui.Model
、org.springframework.ui.ModelMap
或
java.uti.Map
时,处理方法返回时,Map 中的数据会自动添加到模型中。- @SessionAttributes: 将模型中的某个属性暂存到HttpSession 中,以便多个请求之间可以共享这个属性。
- @ModelAttribute: 方法入参标注该注解后, 入参的对象就会放到数据模型中。
7.2. ModelAndView
- 控制器处理方法的返回值如果为 ModelAndView, 则其既包含视图信息,也包含模型数据信息。
- Spring MVC会把 ModelAndView 的 Model 中数据放到 request 域对象中。
- 处理方法代码
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){
String viewName = SUCCESS;
ModelAndView modelAndView = new ModelAndView(viewName);
//添加模型数据到ModelAndView中
modelAndView.addObject("time",new Date());
return modelAndView;
}
7.3. Map及Model
- 处理方法代码
@RequestMapping("/testMap")
public String testMap(Map<String,Object> map){
//org.springframework.validation.support.BindingAwareModelMap
System.out.println(map.getClass().getName());
map.put("names", Arrays.asList("Tom","Jerry","Mike"));
return SUCCESS;
}
7.4. @SessionAttribute
- 若希望在多个请求之间共用某个模型属性数据,则可以在控制器类上标注一个 @SessionAttributes, Spring MVC将在模型中对应的属性暂存到 HttpSession 中。
- @SessionAttributes 除了可以通过属性名指定需要放到会话中的属性外,还可以通过模型属性的类型指定哪些模型属性需要放到会话中。
- 控制器代码
@SessionAttributes(value = {"user"},types = {String.class})
@RequestMapping("/springMVC")
@Controller
public class RequestMappingDemo {
private static final String SUCCESS = "success";
@RequestMapping("/testSessionAttribute")
public String testSessionAttribute(Map<String,Object> map){
User user = new User(1,"Tom","123","123@qq.com",20);
//value = {"user"}
map.put("user",user);
//types = {String.class}
map.put("school","SWPU");
return SUCCESS;
}
}
7.5. ModelAttribute
- 在方法定义上使用 @ModelAttribute 注解:
- Spring MVC在调用目标处理方法前,会先调用在方法上标注了 @ModelAttribute 的方法。
- 在方法的入参前使用 @ModelAttribute 注解:
- Spring MVC 会使用 value 属性值在 implicitModel 中查找对应的对象,若存在则会直接传入到目标方法的入参中。
- Spring MVC 会以value 为 key,POJO类型的对象为 value ,存入到 request 中
@ModelAttribute修饰的方法
- 运行流程:
- i. 执行@ModelAttribute注解修饰的方法:从数据库中取出对象,把对象放入Map,键:user
- ii. SpringMVC从Map中取出User对象,并把表单的请求参数赋给该User对象的对应属性
- iii. SpringMVC把上述对象传入目标方法的参数。
- 注意:在@ModelAttribute修饰的方法中,放入到Map时的键需要和目标方法入参类型的第一个字母小写的字符串一致。
方法代码
@ModelAttribute public void getUser(@RequestParam(value = "id",required = false)Integer id,Map<String,Object> map){ if (id!=null){ //模拟从数据库中获取对象 User user = new User(1,"Tom","123","123@qq.com",20); System.out.println("从数据库中获取对象 "+user); map.put("user",user); } }
处理方法代码
@RequestMapping("/testModelAttribute") public String testModelAttribute(User user){ System.out.println("修改 :"+user); return SUCCESS; }
- 运行流程:
@ModelAttribute修饰的入参
此时将 @ModelAttribute 修饰的方法中map所放入的key改为abc
,那在入参中用@ModelAttribute("abc")User user
才能接收到。方法代码
@ModelAttribute public void getUser(@RequestParam(value = "id",required = false)Integer id,Map<String,Object> map){ if (id!=null){ User user = new User(1,"Tom","123","123@qq.com",20); System.out.println("从数据库中获取对象 "+user); //key修改为abc map.put("abc",user); } }
处理方法代码
@RequestMapping("/testModelAttribute") public String testModelAttribute(@ModelAttribute("abc")User user){ System.out.println("修改 :"+user); return SUCCESS; }
8. 视图和视图解析器
- 请求处理方法执行完成后,最终返回一个 ModelAndView 对象。对于那些返回 String,View 或 ModeMap 等类型的处理方法,Spring MVC 也会在内部将它们装配成一个 ModelAndView
对象,它包含了逻辑名和模型对象的视图。- Spring MVC 借助视图解析器(ViewResolver)得到最终的视图对象(View),最终的视图可以是 JSP
,也可能是Excel、JFreeChart 等各种表现形式的视图。- 对于最终究竟采取何种视图对象对模型数据进行渲染,处理器并不关心,处理器工作重点聚焦在生产模型数据的工作上,从而实现 MVC 的充分解耦。
8.1. 视图
视图的作用是渲染模型数据,将模型里的数据以某种形式呈现给客户。
为了实现视图模型和具体实现技术的解耦,Spring 在org.springframework.web.servlet 包中定义了一个高度抽象的 View 接口:
public interface View { String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus"; String PATH_VARIABLES = View.class.getName() + ".pathVariables"; String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType"; String getContentType(); void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception; }
视图对象由视图解析器负责实例化。由于视图是无状态的,所以他们 不会有线程安全的问题。
8.2. 视图解析器
SpringMVC 为逻辑视图名的解析提供了不同的策略,可以在 Spring WEB 上下文中配置一种或多种解析策略,并指定他们之间的先后顺序。每一种映射策略对应一个具体的视图解析器实现类。
视图解析器的作用比较单一:将逻辑视图解析为一个具体的视图对象。所有的视图解析器都必须实现 ViewResolver 接口:
package org.springframework.web.servlet; import java.util.Locale; public interface ViewResolver { View resolveViewName(String viewName, Locale locale) throws Exception; }
可以用一种视图解析器或混用多种视图解析器。
每个视图解析器都实现了 Ordered 接口并开放出一个 order 属性,可以通过 order 属性指定解析器的优先顺序,order 越小优先级越高。
Spring MVC 会按视图解析器顺序的优先顺序对逻辑视图名进行解 析,直到解析成功并返回视图对象,否则将抛出 ServletException 异 常。
8.2.1. InternalResourceViewResolver
JSP 是最常见的视图技术,可以使用 InternalResourceViewResolver 作为视图解析器:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/view/"/> <property name="suffix" value=".jsp"/> </bean>
若项目中使用了 JSTL,则 SpringMVC 会自动把视图由 InternalResourceView 转为 JstlView。
若使用 JSTL 的 fmt 标签则需要在 SpringMVC 的配置文件中配置国际化资源文件。
<!--配置国际化资源文件-->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="i18n"/>
</bean>
若希望直接响应通过 SpringMVC 渲染的页面,可以使用
mvc:view-controller
标签实现<!--配置直接转发的页面--> <!--可以直接响应转发的页面,不需经过handler的方法--> <mvc:view-controller path="/success" view-name="success"/> <!--通常都需要配置mvc:annotation-driven标签--> <mvc:annotation-driven/>
- 但同时会导致以前的访问出现404,此时配置
mvc:annotation-driven
标签
<!--通常都需要配置mvc:annotation-driven标签--> <mvc:annotation-driven/>
- 但同时会导致以前的访问出现404,此时配置
8.3. 自定义视图
8.3.1. 例子
- 自定义视图
@Component(value = "helloView")
public class HelloView implements View{
@Override
public String getContentType() {
return "test/html";
}
@Override
public void render(Map<String, ?> map, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
httpServletResponse.getWriter().print("Hello View,Time: "+ new Date());
}
}
- 配置 BeanNameViewResolver 视图解析器:使用视图的名字来解析视图
<!--通过order属性来定义视图解析器的优先级,order值越小优先级越高-->
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
<property name="order" value="100"/>
</bean>
- BeanNameViewResolver源代码
public class BeanNameViewResolver extends WebApplicationObjectSupport implements ViewResolver, Ordered {
private int order = Integer.MAX_VALUE; // default: same as non-Ordered
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
@Override
public View resolveViewName(String viewName, Locale locale) throws BeansException {
ApplicationContext context = getApplicationContext();
if (!context.containsBean(viewName)) {
if (logger.isDebugEnabled()) {
logger.debug("No matching bean found for view name '" + viewName + "'");
}
return null;
}
if (!context.isTypeMatch(viewName, View.class)) {
if (logger.isDebugEnabled()) {
logger.debug("Found matching bean for view name '" + viewName +
"' - to be ignored since it does not implement View");
}
return null;
}
return context.getBean(viewName, View.class);
}
}
- 可以看到
return context.getBean(viewName, View.class)
,当没有发生异常时,BeanNameViewResolver 会通过viewName从容器中得到bean,所以我们需要将自定义的视图放入容器中。 此时我们设定的BeanNameViewResolver 的order为100,InternalResourceViewResolver的可以通过源代码知道,其order是Integer.MAX_VALUE。
控制器定义处理方法
@RequestMapping("/testView")
public String testView(){
System.out.println("testView");
return "helloView";
}
8.3.2. 自定义Excel视图
若希望使用 Excel 展示数据列表,仅需要扩展 Spring MVC 提供的 AbstractExcel View 或 AbstractJExcel View 即可。实现 buildExcelDocument() 方法,在方法中使用模型数据对象构建 Excel
文档就可以了。AbstractExcelView 基于 POI API,而 AbstractJExcelView 是基于 JExcelAPI 的。
视图对象需要配置 IOC 容器中的一个 Bean,使用BeanNameViewResolver 作为视图解析器即可。
若希望直接在浏览器中直接下载 Excel 文档,则可以设置响应头 Content-Disposition 的值为attachment;filename=xxx.xls。
9. 重定向与转发
一般情况下,控制器方法返回字符串类型的值会被当成逻辑视图名处理。
如果返回的字符串中带 forward: 或 redirect: 前缀时,Spring MVC 会将 forward: 和 redirect: 当成指示符,其后的字符串作为 URL 来处理。
- redirect:success.jsp:会完成一个到 success.jsp 的重定向的操作
- forward:success.jsp:会完成一个到 success.jsp 的转发操作
在 InternalResourceViewResolver 所继承的父类 UrlBasedViewResolver 中定义了createView方法会对返回的字符串进行处理。
- 源代码如下
protected View createView(String viewName, Locale locale) throws Exception {
// If this resolver is not supposed to handle the given view,
// return null to pass on to the next resolver in the chain.
if (!canHandle(viewName, locale)) {
return null;
}
// Check for special "redirect:" prefix.
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
view.setHosts(getRedirectHosts());
return applyLifecycleMethods(viewName, view);
}
// Check for special "forward:" prefix.
if (viewName.startsWith(FORWARD_URL_PREFIX)) {
String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
return new InternalResourceView(forwardUrl);
}
// Else fall back to superclass implementation: calling loadView.
return super.createView(viewName, locale);
}
- 处理方法代码
@RequestMapping("/testRedirect")
public String testRedirect(){
System.out.println("testRedirect");
return "redirect:/index.jsp";
}