标签:$.ajax stream 驱动 error sage defaults ase row abs
Spring MVC 处理请求的大致过程:
Spring MVC 框架使用 DispatcherServlet 接收所有客户端请求(暂时用 /,后期可以具体使用 .do 和 .htm 以防止干扰对静态资源的请求),并在服务器端根据 @RequestMapping 注释映射到相应 Handler 进行处理。
http://blog.csdn.net/zgzczzw/article/details/53926635
<!-- Web 应用加载参数,即 servlet context 参数 -->
<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:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
在 springmvc 的配置文件中设置 InternalResourceViewResolver,当我们对 SpringMVC 控制的资源发起请求时,这些请求都会被 SpringMVC 的 DispatcherServlet 处理,接着 Spring 会分析看哪一个 HandlerMapping 定义的所有请求映射中存在对该请求的最合理的映射。然后通过该 HandlerMapping 取得其对应的 Handler,接着再通过相应的 HandlerAdapter 处理该Handler。HandlerMapping 用来查找处理请求的对象,HandlerAdapter 用来处理请求参数并且在对 Handler 进行处理之后返回一个 ModelAndView 对象。在获得了 ModelAndView 对象之后,Spring 就需要把该 View 渲染给用户,即返回给浏览器。在这个渲染的过程中,发挥作用的就是 ViewResolver 和 View。当 Handler 返回的 ModelAndView 中不包含真正的视图,只返回一个逻辑视图名称的时候,这时 ViewResolver 就会把该逻辑视图名称解析为真正的视图 View 对象。View 是真正进行视图渲染,把结果返回给浏览器的。
load-on-startup 标签:配置是否在应用加载阶段初始化 Servlet。
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/in-pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
它是 URLBasedViewResolver 的子类,所以 URLBasedViewResolver 支持的特性它都支持。在实际应用中 InternalResourceViewResolver 也是使用的最广泛的一个视图解析器。那么 InternalResourceViewResolver 有什么自己独有的特性呢?单从字面意思来看,我们可以把 InternalResourceViewResolver 解释为内部资源视图解析器,这就是 InternalResourceViewResolver 的一个特性。InternalResourceViewResolver 会把返回的视图名称都解析为 InternalResourceView 对象,InternalResourceView 会把 Controller 处理器方法返回的模型属性都存放到对应的 request 属性中,然后通过 RequestDispatcher 在服务器端把请求 forword 重定向到目标 URL。比如上面在 InternalResourceViewResolver 中定义了prefix="/WEB-INF/in-pages",suffix=".jsp",然后请求的 Controller 处理器方法返回的视图名称为 success(下面代码),那么这个时候 InternalResourceViewResolver 就会把 success 解析为一个 InternalResourceView 对象,先把返回的模型属性都存放到对应的 HttpServletRequest 属性中,然后利用 RequestDispatcher 在服务器端把请求 forword到"/WEB-INF/in-pages/success.jsp"。这就是InternalResourceViewResolver 一个非常重要的特性,我们都知道存放在"/WEB-INF/"下面的内容是不能直接通过 request 请求的方式请求到的,为了安全性考虑,我们通常会把 jsp 文件放在WEB-INF目录下,而 InternalResourceView 在服务器端跳转的方式可以很好的解决这个问题。
http://elim.iteye.com/blog/1770554
@RequestMapping 可以声明在 Handler 或者其所在类上,使用 value 进行请求映射,当浏览器端调取资源,会被 DispatcherServlet 分配最合适的 HandlerMapping(默认 BeanNameUrlHandlerMapping)来处理此请求的映射工作,HandlerMapping 会找到最合适的的 Handler(如下"/test/get/{param}"被映射到 TestRequestMapping 的 get()方法),然后使用 HandlerAdapter(如 RequestMappingHandlerAdapter)进行处理操作并返回 ModelAndView。
@RequestMapping 的属性类似传统的 request 的方法,可以获取请求头等信息。
例:return 一个值,被 InternalResourceViewResolver 类处理,加上前后缀进行字符串拼接,转发到指定页面(关于这里的转发与重定向:http://www.cnblogs.com/qiujiazhen/p/5451787.html):
@Controller
@RequestMapping(value = "test")
public class TestRequestMapping {
private static final String MESSAGE = "welcome";
@RequestMapping(value = "get/{param}", method = RequestMethod.GET)
public String get(@PathVariable("param") Integer param) {
System.out.println("getMe:" + param + ";");
return MESSAGE;
}
……
注:映射的地址可以不写"/"(比如上面的 test),Spring 框架会帮加上:
但是最好不要依赖这一特性,所以还是养成加上的习惯!
在 web.xml 文件中配置 HiddenHttpMethodFilter 可以实现接收客户端的 PUT/DELETE 请求。HiddenHttpMethodFilter 是 Filter 组件的实现,其 doFilterInternal 方法会将请求的 POST 方法的隐藏域参数 "_method"(若存在)对应的值解析为相应请求,重新包装 HttpServletRequest 对象,最后放行请求,这样一来便实现了 PUT/DELETE 请求的解析。
<form action="test/put/123" method="post">
<input type="hidden" name="_method" value="PUT">
<input type="submit" value="提交PUT请求">
</form>
注意:如果相应请求地址所映射的方法的 @RequestMapping 注释的 method 属性与该重新被包装的 Request 对象不一致,则调用资源的行为不会获得方法支持。
@PathVariable、@RequestParam、@RequestHeader 等注释可以加在形参之前(对应请求信息),让 Spring MVC 框架将 HTTP 请求的对应信息绑定到相应的方法形参。也可以在 Handler 形参列表中声明传统的参数,同样可以正常使用,比如:HttpServletRequest、Writer 等。
参数如果是对象形式(这个对象可能来自 @ModelAttribute、Session 域、通过反射新建,这三种方式会在下面介绍)来接收请求参数,需要将属性参数键与相应 JavaBean 的 Setter 对应,之后 Spring 框架会包装参数到相应 Bean 的对象以参数传入(这种方式支持 name 以级联方式设置):
@RequestMapping(value = "pojo")
public String pojo(User user) {
System.out.println(user);
return MESSAGE;
}
<form action="test/pojo" method="post">
用户名:<input type="text" name="name"><br/>
密码: <input type="password" name="password"><br/>
省份: <input type="text" name="address.province"><br/>
城市: <input type="text" name="address.city"><br/>
<input type="submit" value="注册">
</form>
User、Address 等 Bean 省略。
Spring MVC 提供了以下几种途径输出模型数据:
ModelAndView 既包含视图信息,也包含模型数据信息,处理方法返回值类型为 ModelAndView 时, 方法体即可通过该对象添加模型数据。
@RequestMapping("testModelAndView")
public ModelAndView testModelAndView() {
ModelAndView mav = new ModelAndView();
ModelMap modelMap = mav.getModelMap();
User user = new User("cdf", "123456");
modelMap.addAttribute("user", user);
mav.setViewName("welcome");
return mav;
}
关键代码:
获取更改后的 ModelAndView:
DispatcherServlet 的 doDispatch 方法
获取结果,解析 ModelAndView 所包装的 Model 和 View
真正解析 View
真正解析 Model
AbstractView 的子类实现真正的 renderMergedOutputModel 方法解析 Model,如 InternalResourceView(下图):
“暴露”Model 的 key、value 给 request,最后真正实现的还是传统的方法 -- setAttribute
方法形参为 org.springframework.ui.Model、org.springframework.ui.ModelMap 或 java.uti.Map 时,在处理方法返回时,Map 中的数据会自动添加到模型中(最终还是要 render ModelAndView,原理并没改变):
Spring MVC 在调用方法前会创建一个隐含的模型对象作为模型数据的存储容器。如果方法形参为 Map 或 Model 类型,Spring MVC 会将隐含模型的引用传给对应形参。在方法体内,开发者可以通过形参访问到模型中的所有数据,也可以向模型中添加新的属性数据。
@RequestMapping("testModelAndView2")
public String testModelAndView(Model model, Map<String, Object> map) {
User user = new User("cdf", "123456");
model.addAttribute("user", user);
map.put("date", new Date());
return "welcome";
}
}
上例中返回的页面可以显示 user 和 date。
@SessionAttributes 注释可以将模型中的某个属性暂存到 HttpSession 中,以便多个请求之间可以共享这个属性。在控制器类上标注 @SessionAttributes, Spring MVC 将在模型中对应的属性暂存到 HttpSession 域中。
用法如下:
例:
@SessionAttributes(names = { "user" }, types = { com.cdf.springmvc.modelandview.User.class })
@Controller
public class TestHandler {
@RequestMapping("testSessionAttribute")
public String testSessionAttribute(Model model) {
User user = new User("cdf", "123456");
model.addAttribute("user", user);
return "welcome";
}
……
注意:
标参数上(指定 key 给 ModelMap 结构,具体下面讲):
<form action="testModelAndView4" method="post">
name:<input type="text" name="name"><br/>
password:<input type="password" name="password"><br/>
<input type="submit" value="提交"><br/>
</form>
@RequestMapping("testModelAndView4")
public ModelAndView testModelAndView(@ModelAttribute("u") User user) {
ModelAndView mv = new ModelAndView("welcome");
return mv;
}
<h1>Welcome, ${requestScope.u.name }!<br/>your password is ${requestScope.u.password }</h1>
标方法上(映射到 testModelAndView4 时,控制台会打印"Pre"):
@ModelAttribute
public void testPre() {
System.out.println("Pre");
}
@RequestMapping("testModelAndView4")
public ModelAndView testModelAndView() {
ModelAndView mv = new ModelAndView("welcome");
return mv;
}
形参对象的获取方式:
标参数上的例中,ModelAndView 获取 User 对象 user 的流程是:
前端页面发送请求,传来“其 name 对应了 User 类的 Setter 方法的参数,请求映射到了 testModelAndView4 上后,被封装到 User user 中,通过 @ModelAttribute 把 User 对象放进一个 Model(实际上是 ModelAndView 里的那个 Model 的副本,这个副本最后也会放 entry 域中)里,其 key 为指定的"u"(若没指定value="u",那么 以 User 的首字母小写 "user" 为 key 添加到 Model 里),然后视图解析器解析 View,将 ‘key-value‘s 们添加到 request 域中,最后转发到目标页面,页面再通过 EL 表达式显示 user。
请求处理方法执行完成后,最终返回一个 ModelAndView 对象。对于那些返回 String,View 或 ModeMap 等类型的处理方法,Spring MVC 也会在内部将它们装配成一个 ModelAndView 对象,它包含了逻辑名和模型对象的视图。
Spring MVC 借助视图解析器(ViewResolver)得到最终的视图对象(View),最终的视图可以是 JSP ,也可能是 Excel、JFreeChart 等各种表现形式的视图。
对于最终究竟采取何种视图对象对模型数据进行渲染,处理器并不关心,处理器工作重点聚焦在生产模型数据的工作上,从而实现 MVC 的充分解耦。
JSP 是最常见的视图技术,可以使用 InternalResourceViewResolver 作为视图解析器:
<bean>
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/in-pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
例:自定义视图,选择视图解析器解析
// 自定义视图,使用 BeanNameViewResolver 解析返回值(myview)并,实现把 model 的 value 都输出到返回的页面中去。
@Component
public class MyView implements View {
@Override
public String getContentType() {
return "text/html;charset=uft-8";
}
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception {
PrintWriter writer = response.getWriter();
Collection<?> values = model.values();
for (Object object : values) {
writer.write(object.toString());
}
}
}
@RequestMapping("testView")
public String testView(Map<String, Object> map) {
map.put("1", new String("now is "));
map.put("2", new Date());
map.put("3", new String(",welcome!"));
return "myView";// 返回上面自定义的 View
}
按解析器的 order 属性值大小来解析
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/in-pages/"></property>
<property name="suffix" value=".jsp"></property>
<property name="order" value="2"></property>
</bean>
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
<property name="order" value="1"></property>
</bean>
若要使用 JSTL 的 fmt 标签则需要在 SpringMVC 的配置文件中配置国际化资源文件(注意 messageSource 是固定的):
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="i18n"></property>
</bean>
注:若返回使用了 JSTL 的 JSP 页面,SpringMVC 会自动将视图改为 JstlView 类型。
若希望直接响应(不通过映射)通过 SpringMVC 渲染的页面,可以使用 mvc:view-controller 标签实现(一定要注意此时是否配置了 mvc:annotation-driven,防止出现映射请求失效的问题):
<mvc:view-controller path="straightRedirect" view-name="welcome"/>
一般情况下,控制器方法返回字符串类型的值会被当成逻辑视图名处理
如果返回的字符串中带 forward: 或 redirect: 前缀时,SpringMVC 会对他们进行特殊处理:将 forward: 和 redirect: 当成指示符,其后的字符串作为 URL 来处理
redirect:welcome.jsp:会完成一个到 welcome.jsp 的重定向的操作
forward:welcome.jsp:会完成一个到 welcome.jsp 的转发操作
注意:如果重定向到映射(通常是要访问 WEB-INF 下的不能直接访问的页面时),需要特别注意路径问题,此时一定要分清是使用相对路径还是绝对路径!
以下是实现简单的对 User 的增删改查的操作示例,资源请求风格为 REST。页面表单使用了 Spring 提供的表单标签,通过 SpringMVC 的表单标签可以实现将模型数据中的属性和 HTML 表单元素相绑定,以实现表单数据更便捷编辑和表单值的回显。
注:优雅的 REST 风格的资源URL 不希望带 .html 或 .do 等后缀,我们若将 DispatcherServlet 请求映射配置为 "/",则 Spring MVC 将捕获 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 属性显式指定。
<!-- 开启组件扫描 -->
<context:component-scan base-package="com.cdf"></context:component-scan>
<mvc:annotation-driven></mvc:annotation-driven>
<!-- 引入外部文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置C3P0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" lass="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>
<!-- 开启声明式事务 -->
<tx:annotation-driven mode="proxy" transaction-manager="transactionManager"/>
<!-- 使用具名参数的 JdbcTemplate -->
<bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>
<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<!-- 处理静态资源 -->
<mvc:default-servlet-handler/>
@Component
@RequestMapping
public class MyHandler {
@Autowired
private UserDAO userDAO;
@Autowired
private DepartmentDAO departmentDAO;
// 显示 User 列表
@RequestMapping(value = { "/users" }, method = { RequestMethod.GET })
public String showUserList(Map<String, Object> map) {
List<User> allUsers = userDAO.getAllUsers();
map.put("users", allUsers);
return "user-list";
}
// 去 User 编辑页面
@RequestMapping(value = { "/user/{id}" }, method = { RequestMethod.GET })
public String toEditUser(@PathVariable(name = "id") Integer id, Map<String, Object> map) {
List<Department> allDepartments = departmentDAO.getAllDepartments();
map.put("departments", allDepartments);// 用于下拉列表
map.put("user", userDAO.getUserById(id));// 用于回显
return "user-edit";
}
// 去 User 添加页面
@RequestMapping(value = { "/user" }, method = { RequestMethod.GET })
public String toAddUser(Map<String, Object> map) {
List<Department> allDepartments = departmentDAO.getAllDepartments();
map.put("departments", allDepartments);
map.put("user", new User());
return "user-edit";
}
// 添加 User
@RequestMapping(value = { "/user" }, method = { RequestMethod.POST })
public String addUser(User user) {
userDAO.saveUser(user);
return "redirect:/users";
}
// 修改 User
@RequestMapping(value = { "/user" }, method = { RequestMethod.PUT })
public String editUser(User user) {
userDAO.updateUser(user);
return "redirect:/users";
}
// 删除 User
@RequestMapping(value = { "/user/{id}" }, method = { RequestMethod.DELETE })
public String deleteUser(@PathVariable(name = "id") Integer id) {
userDAO.deleteUser(id);
return "redirect:/users";
}
}
<body>
<h1 align="center">User List</h1>
<table align="center" border="1px" width="50%" cellspacing="0px">
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Gender</th>
<th>Department</th>
<th>Operation</th>
</tr>
<c:forEach items="${requestScope.users }" var="user">
<tr align="center">
<td>${user.id }</td>
<td>${user.name }</td>
<td>${user.email }</td>
<td>${user.gender==0?‘女‘:‘男‘}</td>
<td>${user.depId }</td>
<td>
<a class="delete" href="user/${user.id }">删除</a> <a href="user/${user.id }">修改</a>
</td>
</tr>
</c:forEach>
</table>
<h3 align="center"><a href="user" style="text-decoration: none;color: orange;">Add New User</a></h3>
<!-- 用于删除操作 -->
<form action="" method="post">
<input type="hidden" name="_method" value="DELETE">
</form>
</body>
一般情况下,通过 GET 请求获取表单页面,而通过 POST 请求提交表单页面,因此获取表单页面和提交表单页面的 URL 是相同的。只要满足该最佳条件的契约,
<body>
<div style="float: left; border: 50px auto;">
<%
Map<String, String> map = new HashMap<String, String>();
map.put("1", "男");
map.put("0", "女");
request.setAttribute("genders", map);
boolean flag = false;
%>
<!-- SpringMVC 认为表单值一定要回显,默认情况下会使用"command"到 request 域对象中找对应的数据。
可以使用 form:form 中 的 modelAttribute 属性指定使用哪个 key 到 request 域对象中找数据。 -->
<form:form action="${pageContext.servletConfig.servletContext.contextPath }/user" method="post" modelAttribute="user">
<!-- 以 User 的 id 字段是否为空判断 修改/添加 操作 -->
<c:if test="${empty requestScope.user.id }">
<!-- 添加 -->
Name:<form:input path="name" />
<!-- path 相当于传统 input 标签的 name 属性,支持级联 -->
</c:if>
<c:if test="${not empty requestScope.user.id }">
<!-- 修改,这里的 id 值为回显的值,由于指定了 modelAttribute,所以值是拿的到的,
这样一来修改完的 User 就是完整的了,不会存在缺少id的情况 -->
<form:hidden path="id" />
<!-- 对于_method 不能使用 form:hidden 标签,因为 modelAttribute 对应的 bean 中没有 _method 属性,
因此回显数据的时候找不到对应的属性就会报错 -->
<input type="hidden" name="_method" value="PUT">
</c:if>
<br />
Email:<form:input path="email" />
<br />
Gender:<form:radiobuttons path="gender"
items="${requestScope.genders }" />
<br />
<!-- 由于 User 的 Department 依靠 depId 这个 int 型字段,发送请求后封装到 user 需要 depId,所以这里的 path 是 depId -->
<!-- itemLabel、itemValue 分别是每个 item 的显示名和表单传的 value 值 -->
<!-- 最终传到后台的会是 path=itemValue 形式 -->
Department:<form:select path="depId"
items="${requestScope.departments }" itemLabel="name" itemValue="id"></form:select>
<br />
<c:if test="${empty requestScope.user.id }">
<input type="submit" value="Add User" />
</c:if>
<c:if test="${not empty requestScope.user.id }">
<input type="submit" value="Update User" />
</c:if>
</form:form>
</div>
</body>
SpringMVC 表单标签共有属性:
cssErrorClass:表单组件的数据存在错误时,采取的 CSS 样式
form:radiobutton:单选框组件标签,当表单 bean 对应的属性值和 value 值相等时,单选框被选中
Spring MVC 通过反射机制对目标处理方法进行解析,将请求消息绑定到处理方法的形参中。数据绑定的核心部件是 DataBinder,运行机制如下:
类型转换、格式化(通常是一起的)过程大概如下:
执行 bindRequestParameters 后:
ConversionService 是 Spring 类型转换体系的核心接口。
可以利用 ConversionServiceFactoryBean 在 Spring 的 IOC 容器中定义一个 ConversionService。Spring 将自动识别出 IOC 容器中的 ConversionService,可以在 Bean 属性配置及 SpringMVC 处理方法形参绑定等场合使用它进行数据的转换及格式化。
可通过 ConversionServiceFactoryBean 的 converters 属性注册自定义的类型转换器,下面模拟实现一个把请求参数的 String 转换为 User 的自定义规则 Conterver(注意在 annotation-driven 中声明 ConversionService):
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters" ref="myConverter"></property>
</bean>
<!-- 将自定义的 ConversionService 注册到 SpringMVC 的上下文中 -->
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
@Component
public class MyConverter implements Converter<String, User> {
@Override
public User convert(String param) {
String[] parts = param.split("-", 4);// 请求参数格式:name-email-gender-department
if (parts == null)
return null;
User user = new User();
user.setName(parts[0]);
user.setEmail(parts[1]);
user.setGender(Integer.parseInt(parts[2]));
user.setDepId(Integer.parseInt(parts[3]));
return user;
}
}
// 测试自定义 Converter
@RequestMapping(value = { "/converter" }, method = { RequestMethod.POST })
public String testConverter(@RequestParam(value = "user") User user) {
System.out.println("请求字符串通过 MyConverter 解析后,得到的 User 是:\n" + user);
userDAO.saveUser(user);
return "redirect:/users";
}
测试:
DEBUG 模式发现:
结果:
<mvc:annotation-driven> 会自动注册 RequestMappingHandlerMapping(默认是 AnnotationMethodHandlerAdapter,Spring3.2之后被 RequestMappingHandlerMapping 替代) 、RequestMappingHandlerAdapter 与 ExceptionHandlerExceptionResolver 三个bean,还将提供以下支持:
配置处理静态资源(<mvc:default-servlet-handler>)后,若不配置 annotation-driven,会导致 ConversionService 失效,如上图第二种情况(此时使用的 ConversionService 为 DefaultConversionService,它并不具备数据格式化的功能),此时需要配置 annotation-driven 并设置 conversion-service 为“包含可以转换并格式化请求数据的 Converter 的 ConverterService”。此时还会导致 RequestMappingHandlerAdapter 加载不了,最终导致只能处理静态资源请求,而无法映射到 Handler 的方法。
由 @InitBinder 标识的方法,可以对 WebDataBinder 对象进行初始化。WebDataBinder 是 DataBinder 的子类,用于完成由表单字段到 JavaBean 属性的绑定,并且提供设置校验器、转换器等等方法。
例:
@InitBinder
public void testInitBinder(WebDataBinder binder) {
binder.setDisallowedFields("name");
}
Spring 在格式化模块中定义了一个实现 ConversionService 接口的实现类: FormattingConversionService,该实现类扩展了 GenericConversionService,因此它既具有类型转换的功能,又具有格式化的功能。
FormattingConversionService 拥有一个 FormattingConversionServiceFactroyBean 工厂类,后者用于在 Spring 上下文中构造前者。
装配了 FormattingConversionServiceFactroyBean 后,就可以在 Spring MVC 形参绑定及模型数据输出时使用注解驱动了。<mvc:annotation-driven/> 默认创建的 ConversionService 实例即为 FormattingConversionServiceFactroyBean(这就是为什么上文例子中配置了<mvc:annotation-driven/>但没设定 ConversionService 属性时,也可以正常映射参数到 Bean 属性的原因)。
FormattingConversionServiceFactroyBean 内部已经注册了 :
NumberFormatAnnotationFormatterFactroy:支持对数字类型的属性使用 @NumberFormat 注解。@NumberFormat 拥有两个互斥的属性:
JodaDateTimeFormatAnnotationFormatterFactroy:支持对日期类型的属性使用 @DateTimeFormat 注解,可对 java.util.Date、java.util.Calendar、java.long.Long 时间类型进行标注,@DateTimeFormat 属性说明:
例:
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
做数据转换、格式化、校验后,SpringMvC 会将发生的异常全部封装到 BindingResult 对象中去,可以通过方法形参传入一个 BindingResult(或其父类 Errors) 对象来获取错误信息:
// 添加 User
@RequestMapping(value = { "/user" }, method = { RequestMethod.POST })
public String addUser(User user, BindingResult result) {
List<FieldError> fieldErrors = result.getFieldErrors();
for (FieldError fieldError : fieldErrors) {
System.out.println("错误发生在:" + fieldError.getField() + "\n错误信息:" + fieldError.getDefaultMessage());
}
userDAO.saveUser(user);
return "redirect:/users";
}
打印错误信息示例:
注意,这里的 BindingResult 对象声明需要紧挨着 Bean 声明。
JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 6.0 中。
JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对 Bean 进行验证:
Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解:
Spring 4.0 拥有自己独立的数据校验框架,同时支持 JSR 303 标准的校验框架。
Spring 在进行数据绑定时,可同时调用校验框架完成数据校验工作。在 Spring MVC 中,可直接通过注解驱动的方式进行数据校验。
Spring 的 LocalValidatorFactroyBean 既实现了 Spring 的 Validator 接口,也实现了 JSR 303 的 Validator 接口。只要在 Spring 容器中定义了一个 LocalValidatorFactoryBean,即可将其注入到需要数据校验的 Bean 中。
Spring 本身并没有提供 JSR303 的实现,所以必须将 JSR303 的实现者的 jar 包放到类路径下。
Spring MVC 除了会将表单/命令对象的校验结果保存到对应的 BindingResult 或 Errors 对象中外,还会将所有校验结果保存到 “隐含模型”,即使处理方法的签名中没有对应于表单/命令对象的结果形参,校验结果也会保存在 “隐含对象” 中。隐含模型中的所有数据最终将通过 HttpServletRequest 的对象,因此在 JSP 中可以获取错误信息属性列表暴露给 JSP 视图。在 JSP 页面上可通过 <form:errors path=“userName”> 显示错误消息(需要注意的是:此标签需要在<form:form>内使用)。
<mvc:annotation-driven/> 会默认装配好一个 LocalValidatorFactoryBean,通过在处理方法的形参上标注 @Valid 注解即可让 Spring MVC 在完成数据绑定后执行数据校验的工作。
在已经标注了 JSR303 注解的表单/命令对象前标注一个 @Valid,Spring MVC 框架在将请求参数绑定到该行参对象后,就会调用校验框架根据注解声明的校验规则实施校验。
Spring MVC 是通过对处理方法签名的规约来保存校验结果的:前一个表单/命令对象的校验结果保存到随后的形参中,这个保存校验结果的形参必须是 BindingResult 或 Errors 类型,这两个类都位于 org.springframework.validation 包中。
需校验的 Bean 对象和其绑定结果对象或错误对象时成对出现的,它们之间不允许声明其他的形参。
Hibernate Validator 则是JBoss社区开源的一个JSR 303 Bean Validation 规范的优秀实践:
@Past
private Date birthday;
// 添加 User
@RequestMapping(value = { "/user" }, method = { RequestMethod.POST })
public String addUser(@Valid @ModelAttribute("user") User user, BindingResult result, Map<String, Object> map) {
List<FieldError> fieldErrors = result.getFieldErrors();
System.out.println(user.getBirthday());
if (result.hasErrors()) {// 出错,回到添加页面
for (FieldError fieldError : fieldErrors) {
System.out.println("错误发生在:" + fieldError.getField() + "\n错误信息:" + fieldError.getDefaultMessage());
}
map.put("departments", departmentDAO.getAllDepartments());
return "user-edit";
}
userDAO.saveUser(user);
return "redirect:/users";
}
SpringMVC 提供了 <mvc:message-converters> 标签以声明 MessageConverter,此接口定义了 Java 对象与 JMS 消息之间的转换策略。该标签是 <mvc:annotation-driven> 子标签,可以在该标签中声明 bean,让 SpringMVC 在 @ResponseBody 绑定的方法/属性上作用该类的转换策略(例如 String 转 XML 等)。
SpringMVC 框架默认支持 jackson 的使用,不需要另外声明其对应的 MessageConverter 实现类,只需要导入相应 jar 包即可,下面演示使用阿里巴巴开源的 json 转换工具--fastjson 在 SpringMVC 中的使用方式:
注册 fastjson 的 MessageConverter:
<mvc:annotation-driven conversion-service="conversionService">
<mvc:message-converters register-defaults="true">
<!-- @ResponseBody 乱码问题,将 StringHttpMessageConverter 的默认编码设为UTF-8 -->
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<constructor-arg value="UTF-8" />
</bean>
<!-- 配置 fastjson 支持 -->
<bean
class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="charset" value="UTF-8" />
<property name="supportedMediaTypes">
<list>
<value>application/json</value>
<value>text/html;charset=UTF-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
// 测试接收异步并返回 json 数据
@ResponseBody
@RequestMapping(value = { "/ajax" }, method = { RequestMethod.POST })
public String testAJAX() {
return "测试异步获取信息-使用 fastjson";
}
<div>
<a id="ajax" href="ajax">异步请求:弹框显示返回信息</a>
</div>
$("#ajax").click(function() {
var url = $(this).attr("href");
$.ajax({
type:"POST",
url:url,
success:function(msg){
alert(msg);
}
})
return false;
});
测试结果:
HttpMessageConverter<T> 是 Spring3.0 新添加的一个接口,负责将请求信息转换为一个对象(类型为 T),我们可以将对象(类型为 T)输出为响应信息。
DispatcherServlet 默认装配 RequestMappingHandlerAdapter,而 RequestMappingHandlerAdapter 默认装配如下 HttpMessageConverter:
由于框架有 jackson 的对应 MessageAdapter(MappingJackson2HttpMessageConverter),在添加 jackson 相关 jar 包后,RequestMappingHandlerAdapter 装配的 HttpMessageConverter 变为:
在上例中,我们使用了 fastjson 包(未添加 jackson 相关包),并在配置文件中配置 fastjson 后,RequestMappingHandlerAdapter 装配的 HttpMessageConverter 才变为(注意,此时默认使用的 MappingJackson2HttpMessageConverter 就不加载了):
使用 HttpMessageConverter<T> 将请求信息转化并绑定到处理方法的形参中或将响应结果转为对应类型的响应信息,Spring 提供了两种途径:
当控制器处理方法使用到 @RequestBody/@ResponseBody 或 HttpEntity<T>/ResponseEntity<T> 时, Spring 首先根据请求头或响应头的 Accept 属性选择匹配的 HttpMessageConverter, 进而根据参数类型或泛型类型的过滤得到匹配的 HttpMessageConverter, 若找不到可用的 HttpMessageConverter 将报错,具体:
@RequestBody 和 @ResponseBody 不需要成对出现。
默认情况下,SpringMVC 根据请求头中的 Accept-Language 判断客户端的本地化类型。
当接受到请求时,SpringMVC 会在上下文中查找一个本地化解析器(LocalResolver),找到后使用它获取请求所对应的本地化类型信息。
SpringMVC 还允许装配一个动态更改本地化类型的拦截器,这样通过指定一个请求参数就可以控制单个请求的本地化类型。
我们需要关注以下问题:
在页面能够根据浏览器语言设置情况对文本、时间、数值等进行本地化的显示。
在 Handler 方法中获取当前 Locale 对应的国际化资源文件中的信息。
通过超链接切换 Locale,不再依赖浏览器的原设置情况(不是简单的写两个页面)。
解决上述问题的方式如下:
可以通过按一定的规则命名、编写国际化资源文件,并在配置文件中声明 ResourceBundleMessageSource,之后使用 JSTL 的 <fmt:message> 标签即可。
在 Handler 中装配 ResourceBundleMessageSource 并在 Handler 方法形参处传入 Locale 对象,即可使用 ResourceBundleMessageSource 对象的方法(getMessage())来获取当前使用的国际化资源文件中指定的信息。
例:
<!-- 配置拦截器 -->
<mvc:interceptors>
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"></bean>
</mvc:interceptors>
<!-- 配置 LocaleResolver -->
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"></bean>
默认使用请求参数名为“locale”的参数值作为创建 Session 域中 Locale 对象的依据:
默认使用 id 为“localeResolver”的 LocaleResolver:
若未声明 LocaleResolver,SpringMVC 默认使用 AcceptHeaderLocaleResolver:
经过上述两个步骤后,就可以实现“通过在请求中使用一个 key 为‘locale’的请求参数指定语言信息,从而转换 fmt:message 标签的显示内容”。实现过程大致如下:
流程图:
Spring MVC 为文件上传提供了直接的支持,这种支持是通过即插即用的 MultipartResolver 实现的。Spring 用 Jakarta Commons FileUpload 技术实现了一个 MultipartResolver 实现类:CommonsMultipartResolver,需要导入 Apache Commons FileUpload、Apache Commons IO 包。
Spring MVC 上下文中默认没有装配 MultipartResolver,需在上下文中配置 MultipartResolver。
示例:
<form:form
action="${pageContext.servletContext.contextPath }/fileUpload"
method="post" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="上传">
</form:form>
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8"></property>
<property name="maxUploadSize" value="1024000"></property>
</bean>
@RequestMapping(value = { "/fileUpload" }, method = RequestMethod.POST)
public String fileRec(@RequestParam("file") MultipartFile file, HttpSession session) {
System.out.println("接收文件的真实文件名:" + file.getOriginalFilename());
System.out.println("MultipartFile 对象 file 的实际类型:" + file.getClass().getName());
InputStream inputStream = null;
OutputStream fos = null;
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
// MultipartFile 内建了 InputStream
inputStream = file.getInputStream();
System.out.println("MultipartFile 封装的输入流的真实类型:" + inputStream);
bis = new BufferedInputStream(inputStream);
// 将获取到的文件存储到 FileRec 目录下
ServletContext servletContext = session.getServletContext();
String realPath = servletContext.getRealPath("FileRec");
String path = realPath + File.separator + file.getOriginalFilename();
System.out.println("存储路径:" + path);
fos = new FileOutputStream(path);
bos = new BufferedOutputStream(fos);
int len = 0;
byte[] b = new byte[1024];
while ((len = bis.read(b)) != -1) {
bos.write(b, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bos != null) {
bos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (bis != null) {
bis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return "user-list";
}
Spring MVC 可以使用拦截器对请求进行拦截处理,自定义的拦截器须实现 HandlerInterceptor 接口。该接口定义了以下方法:
流程如下:
注:
有关第1点的说明:
有关第2点的说明:
例:
自定义两个拦截器,其 preHandle() 方法分别返回 true 和 false:
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("MyInterceptor----preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor----postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("MyInterceptor----afterCompletion");
}
}
public class MyInterceptor2 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("MyInterceptor2----preHandle");
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor2----postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("MyInterceptor2----afterCompletion");
}
}
先后声明两个拦截器:
<!-- 配置拦截器,在 mvc:interceptors 标签中声明的 Interceptor 会拦截所有请求。 -->
<mvc:interceptors>
<mvc:interceptor>
<!-- 在 mvc:interceptor 标签中声明的 Interceptor 会拦截对应 mvc:mapping 定义的请求。 -->
<mvc:mapping path="/testMyInterceptor" />
<bean class="com.cdf.interceptor.MyInterceptor"></bean>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/testMyInterceptor" />
<bean class="com.cdf.interceptor.MyInterceptor2"></bean>
</mvc:interceptor>
</mvc:interceptors>
请求映射:
<a href="${pageContext.servletContext.contextPath }/testMyInterceptor">测试拦截器</a>
@RequestMapping(value = "/testMyInterceptor", method = RequestMethod.GET)
public String testInterceptor() {
System.out.println("成功进入…");
return "redirect:users";
}
测试结果:
可以看到,方法并没有成功进入,并且 MyInterceptor2 的 afterCompletion 方法也执行了,与预想的一样。
SpringMVC 通过 HandlerExceptionResolver 处理程序的异常,包括 Handler 映射、数据绑定以及目标方法执行时发生的异常。
当配置文件没有声明 mvc:annotation-driven 时,DispatcherServlet 默认装配:
而当声明了 mvc:annotation-driven 时,ExceptionHandlerExceptionResolver 会取代已过时的 AnnotationMethodHandlerExceptionResolver:
主要处理 Handler 中用 @ExceptionHandler 注解注释了的方法。
@ExceptionHandler 注解定义的方法优先级问题:例如发生的是 NullPointerException,但是声明的异常有 RuntimeException 和 Exception,此候会根据异常的最近继承关系找到继承深度最浅的那个 @ExceptionHandler 注解方法,即标记了 RuntimeException 的方法。
ExceptionHandlerMethodResolver 内部若找不到 @ExceptionHandler 注解的话,会找 @ControllerAdvice 注解注释的类中的 @ExceptionHandler 注解方法。
@ExceptionHandler 注解定义的方法形参中不能定义 Map。
例:
@ExceptionHandler(RuntimeException.class)
public ModelAndView testAdvice(RuntimeException e) {
ModelAndView mv = new ModelAndView("error");
mv.addObject("exception", e);
return mv;
}
// 测试异常处理
@RequestMapping(value = "/testRuntimeException", method = RequestMethod.GET)
public String testNullPointerException() {
throw new RuntimeException("测试异常");
}
// 测试我的异常处理
@RequestMapping(value = "/testMyException", method = RequestMethod.GET)
public String testMyException() {
throw new MyException("测试我的异常");
}
@ControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler
public ModelAndView testMyExceptionHandler(MyException e) {
ModelAndView mv = new ModelAndView();
mv.addObject("advice", e);
return mv;
}
}
在自定义异常类声明上使用 @ResponseStatus 注解。
若在处理器方法中抛出了上述异常且 ExceptionHandlerExceptionResolver 不解析此异常,那么 @ResponseStatus 注解的 value 定义的 HttpStatus 状态码将会响应给客户端。
例:
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "我们不是谷歌,我们只是谷歌的搬运工~")
public class MyException extends RuntimeException {
private static final long serialVersionUID = 1L;
public MyException() {
super();
}
public MyException(String message) {
super(message);
}
}
对一些特殊的异常进行处理,比如:
NoSuchRequestHandlingMethodException、HttpRequestMethodNotSupportedException、HttpMediaTypeNotSupportedException、HttpMediaTypeNotAcceptableException 等。
如果希望对所有异常进行统一处理,可以使用 SimpleMappingExceptionResolver,它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常。
例:
<!-- 配置 SimpleMappingExceptionResolver 让异常发生时响应其对应的视图-->
<bean id="simpleMappingExceptionResolver"
class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="com.cdf.exception.MyException">runtime-exception</prop>
</props>
</property>
</bean>
标签:$.ajax stream 驱动 error sage defaults ase row abs
原文地址:http://www.cnblogs.com/chendifan/p/7371860.html