SpringMVC扩展
REST 软件架构:
Representational State Transfer,表述性状态转移,是一种软件架构风格 查看、修改、删除所对应的传统URL与REST风格的URL对比
/userview.html?id=12 VS /user/view/12
参数不再使用“?”传递
与传统不同就是 REST 不在通过?来传递参数,这种URL的可读性更好,项目架构清晰; 最关键是SpringMVC 它支持这种风格~ 弊端:对于中国的项目,URL中有时候存储中文, 中文乱码…(中文真让人头大呀~)
现在很多网站都是 REST 和 传统的URL 结合使用;
实例代码:
代码语言:javascript复制 @RequestMapping(value="/xxx/{参数名1}/{参数名2}")
public String info(@PathVariable("参数名1") Integer id,@PathVariable("参数名2") String name){
//@PathVariable注解: 将URL中的{xx} 占位符参数 绑定到对应的控制器方法参数中;
//打印输出
System.out.println("===============" id "============" name);
return "/页面名"; //这里return "/页面名"; 加了一个 /表示回到项目根目录,因为受上面的@RequestMapping(value="/xx/{}/{}")影响,当然相对路径不匹配~
}
假设有一条URL : localhost:8080/项目名/rest/1/wsm
重点是 /rest/1/wsm
控制器是代码是:
@RequestMapping(value="/rest/{id}/{name}")
public String info(@PathVariable("id") Integer id,@PathVariable("name") String name){
//控制台打印
System.out.println("===============" id "============" name);
return "/index"; //随便就好~
}
控制台输出:
需要注意的就是:
return “/xx”; / 使其回归根目录~
参数多个时候, xx/{}/{} 要对应,不然可能找不到控制器;
而且因为很多时候, 受REST影响返回页面的静态资源也可能存在路径异常: 通常建议使用绝对路径来解决此问题…
在路径前加 ${pageContext.request.contextPath }/
表示绝对路径; 一般用于JSP页面上对的静态资源引用防止路径引用出错…
等价于<%=request.getContextPath()%>
小脚本引用内置对象调用方法来获取 绝对路径…
{pageContext.request.contextPath }/ 也就是取出部署的应用程序名或者是当前的项目名称 {pageContext.request.contextPath}或<%=request.getContextPath()%> 取出来的就是: /项目名 。 而"/"代表的含义就是 http://localhost:8080 比如我的项目名称是demo1在浏览器中输入为:http://localhost:8080/Demo/index.jsp。 取出来的就是: /Demo
使用Servlet API对象作为处理方法的入参
在SpringMVC中 控制器可以不依赖任何Servlet APl对象( 直接把类型作为,参数放在方法中 既可以使用)
可以将Servlet APl 对象作为处理方法的参数 进行使用;
controller(控制器方法Demo)
@RequestMapping("/请求页面名")
public String Demo(HttpSession session,HttpServletRequest request){
session.setAttribute("session","session作用域"); //往session 作用域存储数据;
request.setAttribute("request","request作用域"); //往request 作用域存储数据;
return "响应页面";
}
静态资源文件的引用:
SpringMVC 在引用外部的静态资源有时候存在失效情况…
因为: web.xml 中配置的 DispatcherServlet (分配器) 请求映射为 /
即: SpringMVC 将捕获的web 容器的所有请求;
(包括静态的请求,而SpringMVC将它们当成了一个普通的请求, 但由于找不到对于的处理器,所有按照常规的方式引入: 静态文件"无法访问" …)
采用 <mvc:resources > 解决静态资源访问问题: 首先为了方便管理,一般将项目中的所有静态文件资源:(JS,HTML,CSS…) 放在一个目录下
JSP文件:
<link rel="stylesheet" type="text/css" href="static/css/index.css">
<!-- 不详细介绍了就是一个 引用外部资源的代码... -->
Spring核心配置文件
<!-- 访问静态资源 css js html.. 打开访问权限,SpringMVC 默认不允许静态资源的加载的; -->
<mvc:resources location="/static/" mapping="/static/**" />
<!--
location 将静态资源映射到指定的路径下; 文件请求经过 DispatcherServlet 判断文件的在 static 目录下进行特殊处理..
mapping 本地静态资源文件的目录; ** 文件下的所有文件;
-->
解决项目中的中文乱码问题:
web.xml
<!-- 解决项目中的控制器 中文参数乱码问题 -->
<filter> <!-- 过滤器 -->
<filter-name>myencoding</filter-name> <!-- 过滤器name -->
<!-- 引入过滤器 类 -->
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name> <!-- 指定编码形式 -->
<param-value>UTF-8</param-value> <!-- UTF-8编码 -->
</init-param>
</filter>
<filter-mapping> <!-- 过滤器映射 -->
<filter-name>myencoding</filter-name> <!-- 根据过滤器名,映射指定的过滤器.. -->
<url-pattern>/*</url-pattern> <!-- /* 对所有文件(包括JSP)进行过滤配置,设置文件编码UTF-8 -->
</filter-mapping>
异常处理:
局部异常处理
方式一:控制器类继承 HandlerExceptionResolver
@Override //控制器类继承了 HandlerExceptionResolver 重写resolveException(..); 方法;
public ModelAndView resolveException(HttpServletRequest arg0,HttpServletResponse arg1, Object arg2, Exception arg3) {
//当发生异常时,SpringMVC 会调用resolveException(..); 方法并返回一个 ModelAndView 给浏览器;
return new ModelAndView("err"); //指定异常之后跳转的页面;
}
方式二:使用注解 @ExceptionHandler
@RequestMapping(value="/index") //控制器接收的 index
public ModelAndView index(){
ModelAndView mas = new ModelAndView(); //注意导包错误!
mas.setViewName("index"); //要响应的 视图页面;
System.out.println(1/0); //手动抛出一个运行时异常...运行时异常
return mas;
}
@ExceptionHandler(value={RuntimeException.class}) //注解声明运行时异常,当类中有运行时异常会进入该方法处理;
public String err(RuntimeException e,HttpServletRequest request){ //参数异常对象,可以通过request..返回给异常页面查看
request.setAttribute("e",e);
return "err"; //处理异常方法,返回异常页面 err ;
}
代码语言:javascript复制 对于这些异常参数,可以存储在 request/session/Model 中返回异常页面.通过 ${参数名.message } 来查看 异常信息;
全局异常处理
上面的局部异常, 一次只有作用于一个 controller控制器有效, 局部异常 如果需要对所有异常进行匹配就需要全局异常处理了
全局异常可以使用:SimpleMappingExceptionResolver
<!-- 全局的异常配置信息 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="defaultErrorView" value="err" />
</bean>
可以在props中定义多种类型异常
<!-- 可以在props中定义多种类型异常 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver" >
<property name="exceptionMappings">
<props>
<prop key="java.lang.RuntimeException">err</prop>
<prop key="异常">异常类名映射的视图名</prop>
</props>
</property>
</bean>
格式化注解@DateTimeFormat
有时候SpringMVC 新增时候报错, BindException,是在对bean的属性进行数据绑定时出了问题。
这是springmvc框架的问题,若不解决次问题
页面传递回来的时间类型的数据就无法在controller中接受(实体类Date 页面传过是 String )
也就无法完成新增用户的功能。
通常都是时间类型绑定失败;
解决方法有很多:使用@DateTimeFormat
引入:joda-time-2.9.9.jar 包
在实体类上引入注解:
public class 实体类 {
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
}
在上述代码中,@DateTimeFormat(pattern="yyyy-MM-dd")可以将形如1988-12-01的字符串转换为java.util.Date类型。
算是一种解决方法;
注意:
这个只是在中文赋值时候转换成 Date 类型的对象,如果想要查看 还是以英文的时间格式,
不过~ 这个在Spring 的表单中可以搭配使用, 显示对应的格式~
总结:
- @DateTimeFormat 声明在实体属性上,前端发送的 字符串类型日期格式, 可以直接和对象进行绑定匹配!
- 只是解决前端String——后端Date
绑定
,后端Date——前端展示还需另外处理!
Spring表单
现在前后端已经区分很明确了,JSP已经很少使用了,Spring表单也很少使用了!
我们在进行SpringMVC 项目开发时,一般会使用 EL表达式 和 JSTL标签 HTML表单… 来完成页面视图开发.
Spring也有自己的一套标签库,通过Spring表单标签;
可以更容易的将模型数据表单, 命令对象绑定到 HTML 表单元素中;
首先和JSTL标签库一样,在使用Spring表单之前。必须在JSP页面下添加:引入标签库
<%@ taglib prefix="fm" uri="http://www.springframework.org/tags/form" %>
之后就可以使用Spring的表单标签库了…
实例代码有点不好解释, 就直接上项目Demo了
做一个模拟用户查看功能: SpringMVC 的配置就不锁了,web.xml ..applicationContext-mvc.xml
首先实体类user
public class User {
private Integer id;
private String name;
private String pwd;
@DateTimeFormat(pattern="yyyy-MM-dd") //使用了注解时间参数赋值问题,注意引用jar哦~
private Date birthday;
private Integer sex;
//get/set 方法...;
}
控制器UserController.java
package com.wsm.controller;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import com.wsm.entity.User;
@Controller
public class UserController {
//因为这里我们没有数据库就模拟一些数据出来~
//静态用户集合,类加载时候创建;
private static List<User> users = new ArrayList<User>();
//静态代码块,初始化一下用户出来;
static{
User u1 = new User();
u1.setId(1);
u1.setName("wsm");
u1.setBirthday(new Date());
u1.setPwd("123");
u1.setSex(1);
User u2 = new User();
u1.setId(2);
u2.setName("wsx");
u2.setBirthday(new Date());
u2.setPwd("321");
u2.setSex(0);
//把 u1 u2 放在users 集合中
users.add(u1);
users.add(u1);
}
//展示数据 index页面
@RequestMapping("/index")
public String index(Model model){
model.addAttribute("user", users);
return "index";
}
//去修改页面
@RequestMapping("/toupd")
public String toupd(Integer id,Model model){
User u = users.get(id-1); //下标获取, 要注意数据下标 0 开始; 所以减一
model.addAttribute("user",u);
return "upd";
}
}
index.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme() "://" request.getServerName() ":" request.getServerPort() path "/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>My JSP 'index.jsp' starting page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->
</head>
<body>
<c:if test="${user==null }">
<script type="text/javascript">
location.href="index";
</script>
</c:if>
<a href="toadd" >新增</a>
<table border="1" width="100%">
<tr>
<td>编号</td>
<td>用户名</td>
<td>密码</td>
<td>性别</td>
<td>生日</td>
<td>操作</td>
</tr>
<c:forEach items="${user }" var="u" >
<tr>
<td>${u.id }</td>
<td>${u.name }</td>
<td>${u.pwd }</td>
<td>${u.sex }</td>
<td>${u.birthday }</td> <!-- 这时页面上的时间还是英文格式, 哎看着就不舒服-->
<td> <a href="toupd?id=${u.id }" >修改</a> </td>
</tr>
</c:forEach>
</table>
</body>
</html>
点击修改擦看详情信息~ 这里不实现修改了…
upd.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib prefix="fm" uri="http://www.springframework.org/tags/form" %> <!-- 引入Spring表单库 -->
<%
String path = request.getContextPath();
String basePath = request.getScheme() "://" request.getServerName() ":" request.getServerPort() path "/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>My JSP 'upd.jsp' starting page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->
</head>
<body>
<!-- 相当于HTML 的 form:
action/method不介绍了: action,如果不指定提交默认为提交到获取表单页面的URL (即上一个地址!)
modelAttribute: 用于表示绑定的 模型属性; 就是 Model 中存储的要修改对象; (不存在会报错)
如果不指定属性名,默认取名是 command (一般都手动取名)
而且可以同时对应 新增和修改;
新增在去新增的控制器model 这存储一个空的对象即可, 提交时候会把表单的数据存在这个属性中: 发送至控制器;
也就是说, 需要由控制器Modle给 modelAttribute 提供一个属性,
modelAttribute 提交时也把表单的数据 返回值控制器;...
-->
<fm:form action="upd" method="post" modelAttribute="user" >
<table>
<tr>
<td>名称</td>
<td>
<fm:input path="name"/>
<!-- path:属性路径,表示表单的对象属性;
如果model中存在 modelAttribute="对象属性" 且 path="又对应对象的属性" 则展示表单;
model中不存在 modelAttribute="属性名" 自动以属性名为名,提交时候把表单的值都放进入,发送至控制器;
-->
</td>
</tr>
<tr>
<td>密码</td>
<td>
<fm:input path="pwd"/>
</td>
</tr>
<tr>
<td>性别</td>
<td>
<fm:radiobutton path="sex" value="1"/>男
<fm:radiobutton path="sex" value="0"/>女
</td>
</tr>
<tr>
<td>生日</td>
<td>
<fm:input path="birthday"/> <!-- 注意看这里,Spring表单对 Date 不在是英文格式时间,而是跟随@DateTimeFormat一样了 -->
</td>
</tr>
</table>
<!--
<input type="submit" value="提交">
提交,就不提交了,不做修改了. 主要是想看看; SpringMVC的表单一些功能...;
-->
</fm:form>
</body>
</html>
ok ,就是这样了~ 部署运行即可~ Spring常用表单标签
名称 | 说明 |
---|---|
< fm:form /> | 输入框组件标签 |
< fm:password /> | 密码框组件标签 |
< fm:hidden /> | 隐藏框组件标签 |
< fm:textarea /> | 多行输入框组件标签 |
< fm:radiobutton /> | 单选框组件标签 |
< fm:checkbox /> | 复选框组件标签 |
< fm:select /> | 下拉列表组件标签 |
< fm:error /> | 显示表单数据校验所对应的错误信息 (通常可以搭配 JSR303约束使用) |
标签属性
属性 | 描述 |
---|---|
path | 属性路径,表示表单对象属性。 |
cssClass | 表单组件对应的CSS样式类名 |
cssErrorClass | 当提交表单后报错(服务端错误),采用的CSS样式类 |
cssStyle | 表单组件对应的CSS样式 |
htmlEscape | 绑定的表单属性值是否要对HTML特殊字符进行转换,默认为true |
注意: 表单组件标签也拥有HTML标签的各种属性,比如:id、onclick等等,都可以根据需要,灵活使用;
数据校验:JSR303
目前为止对于数据的验证;一般都是在前端 进行JS 表单验证; 而 一直没有加入 "服务器端的数据验证"
SpringMVC中有两种方式可以进行 数据验证:
- 利用Spring自带的验证框架
fm
- 利用JSR 303 实现; (一般都搭配 Spring框架一起使用) ,
也可以和表单框架搭配使用!
JSR 303:Java为Bean数据合法性校验所提供的标准框架
- Spring MVC支持JSR 303标准的校验框架
- JSR 303通过在Bean属性上标注校验注解指定校验规则,并通过标准的验证接口对Bean进行验证;
可以通过
http://jcp.org/en/jsr/detail?id=303
下载 JSR303 Bean Validation
注意: Spring本身没有提供JSR303的实现 实现者:Hibernate Validator , 所以必须加入 Hibernate Validator 库的 jar;
hibernate-validator-4.3.2.Final.jar
jboss-logging-3.1.0.CR2.jar
validation-api-1.0.0.GA.jar
导入这三个Jar Srping就会自动加载…
JSR 303 约束
约束 | 说明 |
---|---|
@Null | 被注释的元素必须为null |
@NotNull | 被注释的元素必须不为null |
@AssertTrue | 被注释的元素必须为 true |
@AssertFalse | 被注释的元素必须为 false |
@Min(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@Max(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@DecimalMin(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@DecimalMax(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@Size(max, min) | 被注释的元素的大小必须在指定的范围内 |
@Digits (integer, fraction) | 被注释的元素必须是一个数字,其值必须在可接受的范围内 |
@Past | 被注释的元素必须是一个过去的日期 |
@Future | 被注释的元素必须是一个将来的日期 |
继续刚才的Demo 加一个新增 表单验证;
修改实体类;
User.java
package com.wsm.entity;
import java.util.Date;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Past;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.format.annotation.DateTimeFormat;
public class User {
private Integer id;
@NotEmpty(message = "不能为空!")
@NotNull(message = "不能为Null!")
private String name;
@Length(min = 6, max = 20,message="密码的长度必须6-20之间")
private String pwd;
@DateTimeFormat(pattern="yyyy-MM-dd") //使用了注解时间参数赋值问题,注意引用jar哦~
@Past(message="年龄必须是过去时")
private Date birthday;
private Integer sex;
//get/set 方法...;
}
控制器:加两个方法:
代码语言:javascript复制 //新增控制器
//去新增
@RequestMapping("/toadd")
public String toadd(Model model){
User u = new User();
model.addAttribute("userForm",u); //因为表单中需要一个 modelAttribute="userForm" 所以存一个空的给它咯~
return "add";
}
//新增
@RequestMapping("/add")
public String add(@ModelAttribute("userForm") @Valid User user,BindingResult bindingResult){
//两个参数注解 @ModelAttribute() @Valid
// @ModelAttribute(): 将表单modelAttribute="userForm" 赋值给 user;
// @Valid <mvc:annotation-driven /> 默认装配一个 LocalValidatorFactoryBean,通过注解使SpringMVC 在数据绑定使进行 校验;
// @Valid注解标示的参数后面,必须紧挨着一个BindingResult参数,否则Spring会在校验不通过时直接抛出异常
//参数 bindingResult 是必须的,或是Error类型. 用来判断是否发送异常~ : 表单输入与实体类注解不符合;
if(bindingResult.hasErrors()){
return "add"; //有误跳转回新增页面; 由 <fm:errors path="xx" /> 展示对应的异常信息;
}
//新增就是往集合中在放一个;
users.add(user);
return "index"; //新增成功index 展示;
}
add.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib prefix="fm" uri="http://www.springframework.org/tags/form" %> <!-- 引入Spring表单 -->
<%
String path = request.getContextPath();
String basePath = request.getScheme() "://" request.getServerName() ":" request.getServerPort() path "/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>My JSP 'add.jsp' starting page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->
</head>
<body>
<fm:form action="add" method="post" modelAttribute="userForm">
<table>
<tr>
<td>编号</td>
<td>
<fm:input path="id"/>
<fm:errors path="id"></fm:errors>
</td>
</tr>
<tr>
<td>名称</td>
<td>
<fm:input path="name"/>
<fm:errors path="name"></fm:errors>
</td>
</tr>
<tr>
<td>密码</td>
<td>
<fm:input path="pwd"/>
<fm:errors path="pwd"></fm:errors>
</td>
</tr>
<tr>
<td>生日</td>
<td>
<fm:input path="birthday"/>
<fm:errors path="birthday"></fm:errors>
</td>
</tr>
<tr>
<td>性别</td>
<td>
<fm:radiobutton path="sex" value="1"/>男
<fm:radiobutton path="sex" value="0"/>女
</td>
</tr>
</table>
<input type="submit" value="提交">
</fm:form>
</body>
</html>