笔记为个人学习过程中整理,如有错误,欢迎指正
ResponseStatusExceptionResolver 34
DefaultHandlerExceptionResolver 35
SimpleMappingExceptionResolver 35
第一讲 HelloWorld
- 步骤:
- 加入jar包
第一个jar包需要单独去找
commons-logging-1.2.jar
下面的jar都在spring的libs文件夹下
spring-aop-5.0.0.RELEASE.jar
spring-beans-5.0.0.RELEASE.jar
spring-context-5.0.0.RELEASE.jar
spring-core-5.0.0.RELEASE.jar
spring-expression-5.0.0.RELEASE.jar
spring-web-5.0.0.RELEASE.jar
spring-webmvc-5.0.0.RELEASE.jar
- 在web.xml文件中配置DispatcherServlet
- 加入SpringMVC的适配文件
- 编写处理请求的处理器,并标识为处理器
- 编写视图
SpringMVC环境配置
<?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"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<!-- 配置 DispatcherServlet -->
<!-- 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>
<!-- 配置 DispatcherServlet 的一个初始化参数:配置 SpringMVC 配置文件的名称和位置 -->
<!-- 实际上也可以不通过 contextConfigLocation 来配置SpringMVC的配置文件,而使用默认的 默认的配置文件为
/WEB-INF/<servlet-name>-servlet.xml -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Map all requests to the DispatcherServlet for handling -->
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<!-- 设置应答的请求,此处设置为应答所有请求 -->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="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-4.2.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd">
<!-- 配置自定扫描的包 -->
<context:component-scan base-package="springmvc"></context:component-scan>
<!-- 配置视图解析器:如何把 handler 方法返回值解析为实际的物理视图 -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
</beans>
配置完成截图
package springmvc.handlers;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class HelloWorld {
/**
* 1. 使用 @RequestMapping 的注解来映射请求的 URL
* 2. 返回值会通过视图解析器解析为实际的物理视图,对于 InternalResourceViewResolver 视图解析器,会做出如下解析:
* 通过 prefix + returnVal + suffix 这样的方式得到实际的物理视图,然后做转发操作
* @return
*/
@RequestMapping("/hello")
public String hello() {
System.out.println("hello world");
return "success";
}
}
第二讲 部分常用注解
使用@RequestMapping映射请求
SpringMVC使用@RequestMapping注解为控制器指定可以处理哪些URL请求
在控制器的类定义及方法定义处都可以标注
@RequestMapping
- 类定义处:提供初步的请求映射信息。相对于WEB应用的根目录
- 方法处:提供进一步的细分映射信息。相对于类定义处的URL。若类定义处未标注@RequestMapping,则方法处标记的URL相对于WEB应用的根目录
DispatcherServlet截获请求后,就通过控制器上@RequestMapping提供的映射信息确定请求所对应的处理方法。
例:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest {
private static final String SUCCESS = "success";
@RequestMapping("/testRequestMapping")
public String testRequestMapping() {
System.out.println("testRequestMapping");
return SUCCESS;
}
}
此处就必须通过WEB应用名/springmvc/testRequestMapping才能执行SpringMVCTest方法
@RequestMapping除了可以使用请求URL映射请求外,还可以使用请求方法、请求参数及请求头映射请求
@RequestMapping的value、method、params及heads分别表示请求URL、请求方法、请求参数及请求头的映射条件,他们之间是“与”的关系,联合使用多个条件可以让请求映射更加精确化
params和headers支持简单的表达式:
- param1:表示请求必须包含名为param1的请求参数
- !param1:表示请求不能包含名为param1的请求参数
- param1 != value1:表示请求包含名为param1的请求参数,但其值不能为value1
- {"param1=value1","param2"}:请求必须包含名为param1和param2的两个请求参数,且param1参数的值必须为value1
例:
/* 使用 method属性指定请求方式,此处指定为post,
* 则所有get方式传过来的数据不会执行此方法,会显示Request method 'GET' not supported
*/
@RequestMapping(value="/testMethod", method=RequestMethod.POST)
public String testMethod() {
System.out.println("testMethod");
return SUCCESS;
}
/* HTTP请求的消息头的Accept-Language
* 属性值必须为zh-CN,zh;q=0.9才可以进入执行此方法
*/
@RequestMapping(value = "testParamsAndHeaders", params = { "username","age!=10" }, headers = { "Accept-Language=zh-CN,zh;q=0.9" })
public String testParamsAndHeaders() {
System.out.println("testParamsAndHeaders");
return SUCCESS;
}
使用@RequestMapping映射时支持通配符,不过通配符需要使用Ant风格资源地址支持3种匹配符:
- ?:匹配文件名中的一个字符
- *:匹配文件名中的任意字符
- **:**匹配多层路径
@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
@PathVariable映射URL绑定的占位符
通过@PathVariable可以将URL中占位符参数绑定到控制器处理方法的传入参数中:URL中的{xxx}占位符可以通过@PathVariable("xxx")绑定到操作方法的传入参数中
jsp中链接:
<a href="springmvc/testPathVariable/143">Test PathVariable</a>
响应类中:
@RequestMapping("/testPathVariable/{id}")
public String testPathVariable(@PathVariable("id") Integer id) {
System.out.println("testPathVariable:" + id);
return SUCCESS;
}
此时会输出143
REST技术
REST:即Representational State Transfer。(资源) 表现层状态转化。是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便。所以正得到越来越多网站的采用
- 资源( Resources):网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的存在。可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的URI。要获取这个资源,访问它的URI就可以,因此URI 即为每一个资源的独一无二的识别符。
- 表现层(Representation):把资源具体呈现出来的形式,叫做它的表现层(Representation )。比如,文本可以用txt 格式表现,也可以用HTML 格式XML 格式、JSON 格式表现,甚至可以采用二进制格式。
- 状态转化(State Transfer):每发出一个请求,就代表了客户端和服务器的一次交互过程。HTTP协议,是一个无状态协议,即所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生“状态转化”(State Transfer)。而这种转化是建立在表现层之上的,所以就是“表现层状态转化”。具体说,就是HTTP 协议里面,四个表示操作方式的动词: GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE 用来删除资源。
示例:
- /order/1 HTTP GET:得到id = 1的order
- /order/1 HTTP DELETE:删除id = 1的order
- /order/1 HTTP PUT:更新id = 1的order
- /order HTTP POST:新增order
HiddenHttpMethodFilter:浏览器form 表单只支持GET 与POST 请求,而DELETE、PUT等method并不支持,Spring3.0 添加了一个过滤器,可以将这些请求转换为标准的http方法,使得支持GET、POST、PUT与DELETE 请求。
需要用到REST时的准备工作就是要在web.xml中进行配置:
<!-- 配置org.springframework.web.filter.HiddenHttpMethodFilter:可以把POST请求转化为DELETE或POST请求 -->
<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>
- 需要发送POST请求
- 需要在发送POST请求时携带一个name="_method"的隐藏域,value为"PUT"或"DELETE"
- 要获得id值使用 @PathVariable("id") 注解即可
例:
<!-- 测试REST的GET请求 -->
<a href="springmvc/testRest/1">Test Rest Get</a>
<br><br>
<!-- 测试REST的POST请求 -->
<form action="springmvc/testRest" method="post">
<input type="submit" value="TestRest:POST">
</form>
<br><br>
<!-- 测试REST的DELETE请求 -->
<form action="springmvc/testRest/54" method="post">
<input type="hidden" name="_method" value="DELETE">
<input type="submit" value="TestRest:DELETE">
</form>
<br><br>
<!-- 测试REST的PUT请求 -->
<form action="springmvc/testRest/12" method="post">
<input type="hidden" name="_method" value="PUT">
<input type="submit" value="TestRest:PUT">
</form>
响应类中:
@RequestMapping(value="/testRest/{id}", method=RequestMethod.PUT)
public String testRestPut(@PathVariable("id") Integer id) {
System.out.println("testRest PUT:"+id);
return SUCCESS;
}
@RequestMapping(value="/testRest/{id}", method=RequestMethod.DELETE)
public String testRestDelete(@PathVariable("id") Integer id) {
System.out.println("testRest DELETE:"+id);
return SUCCESS;
}
@RequestMapping(value="/testRest", method=RequestMethod.POST)
public String testRest() {
System.out.println("testRest POST");
return SUCCESS;
}
@RequestMapping(value="/testRest/{id}", method=RequestMethod.GET)
public String testRest(@PathVariable("id") Integer id) {
System.out.println("testRest GET:"+id);
return SUCCESS;
}
@RequestParam注解
该注解可以用于绑定请求参数值。
jsp中链接:
<a href="springmvc/testRequestParam?username=freedom&age=11">Test
RequestParam</a>
响应类:
@RequestMapping(value = "/testRequestParam")
public String testRequestParam(
// 设置了age为非必须的,当传参数没有传入age时,age默认为0 @RequestParam(value = "username") String un,
@RequestParam(value = "age", required = false, defaultValue = "0") int age) {
System.out.println("testRequestParam:username:" + un + ", age:" + age);
// 输出testRequestParam:username:freedom, age:11
return SUCCESS;
}
@RequestHeader注解
使用该注解可以用于绑定请求头的属性值,其中属性及用法同@RequestParam注解
@RequestMapping("/testRequestHeader")
public String testRequestHeader(
@RequestHeader(value = "Accept-Language") String al) {
System.out.println("testRequestHeader:Accept-Language:" + al);
// 输出 testRequestHeader:Accept-Language:zh-CN,zh;q=0.9
return SUCCESS;
}
@CookieValue注解
使用@CookieValue可以绑定请求中的Cookie值,让处理方法入参绑定某个Cookie值,其中属性及用法同@RequestParam注解
@RequestMapping("/testCookieValue")
public String testCookieValue(@CookieValue("JSESSIONID") String sessionId) {
System.out.println("testCookieValue:sessionId:"+sessionId);
// 输出 testCookieValue:sessionId:42F24A5E6C3FA2508AAF6F363E64BFD8,因为JSESSIONID的Cookie值为这么多
return SUCCESS;
}
使用POJO作为参数
SpringMVC会按请求参数名和POJO属性名进行自动匹配,自动为该对象填充属性值,支持级联属性。如:dept.deptId、dept.address.tel等
例:
在JSP页面中的表单内容如下
<form action="springmvc/testPojo" method="post">
username:<input type="text" name="username">
<br>
password:<input type="password" name="password">
<br>
email:<input type="text" name="email">
<br>
age:<input type="text" name="age">
<br>
city:<input type="text" name="address.city">
<br>
province:<input type="text" name="address.province">
<br>
<input type="submit" value="Submit">
</form>
在网页上的表单中填写如下内容
之后提交后,在响应类中
@RequestMapping("/testPojo")
public String testPojo(User user) {
System.out.println("testPojo:" + user);
/*
* 输出testPojo:User [username=4343, password=mypassword,
* email=975@qq.com, age=45, address=Address
* [province=sichuan, city=mianyang]]
*/
return SUCCESS;
}
使用Servlet API作为入参
Spring可以使用Servlet原生的API作为目标方法的参数,MVC的Handler可以接受如下Servlet API类型的参数
- HttpServletRequest
- HttpServletResponse
- HttpSession
- java.security.Principal
- Locale
- InputStream
- OutputStream
- Reader
- Writer
@RequestMapping("/testServletAPI")
public void testServletAPI(HttpServletRequest request,
HttpServletResponse response, Writer out) throws IOException{
System.out.println("testServletAPI:" + request + ", " + response);
out.write("hello Springmvc");
}
访问的结果为:
第三讲 SpringMVC处理模型数据
SpringMVC提供了以下几种途径输出模型数据:
- ModelAndView:处理方法返回值类型为ModelAndView时,方法体即可通过该对象添加模型数据
- Map及Model:入参为org.springframework.ui.Model、org.springframework.ui.ModelMap或java.util.Map时,处理方法返回时,Map中的数据会自动添加到模型中
- @SessionAttributes:将模型中的某个属性暂存到HttpSession中,以便多个请求之间可以共享这个属性
- @ModelAttribute:方法入参标注该注解后,入参的对象就会放到数据模型中
ModelAndView
控制器处理方法的返回值如果为ModelAndView,则其既包含视图信息,也包含模型数据信息。
- 添加模型数据:
- ModelAndView addObject(String attributeName, Object attributeValue)
- ModelAndView addAllObject(Map<String, ?> modelMap)
- 设置视图:
- void setView(View view)
- void setViewName(String viewName)
SpringMVC会把ModelAndView的model中的数据放入到request域对象中
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView() {
String viewName=SUCCESS;
ModelAndView modelAndView=new ModelAndView(viewName);
//添加模型数据到ModelAndView中
modelAndView.addObject("time", new Date());
return modelAndView;
}
在将要跳转到的jsp页面上这么写
time: ${requestScope.time}
则会在网页上显示当前时间
Map
SpringMVC在内部使用了一个org.springframework.ui.Model接口存储模型数据
- 具体步骤
- SpringMVC在调用方法前会创建一个隐含的模型对象作为模型数据的存储容器
- 如果方法的入参为Map或Model类型,SpringMVC会将隐含模型的引用传递给这些入参。在方法体内,开发者可以通过这个入参对象访问到模型中的所有数据,也可以向模型中添加新的属性数据
@RequestMapping("/testMap")
public String testMap(Map<String, Object> map) {
map.put("names", Arrays.asList("Sony", "Jerry", "Angra"));
return SUCCESS;
}
在将要跳转到的jsp页面上这么写
names:${requestScope.names}
则在网页上会显示names:[Sony, Jerry, Angra]
SessionAttributes注解
若希望在多个请求之间共用某个模型属性数据,则可以在控制器类上标注一个@SessionAttributes,SpringMVC将在模型中对应的属性暂存到HttpSession中
@SessionAttributes除了可以通过属性名指定需要放到会话中的属性外,还可以通过模型属性的对象类型指定哪些模型属性需要放到会话中
- @SessionAttributes(types-User.class)会将隐含模型中所有类型为User.class的属性添加到会话中
- @SessionAttributes(value={"user1", "user2"})
- @SessionAttributes(types={User.class, Dept.class})
- @SessionAttributes(value={"user1", "user2"}, types={Dept.class})
/*
* 在 testSessionAttributes 方法体中将 user 存入 Map 的同时,
* 因为与此处的user名字一样,所以也会在 session 中保存
* 同理所有存入 map 的类型为 String 的变量都会存入session
*/
@SessionAttributes(value = { "user" }, types = { String.class })
@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest {
private static final String SUCCESS = "success";
@RequestMapping("/testSessionAttributes")
public String testSessionAttributes(Map<String, Object> map){
User user = new User("Angra", "MyPass123", "97535@qq.com", 27);
/*
* 在将 user 存入 Map 的同时,因为与SessionAttributes
* 属性中的名字一样,所以也会在 session 中保存
*/
map.put("user", user);
/*
* 在将 ownSchool 存入 Map 的同时,因为其为String类型
* 所以也会在 session 中保存
*/
map.put("school", "ownSchool");
return SUCCESS;
}
}
第四讲 ModelAttribute注解
有@ModelAttribute标记的方法,会在每个目标方法执行之前被SpringMVC调用
运行流程:
- 执行@ModelAttribute注解修饰的方法:从数据库中取出对象,并把对象放入到Map中,键为user
- SpringMVC从Map中取出User对象,并且把表单的请求参数赋给该User对象的对应属性
- SpringMVC把上述对象传入目标方法的参数
注意:在@ModelAttribute修饰的方法中,放入Map时的键需要和目标方法入参类型的第一个字母小写的字符串一致
源代码分析的流程
- 调用@ModelAttribute注解修饰的方法,实际上把@ModelAttribute方法中Map中的数据放在了implicitModel中
- 解析请求处理的目标参数,实际上该目标参数来自于WebDataBinder对象的target属性
- 创建WebDataBinder对象:
- 确定objectName属性:若传入的attrName属性值为"",则objectName为类名第一个字母小写
- 确定target属性:
- 在implicitModel中查找attrName对应的属性值,若存在,则ok;若不存在,则验证当前Handler是否使用了@SessionAttributes进行修饰,若使用了,则尝试从Session中获取attrName所对应的属性值。若session中没有对应的属性值,则会抛出异常。若Handler没有使用@SessionAttributes进行修饰,或者@SessionAttributes中没有使用value值指定的key和attrName相匹配,则通过反射创建了POJO对象
- SpringMVC把表单的请求参数赋给了WebDataBinder的target 对应的属性
- *SpringMVC会把WebDataBinder的attrName和target给到 implicitModel,进而传到request域对象中
- 把WebDataBinder的target作为参数传递给目标方法的入参
- SpringMVC确定目标方法POJO类型入参的过程:
- 确定一个key
-
- 若目标方法的POJO类型的参数没有使用@ModelAttribute作为修饰,则key为POJO类名第一个字母的小写
- 若使用了@ModelAttribute来修饰,则key为@ModelAttribute注解的value属性值
-
- 在implicitModel中查找key对应的对象,若存在,则作为入参传入
若在@ModelAttribute标记的方法中Map中保存过,且key和1确定的key一直,则一定会获取到
- 若implicitModel中不存在key对应的对象,则检查当前的Handler是否使用@SessionAttributes注解修饰,若使用了该注解,且@SessionAttributes注解的value属性值中包含了key,则会从HttpSession中来获取key所对应的value值,若存在则直接传入到目标方法的入参中,若不存在则将抛出异常
- 若Handler没有标识@SessionAttributes注解或@SessionAttributes注解的value值中不包含key,则会通过反射来创建POJO类型的参数,传入为目标方法的参数
- SpringMVC会把key和value保存到implicitModel中,进而会保存到request中
第五讲 视图
JstlView
若项目中使用了JSTL,则SpringMVC会自动把视图由Internal
在JSP中使用JSTL标签需要在文件头加入如下语句:
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
使用国际化语言的标签:
<fmt:message key="i18n.username"></fmt:message>
配置国际化资源文件需要在springmvc.xml文件中写以下代码
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="i18n"></property>
</bean>
使用mvc:view-controller标签可以直接响应转发的页面,而无需再经过Handler的方法
<mvc:view-controller path="/success" view-name="success"/>
但是如果不进行其他配置其他转向success的页面就会404,在开发中还需要配置mvc:annotation-driven,直接写如下语句就行了
<mvc:annotation-driven></mvc:annotation-driven>
自定义视图
若希望使用Excel 展示数据列表,仅需要扩展SpringMVC 提供的AbstractExcelView 或AbstractJExcel View 即可。实现buildExcelDocument(
方法,在方法中使用模型数据对象构建Excel 文档就可以了。
- AbstractExcelView 基于POI API,而Abstract ExcelView 是基于JExcelAPI 的。
- 视图对象需要配置IOC 容器中的一个Bean,使用BeanNameViewResolver作为视图解析器即可
- 若希望直接在浏览器中直接下载Excel 文档,则可以设置响应头Content-Disposition 的值为attachment;filename=xxx.xIs
- springmvc.xml文件中的配置:
<!-- 配置视图BeanNameViewResolver解析器:使用视图的名字来解析视图 -->
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
<!-- 通过order属性来定义视图解析器的优先级,order值越小优先级越高 -->
<property name="order" value="100"></property>
</bean>
- handler中内容
@RequestMapping("/testView")
public String testView() {
System.out.println("testView");
return "helloView";
}
- view中的内容
@Component
public class HelloView implements View {
@Override
public String getContentType() {
return "text/html";
}
@Override
public void render(Map<String, ?> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
response.getWriter().print("hello view,time:" + new Date());
}
}
重定向
一般情况下,控制器方法返回字符串类型的值会被当成逻辑视图名处理
如果返回的字符串中带forward: 或redirect: 前缀时,SpringMVC 会对他们进行特殊处理:将forward: 和redirect: 当成指示符,其后的字符串作为URL 来处理
- redirect:success.jsp: 会完成一个到success.jsp 的重定向的操作
- forward:success.jsp: 会完成一个到success.jsp 的转发操作
例:
@RequestMapping("/testRedirect")
public String testRedirect() {
System.out.println("testRedirect");
return "redirect:/index.jsp";
}
第六讲 RESTful SpringMVC CRUD
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
这是SpringMVC的form标签,其可以更快速的开发出表单页面,而且可以更方便的进行表单值的回显,支持级联操作
可以通过modelAttribute属性指定绑定的模型属性,若没有指定该属性,则默认从request域对象中读取command的表单bean,如果该属性值也不存在,则会发生错误,意思就是SpringMVC认为表单一定会进行回显,即便你不需要回显也需要这样做,可以传入一个new的对象没有任何属性值的,以达到没有回显的效果
<form:form action="emp" method="POST" modelAttribute="employee">
<!-- path属性对应html的form标签里的name属性值 -->
LastName:<form:input path="lastName" />
<br>
Email:<form:input path="email" />
<br>
<%
Map<String, String> genders = new HashMap<String, String>();
genders.put("1", "Male");
genders.put("0", "Female");
request.setAttribute("genders", genders);
%>
Gender:<form:radiobuttons path="gender" items="${genders}" />
<br>
Department:<form:select path="department" items="${departments}" itemLabel="departmentName" itemValue="id"></form:select>
<input type="submit" value="Submit">
</form:form>
- form:input、form:password、form:hidden、form:textarea:对应HTML表单的text、password、hidden、textarea 标签
- form:radiobutton:单选框组件标签,当表单bean 对应的属性值和value 值相等时,单选框被选中
- form:radiobuttons: 单选框组标签,用于构造多个单选框
- items:可以是一个List、String[]或Map
- itemValue:指定radio 的value 值。可以是集合中bean 的一个属性值
- itemLabel:指定radio 的label 值
- delimiter:多个单选框可以通过delimiter 指定分隔符
- form:checkbox:复选框组件。用于构造单个复选框
- form:checkboxs:用于构造多个复选框。使用方式同form:radiobuttons标签
- form:select:用于构造下拉框组件。使用方式同form:radiobuttons标签
- form:option:下拉框选项组件标签。使用方式同form:radiobuttons标签
- form:errors:显示表单组件或数据校验所对应的错误
- <form:errors path= "*" />:显示表单所有的错误
- <form:errors path= "user*" />:显示所有以user为前缀的属性对应的错误
- <form:errors path="username" />:显示特定表单对象属性的错误
SpringMVC处理静态资源
优雅的REST风格的资源URL不希望带.html或.do等后缀
若将DispatcherServlet请求映射配置为/,则SpringMVC将捕获WEB容器的所有请求,包括静态资源的请求,SpringMVC会将他们当成一个普通请求处理,因找不到对应处理器将导致错误。
可以在SpringMVC的配置文件中配置<mvc:default-servlet-handler/>的方式解决静态资源的问题:
- <mvc:default-servlet-handler/>将在SpringMVC上下文中定义一个DefaultServletHttpRequestHandler,它会对进入DispatcherServlet的请求进行筛查,如果发现是没有经过映射的请求,就将该请求交由WEB 应用服务器默认的Servlet处理,如果不是静态资源的请求,才由DispatcherServlet继续处理
- 一般WEB 应用服务器默认的Servlet的名称都是default。若所使用的WEB服务器的默认Servlet名称不是default,则需要通过default-servlet-name属性显式指定
- 有时还需要写<mvc:annotation-driven><mvc:annotation-driven/>才能好用
普通的链接是使用GET传输的,要使用REST风格必须是POST传输,此时我们要将链接改成POST传输,这个需要使用JavaScript和jQuery技术实现,具体过程百度
对于_method不能使用form:hidden标签,因为modelAttribute对应的bean中没有_method这个属性。
<form:form action="${pageContext.request.contextPath}/emp" method="POST" modelAttribute="employee"></form:form>
action前的${pageContext.request.contextPath}表示绝对路径,防止跳转页面出错
数据绑定流程
- SpringMVC主框架将ServletRequest对象及目标方法的入参实例传递给WebDataBinderFactory实例,以创建DataBinder实例对象
- DataBinder调用装配在SpringMVC上下文中的ConversionService组件进行数据类型转换、数据格式化工作。将Servlet中的请求信息填充到入参对象中
- 调用Validator组件对已经绑定了请求消息的入参对象进行数据合法性校验,并最终生成数据绑定结果BindingData对象
- SpringMVC抽取BindingResult中的入参对象和校验错误对象,将它们赋给处理方法的响应入参
自定义类型转换器
Spring定义了3种类型的转换器接口,实现任意一个转换器接口都可以作为自定义转换器注册到ConversionServiceFactoryBean中:
- Converter<S,T>:将S类型对象转为T类型对象
- ConverterFactory:将相同系列多个“同质”Converter 封装在一起。如果希望将一种类型的对象转换为另一种类型及其子类的对象(例如将String转换为Number及Number子类(Integer、Long、Double等)对象)可使用该转换器工厂类
- GenericConverter:会根据源类对象及目标类对象所在的宿主类中的上下文信息进行类型转换
在网页中我们按照lastName-email-gender-department.id的规则输入一个字符串,以记录employee的信息(此处只是为了试验便捷,所以用一个文本框输入全部信息之后进行操作,如果写多个选框代表不同信息操作方式是一样的)
要保存employee成为Employee类型
此时是不能直接保存的,因为网页和控制器没有自动转换,要将String类型转变为Employee类型,这是转换器:
转换器编辑完成后还需要对其进行配置在springmvc.xml中配置
mvc:annotation-driven
<mvc:annotation-driven></mvc:annotation-driven>会自动注册RequestMappingHandlerMapping、RequestMappingHandlerAdapter与ExceptionHandlerExceptionResolver三个bean。
还将提供以下支持:
- 支持使用ConversionService实例对表单参数进行类型转换
- 支持使用@NumberFormatannotation、@DateTimeFormat注解完成数据类型的格式化
- 支持使用@Valid注解对JavaBean实例进行JSR 303验证
- 支持使用@RequestBody和@ResponseBody注解
开发的时候最好加上这个配置
@InitBinder注解
由@InitBinder标识的方法,可以对WebDataBinder对象进行初始化。WebDataBinder是DataBinder的子类,用于完成由表单字段到JavaBean属性的绑定
@InitBinder方法不能有返回值,它必须声明为void
@InitBinder方法的参数通常是是WebDataBinder
/**
* 不自动绑定对象中的roleSet 属性,另行处理。
*/
@InitBinder
public void initBinder(WebDataBinder dataBinder){
dataBinder.setDisallowedFields("roleSet");
}
数据格式化
首先需要配置<mvc:annotation-driven></mvc:annotation-driven>,之后在类型上加对应的注解即可
这样按照pattern中的格式输入文本框则会进行正确转换
FormattingConversionServiceFactoryBean内部已经注册了:
- NumberFormatAnnotationFormatterFactory:支持对数字类型的属性使用@NumberFormat注解
- JodaDateTimeFormatAnnotationFormatterFactory:支持对日期类型的属性使用@DateTimeFormat 注解
装配了FormattingConversionServiceFactoryBean后,就可以在SpringMVC入参绑定及模型数据输出时使用注解驱动了。<mvc:annotation-driven></mvc:annotation-driven>默认创建的ConversionService实例即为FormattingConversionServiceFactoryBean
<mvc:default-servlet-handler />
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
<!-- 配置ConversionService -->
<bean id="conversionService"
class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<ref bean="employeeConverter" />
</set>
</property>
</bean>
这样配置就能既可以添加自定义的类型转换器,又可以使用SpringMVC给我们提供的格式化工具
如果转换类型出错,错误信息会被存进BindingResult里,可以在函数入参时传入BindingResult,如果出错则其getErrorCount()必大于零,用法如下:
@RequestMapping(value = "/emp", method = RequestMethod.POST)
public String save(Employee employee, BindingResult result) {
System.out.println("save:" + employee);
if (result.getErrorCount() > 0) {
System.out.println("出错了!");
for (FieldError error : result.getFieldErrors()) {
System.out.println(error.getField() + ":"
+ error.getDefaultMessage());
}
}
employeeDao.save(employee);
return "redirect:/emps";
}
第七讲 JSR303数据校验
JSR 303是Java为Bean数据合法性校验提供的标准框架,它已经包含在JavaEE 6.0中
JSR 303通过在Bean属性上标注类似于@NotNull、@Max等标准的注解指定校验规则,并通过标准的验证接口对Bean进行验证
注解 | 功能说明 |
@Null | 被注释的元素必须为null |
@NotNull | 被注释的元素必须不为null |
@AssertTrue | 被注释的元素必须为true |
@AssertFalse | 被注释的元素必须为false |
@Min(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@Max(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@DecimalMin(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@DecimalMax(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@size(max, min) | 被注释的元素的大小必须在指定的范围内 |
@Digits(integer, fraction) | 被注释的元素必须是一个数字,其值必须在可接受的范围内 |
@Past | 被注释的元素必须是一个过去的日期 |
@Future | 被注释的元素必须是一个将来的日期 |
@Pattern(value) | 被注释的元素必须符合指定的正则表达式 |
Hibernate Validator扩展注解 | |
| 被注释的元素必须是电子邮箱地址 |
@Length | 被注释的字符串的大小必须在指定的范围内 |
@NotEmpty | 被注释的字符串必须非空 |
@Range | 被注释的元素必须在合适的范围内 |
Spring4.0拥有自己独立的数据校验框架,同时支持JSR303标准的校验框架。
Spring在进行数据绑定时,可同时调用校验框架完成数据校验工作。在SpringMVC中,可直接通过注解驱动的方式进行数据校验。
Spring的LocalValidatorFactoryBean既实现了Spring的Validator接口,也实现了JSR303的Validator接口。只要在Spring容器中定义了一个 LocalValidatorFactoryBean,即可将其注入到需要数据校验的Bean中。
Spring本身并没有提供JSR303的实现,所以必须将JSR303的实现者的jar包(hibernate-validator-5.0.0.CR2中都有)放到类路径下。
<mvc:annotation-driven/>会默认装配好一个LocalValidatorFactoryBean,通过在处理方法的入参上标注@valid注解即可让SpringMVC在完成数据绑定后执行数据校验的工作
在已经标注了JSR303注解的表单/命令对象前标注一个@Valid,SpringMVC框架在将请求参数绑定到该入参对象后,就会调用校验框架根据注解声明的校验规则实施校验
SpringMVC是通过对处理方法签名的规约来保存校验结果的:前一个表单/命令对象的校验结果保存到随后的入参中,这个保存校验结果的入参必须是BindingResult或Errors类型,这两个类都位于org.springframework.validation包中
需要校验的Bean对象和其绑定结果对象或错误对象是成对出现的,它们之间不允许声明其他的入参
Errors接口提供了获取错误信息的方法,如getErrorCount()或getFieldErrors(String field)
BindingResult扩展了Errors接口
提示消息的国际化
每个属性在数据绑定和数据校验发生错误时,都会生成一个对应的FieldError对象。当一个属性校验失败后,校验框架会为该属性生成4个消息代码,这些代码以校验注解类名为前缀,结合modleAttribute、属性名及属性类型名生成多个对应的消息代码:例如User类中的password属性标准了一个@Pattern注解,当该属性值不满足@Pattern所定义的规则时,就会产生以下4个错误代码:
- Pattern.user.password
- Pattern.password
- Pattern.java.lang.String
- Pattern
当使用SpringMVC标签显示错误消息时,SpringMVC会查看WEB上下文是否装配了对应的国际化消息,如果没有,则显示默认的错误消息,否则使用国际化消息。
例:
最开始的NotEmpty、Email和Past都是用于验证的注解,后面的employee.lastName等都是要验证的变量
处理JSON
1.首先需要单独的jar包,有三个jar包,
2.编写目标方法,使其返回JSON对应的对象或集合
3.在需要用到JSON技术的方法前加@ResponseBody注解即可,如:
HttpMessageConverter<T>的工作原理
HttpMessageConverter的使用
使用HttpMessageConverter<T>将请求信息转化并绑定到处理方法的入参中或将响应结果转为对应类型的响应信息,Spring提供了两种途径:
- 使用@RequestBody/@ResponseBody对处理方法进行标注
- 使用HttpEntity<T>/ResponseEntity<T>作为处理方法的入参或返回值
当控制器处理方法使用到@RequestBody/@ResponseBody或HttpEntity<T>/ResponseEntity<T>时,Spring首先根据请求头或响应头的Accept属性选择匹配的HttpMessageConverter,进而根据参数类型或泛型类型的过滤得到匹配的HttpMessageConverter,若找不到可用的HttpMessageConverter将报错
@RequestBody和@ResponseBody不需要成对出现
文件下载
@RequestMapping("/testResponseEntity")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException {
byte[] body=null;
ServletContext servletContext=session.getServletContext();
InputStream in=servletContext.getResourceAsStream("/files/aaa.txt");
body=new byte[in.available()];
in.read(body);
HttpHeaders headers=new HttpHeaders();
headers.add("Content-Disposition", "attachment;filename=aaa.txt");
HttpStatus statusCode=HttpStatus.OK;
ResponseEntity<byte[]> response=new ResponseEntity<byte[]>(body, headers, statusCode);
return response;
}
第八讲 国际化与文件上传
- 在页面上能够根据浏览器语言设置的情况对文本(不是内容),时间,数值进行本地化处理。
- 可以在bean中获取国际化资源文件Locale对应的消息
- 可以通过超链接切换Locale,而不再依赖于浏览器的语言设置情况
解决:
- 使用JSTL的fmt标签
- 在bean中注入ResourceBundleMessageSource的实例,使用其对应的getMessage方法即可
- 配置LocalResolver和LocaleChangeInterceptor
在JSP中使用JSTL标签需要在文件头加入如下语句:
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
使用国际化语言的标签:
<fmt:message key="i18n.username"></fmt:message>
配置国际化资源文件需要在springmvc.xml文件中写以下代码
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="i18n"></property>
</bean>
使用mvc:view-controller标签可以直接响应转发的页面,而无需再经过Handler的方法
<mvc:view-controller path="/success" view-name="success"/>
如何在Bean里获取locale对应的消息
@RequestMapping("/i18n")
public String testI18n(Locale locale){
String val=messageSource.getMessage("i18n.user", null, locale);
System.out.println(val);
return "i18n";
}
这样输出的val就是国际化后当前locale对应的信息了
如何通过超链接切换Locale
首先在springmvc.xml文件中需要配置
<!-- 配置SessionLocalResolver -->
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"></bean>
<!-- 配置LocaleChanceInterceptor -->
<mvc:interceptors>
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"></bean>
</mvc:interceptors>
之后在超链接中带上地区对应的locale参数即可,如:
<a href="i18n?locale=zh_CH">中文</a>
<a href="i18n?locale=en_US">English</a>
文件上传
SpringMVC为文件上传提供了直接的支持,这种支持是通过即插即用的MultipartResolver实现的。Spring用Jakarta Commons FileUpload技术实现了一个MultipartResolver实现类:CommonsMultipartResolver
SpringMVC上下文中默认没有装配MultipartResolver,因此默认情况下不能处理文件的上传工作,如果想使用Spring的文件上传功能,需现在上下文中配置MultipartResolver
defaultEncoding:必须和用户JSP的pageEncoding属性一致,以便正确解析表单的内容
为了让CommonsMultipartResolver正确工作,必须先将Jakarta Commons FileUpload及Jakarta Commons io的类包添加到类路径下
例:
springmvc.xml中的配置文件
<!-- 配置MultipartResolver -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8"></property>
<property name="maxUploadSize" value="10240000"></property>
</bean>
在Handler中的执行语句
@RequestMapping("/testFileUpload")
public String testFileUpload(@RequestParam("desc") String desc,
@RequestParam("file") MultipartFile file) throws IOException{
System.out.println("desc:" + desc);
System.out.println("OriginalFilename:" + file.getOriginalFilename());
System.out.println("InputStream:" + file.getInputStream());
return "success";
}
这样就能获得上传的file的信息了,但是此处并未进行存储!
第九讲 拦截器
自定义拦截器
SpringMVC也可以使用拦截器对请求进行拦截处理,用户可以自定义拦截器来实现特定的功能,自定义的拦截器必须实现HandlerInterceptor接口
- preHandle():这个方法在业务处理器处理请求之前被调用,在该方法中对用户请求request进行处理。如果程序员决定该拦截器对请求进行拦截处理后还要调用其他的拦截器,或者是业务处理器去进行处理,则返回true;如果程序员决定不需要再调用其他的组件去处理请求,则返回false。
- postHandle():这个方法在业务处理器处理完请求后,但是DispatcherServlet向客户端返回响应前被调用,在该方法中对用户请求request进行处理。
- afterCompletion():这个方法在DispatcherServlet完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作。
在springmvc.xml文件中的配置
<mvc:interceptors>
<!-- 配置自定义的拦截器 -->
<bean class="springmvc.interceptors.FirstInterceptor"></bean>
</mvc:interceptors>
在<mvc:interceptors>里面这样指定适用路径精确配置拦截器,注意这个是没有s的
<mvc:interceptor>
<mvc:mapping path="/i18n"/>
<!-- 配置自定义的拦截器 -->
<bean class="springmvc.interceptors.FirstInterceptor"></bean>
</mvc:interceptor>
自定义的拦截器类要怎么写:
public class FirstInterceptor implements HandlerInterceptor {
/**
* 渲染视图之后调用,可用于释放资源
*/
@Override
public void afterCompletion(HttpServletRequest arg0,
HttpServletResponse arg1, Object arg2, Exception arg3)
throws Exception {
System.out.println("[FirstInterceptor] afterCompletion"); }
/**
* 调用目标方法之后,但在渲染之前
* 可以对请求域中的视图进行修改
*/
@Override
public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1,
Object arg2, ModelAndView arg3) throws Exception {
System.out.println("[FirstInterceptor] postHandle");
}
@Override
public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1,
Object arg2) throws Exception {
System.out.println("[FirstInterceptor] preHandle");
return true;
}
}
注意:preHandler方法的返回值必须是true才会执行postHandler和afterCompletion,但是在实现HandlerInterceptor接口时默认返回false,可以用于设置权限
拦截器的执行顺序
当配置了多个拦截器时,preHandle按配置的正序进行,postHandler和afterCompletion方法按配置的反序进行
当SecondInterceptor的preHandler返回false时,则只执行两个的preHandle和First的afterCompletion三个方法,其他方法全部跳过
第十讲 异常处理
SpringMVC通过HandlerExceptionResolver处理程序的异常,包括Handler映射、数据绑定以及目标方法执行时发生的异常。
SpringMVC提供的HandlerExceptionResolver的实现类
DispatcherServlet默认装配的HandlerExceptionResolver:
- 没有使用<mvc:annotation-driven/>配置:
- 使用了<mvc:annotation-driven/>配置:
ExceptionHandler注解
- 主要处理Handler中用@ExceptionHandler注解定义的方法。
- @ExceptionHandler注解定义的方法优先级问题:例如发生的是NullPointerException但是声明的异常有RuntimeException和Exception,此后会根据异常的最近继承关系找到继承深度最浅的那个@ExceptionHandler注解方法,即标记了RuntimeException的方法
- ExceptionHandlerMethodResolver内部若找不到@ExceptionHandler注解的话,会找@ControllerAdvice中的@ExceptionHandler注解方法
注意事项:
- 在@ExceptionHandler方法的入参中可以加入Exception类型的参数,该参数可以对应发生的异常对象
- @ExceptionHandler方法的入参中不能传入Map,若希望把异常信息传到页面上,需要使用ModelAndView作为返回值
- @ExceptionHandler方法标记的异常有优先级的问题
- @ControllerAdvice:如果在当前Handler找不到@ExceptionHandler标记的方法来处理当前方法出现的异常,则会去@ControllerAdvice标记的类中查找@ExceptionHandler标记的方法来处理异常,处理规则与之前的相同
例:
@ControllerAdvice
public class HandlerException {
/**
* 标记的是数学异常
*/
@ExceptionHandler({ArithmeticException.class})
public ModelAndView handleArithmeticException(Exception ex) {
System.out.println("---出现异常");
ModelAndView mv=new ModelAndView("error");
mv.addObject("exception", ex);
return mv;
}
/**
* 标记的是运行异常,范围较大
*/
@ExceptionHandler({RuntimeException.class})
public ModelAndView handleRuntimeException(Exception ex) {
System.out.println("---[出现异常]");
ModelAndView mv=new ModelAndView("error");
mv.addObject("exception", ex);
return mv;
}
}
ResponseStatusExceptionResolver
在异常及异常父类中找到@ResponseStatus注解,然后使用这个注解的属性进行处理
定义一个@ResponseStatus注解修饰的异常类
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public class UnauthorizedException extends RuntimeException {}
若在处理器方法中抛出了上述异常:若ExceptionHandlerExceptionResolver不解析上述异常。由于触发的异常UnauthorizedException带有@ResponseStatus注解。因此会被ResponseStatusExceptionResolver解析到。最后响应HttpStatus.UNAUTHORIZED代码给客户端。HttpStatus.UNAUTHORIZED代表响应码401,无权限。关于其他的响应码请参考HttpStatus枚举类型源码。
@ResponseStatus既可以用于标识类,也可以用于标识方法
@ResponseStatus(value=HttpStatus.FORBIDDEN,reason="用户名和密码不匹配")中的reason会存在于exception的message中
Handler内容
@RequestMapping("/testResponseStatusExceptionResolver")
public String testResponseStatusExceptionResolver(@RequestParam("i") int i) {
if(i==13){
throw new UserNameNotMatchPasswordException();
}
System.out.println("testResponseStatusExceptionResolver.");
return "success";
}
对应的自定义Exception类的内容
@ResponseStatus(value=HttpStatus.FORBIDDEN,reason="用户名和密码不匹配")
public class UserNameNotMatchPasswordException extends RuntimeException {
private static final long serialVersionUID = 1L;
}
DefaultHandlerExceptionResolver
对一些特殊的异常进行处理,比如:
NoSuchRequestHandlingMethodException、
HttpRequestMethodNotSupportedException、
HttpMediaTypeNotSupportedException、
HttpMediaTypeNotAcceptableException等
不需要特殊配置,作为了解即可!
SimpleMappingExceptionResolver
如果希望对所有异常进行统一处理,可以使用SimpleMappingExceptionResolver,它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常,需要进行配置:
<!-- 配置使用 SimpleMappingExceptionResolver 来映射异常 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.ArrayIndexOutOfBoundsException">error
</prop>
</props>
</property>
</bean>
这样当Handler中发生数组越位的错误时,会把exception存入request,然后会跳转到error的界面,即相当于那个Handler中的那个方法return "error";
如果想改变其储存进request的键,那么可以配置
<property name="exceptionAttribute" value="xxx"></property>
这样xxx就是当前Exception的名字了,${ xxx }就可以在jsp页面上显示该异常
第十一讲 SpringMVC运行流程分析
第十二讲 SpringMVC与Spring
需要进行Spring整合SpringMVC吗?还是否需要再加入Spring的IOC容器?是否需要在web.xml文件中配置启动Spring IOC容器的ContextLoaderListener
- 需要:通常情况下,类似于数据源、事务,整合其他框架都是放在Spring的配置文件中的(而不是SpringMVC的配置文件中)。实际上放入Spring配置文件对应的IOC容器中的还有Service 和Dao.
- 不需要:都放在SpringMVC的配置文件中,也可以分多个Spring的配置文件,然后使用import节点导入其他的配置文件
并用的问题
若Spring的IOC容器和SpringMVC的IOC容器扫描的包有重合的部分,就会导致有的bean会被创建2次
- 解决方案
- 使Spring的IOC容器扫描的包和SpringMVC的IOC容器扫描的包没有重合的部分
- 使用
<context:component-scan base-package="springmvc" use-default-filters="false">
<context:exclude-filter type="annotation" expression=""/>
<context:include-filter type="annotation" expression=""/>
</context:component-scan>
配置来规定只能扫描的注解include-filter的是要扫描的,exclude-filter是不扫描的,在springmvc.xml和beans.xml文件中都要配置,一个中写了include-filter,另一个对应的注解就要写入exclude-filter
SpringMVC的IOC容器中的bean可以来引用Spring IOC容器中的bean,但是反过来则不行,Spring IOC容器中的bean却不能来引用SpringMVC IOC容器中的bean!
SpringMVC对比Struts2
- SpringMVC的入口是Servlet,而Struts2是Filter
- SpringMVC会稍微比Struts2快些。SpringMVC是基于方法设计,而Sturts2是基于类,每次发一次请求都会实例一个Action
- SpringMVC使用更加简洁,SpringMVC的开发效率也比Struts2高:支持JSR303,处理ajax的请求更方便
Struts2的OGNL表达式使页面的开发效率相比SpringMVC更高些。