一、Spring MVC 验证
JSR 303 是ajvaEE6 中的一项子规范 ,叫 Bean Validation 用于对javaBean中的字段进行校验。
官方的参考实现是: Hibernate Validator ,此实现和 Hibernate ORM 没有任何关系 //http://hibernate.org/validator
Spring 3.x 也大力支持 JSR 303,使用的是 Hibernate Validator
1) 导包
4.3.1.Final 版本为例
// jboss-logging-3.1.0.CR2.jar
hibernate-validator-4.3.1.Final.jar
jboss-logging-3.1.0.jar、
validation-api-1.0.0.GA.jar
2) 配置文件
<mvc:annotation-driven validator="validator" /> <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
<property name="validationMessageSource" ref="validatemessageSource"/> //不设置则默认为classpath下的 ValidationMessages.properties
</bean> <bean id="validatemessageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="classpath:validatemessages"/> //配置验证信息资源文件的路径
<property name="fileEncodings" value="utf-8"/>
<property name="cacheSeconds" value="120"/> //缓存时间
</bean>
3) 创建校验信息用的资源文件,本例在类路径(src或config)下直接创建即可
validatemessages.properties 内容
name.not.empty=名字不能为空
age.not.inrange=年龄要在合适的范围内
email.not.correct=邮箱格式不正确
email.not.empty=邮箱不能为空
4) 在实体类上,添加校验
public class UserInfo {
private int id; @NotNull(message="用户名不能为 null")
@NotEmpty(message="{name.not.empty}")
@Pattern(regexp="13\\d{9}",message="用户名是按手机验证的格式不对")
private String userName; @Size(min=3,max=6,message="{password.not.inrange}")
private String password; @NotEmpty(message="{email.not.empty}")
@Email(message="{email.not.correct}")
private String note;
private String aihao; //也可以将规则添在get 方法上
5) 控制器
//在添加用户这个请求提交上来的时候
@RequestMapping(value="/addUser" ,method=RequestMethod.POST)
public String addUser(Model model, @Valid UserInfo user,BindingResult bResult){
if(bResult.hasErrors()){
List<ObjectError> errorList= bResult.getAllErrors();
for(ObjectError e:errorList){
System.out.println(e.getDefaultMessage());
} model.addAttribute("errorList",errorList); //把错误信息传到前台
return "user_add";
} else{
return "success";
}
}
说明: BindingResult 是Errors的子类
@Valid 和 BindingResult 的位置不要写反
@Valid 写成了 @Validated 实测也可以
6) 前端页面
<c:forEach var="e" items="${errorList }">
${e.defaultMessage}
</c:forEach> <form action="${pageContext.request.contextPath }/addUser" method="post">
账号: <input type="text" name="userName" />
密码: <input type="text" name="password" />
备注: <input type="text" name="note" />
<input type=submit value="提交" />
</form>
例子 如何详细的显示出错信息
新建页面 user_add_validate.jsp
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="x" %>
<x:form method="post" action="${pageContext.request.contextPath }/addUser">
账号:<x:input path="userName" /> <x:errors path="userName" /><br>
密码:<x:input path="password" /> <x:errors path="password" /> <br>
备注:<x:input path="note" /><x:errors path="note" /> <br>
<input type=submit value="提交" />
</x:form>
问题:
1) 如果直接访问 user_add_validate.jsp 出现错误
//java.lang.IllegalStateException: No WebApplicationContext found: no ContextLoaderListener registered?
原因是Spring环境没初始化
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
2) 需要指定 modelAttribute="user" 这个属性
<x:form method="post" action="${pageContext.request.contextPath }/addUser" modelAttribute="user" >
//上面也可以写成 commandName="user"
3) 不能直接访问 user_add_validate.jsp 要这样:
@RequestMapping(value="/addUser",method=RequestMethod.GET)
public String addUser(@ModelAttribute("user") UserInfo user){ //@ModelAttribute("user") 必加
return "user_add_validate";
}
4) 添加用户的方法要如下
@RequestMapping(value="/addUser" ,method=RequestMethod.POST)
public String addUser( @ModelAttribute("user") @Valid UserInfo user,BindingResult bResult){
if(bResult.hasErrors()){
return "user_add_validate";
}
else{
return "success";
}
}
附加:
分组校验
问题: 当一个pojo类被多个业务方法使用,但它的验证规则不同,如何处理?
可以采用分组校验
1)定义分组 (其实就是定义接口,空接口就可以,一个接口就是一个分组)
public interface IVG1, IVG2
2)在pojo类上指定校验属于哪个分组
public class UserInfo(
@NotEmpty(message="用户名不能为 null",groups={IVG1.class}) @Size(min=3,max=6,message="{password.not.inrange}",groups={IVG1.class,IVG2.class})
private String password;
...
}
3)在业务方法上指定验证的时候使用哪个分组
@RequestMapping(value="/addUser" ,method=RequestMethod.POST)
public String addUser( @ModelAttribute("user") @Validated(value=IVG1.class) UserInfo user,BindingResult bResult){
if(bResult.hasErrors()){
return "user_add_validate";
}
else{
return "success";
}
}
//注意,用的是 @Validated 注解 不是 @Valid
二、Spring MVC 数据的回显
(其实上例就是带数据回显的)
pojo类型的数据回显
@RequestMapping(value="/addUser" ,method=RequestMethod.POST)
public String addUser( @ModelAttribute("user") UserInfo user){
....
}
页面的 from上也要加 modelAttribute 或 commondName
<x:form action="${pageContext.request.contextPath }/test" method="post" modelAttribute="user">
<x:input path="userName" /> //注意,要回显示的数据必须用 这样的标签
<x:input path="password" />
</x:form>
对于简单类型
用 model.addAttribute(...) 即可
三、Spring MVC 异常处理
SpringMVC 提供全局异常处理器进行统一的异常处理
1) 在controller(Action)内部处理异常
-- login.jsp
<form action="user/login" method="post" >
账号:<input type="text" name="userName" />
密码:<input type="text" name="password" />
<input type="submit" />
-- 自定义异常类 //不定义也可以
public class MyException extends Exception{
private String msg;
public MyException(String msg){
super(msg);
this.msg=msg;
} public void writeLog(){
System.out.println("---异常信息"+this.msg+"已经记录-");
} }
-- action 中的处理
@RequestMapping(value="/login")
public String login(HttpSession session,String userName,String password) throws MyException {
UserInfo user=new UserDao().getLoginUser(userName, password);
if(user==null){
throw new MyException("用户登录失败"); //如果登录失败,抛出异常
}
session.setAttribute("user", user);
return "success";
} @ExceptionHandler(value={MyException.class}) //这里是进行异常处理的地方
public String exectionProcessAAA(MyException e,HttpServletRequest request){
request.setAttribute("e_msg", e.getMessage());
e.writeLog();
return "error" ; //全局的一个异常处理页面
}
2) 全局异常处理
--在配置文件中加入配置
<bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="cat.beans.MyException">error_process_1</prop> //可以看到,可以为每种异常指定一个result视图
<prop key="java.lang.Exception">error_process_2</prop>
<prop key="java.lang.Throwable">error_process_3</prop>
</props>
</property>
</bean>
在异常处理页面
${exception.message} //这样即可取出异常信息
四、Spring MVC 文件上传
1) 单文件上传
== 在配置文件中
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" >
<property name="defaultEncoding" value="UTF-8" /> //这里面的这些属性可以不用,一个都不写也不会出错
<property name="maxUploadSize" value="5400000" />
<property name="uploadTempDir" value="uploadTempDir" />
</bean>
== 页面
<form method="post" action="${pageContext.request.contextPath }/addUserWithImg" enctype="multipart/form-data" >
<input name="userName" />
<input name="password"/>
<input name="note"/>
<input type="file" name=upfile /> //注意,这个名字
<input type="submit" >
</form>
== 控制层
@RequestMapping(value="/addUserWithImg",method=RequestMethod.POST)
public String addUserWithImg(UserInfo user,MultipartFile upfile /*注意这个名字要和表单元素同名*/ ,HttpServletRequest request )
throws IllegalStateException, IOException{
System.out.println(upfile);
System.out.println("contentType:"+upfile.getContentType()); //image/pjepg
System.out.println("name:"+upfile.getName()); //photo
System.out.println("getOriginalFilename:"+upfile.getOriginalFilename()); //zhangsan.jpg
System.out.println("getSize:"+upfile.getSize()); new UserDao().addUser(user); String path=request.getServletContext().getRealPath("/upload_files");
File targetFile=new File(path, upfile.getOriginalFilename()); upfile.transferTo(targetFile); return "success";
}
2) 多文件上传
<form method="post" action="${pageContext.request.contextPath }/addUserWithMultImg" enctype="multipart/form-data" >
<input name="userName" />
<input name="password"/>
<input name="note"/> <input type="file" name=upfiles />
<input type="file" name=upfiles />
<input type="file" name=upfiles />
<input type="file" name=upfiles /> <input type="submit" >
</form>
//注意要加上 @RequestParam
public String ddUserWithMultImg(UserInfo user,@RequestParam("upfiles") MultipartFile [] upfiles ,HttpServletRequest request )
throws IllegalStateException, IOException{
new UserDao().addUser(user);
String path=request.getServletContext().getRealPath("/upload_files"); for(MultipartFile f:upfiles){
if(!f.isEmpty()){
File targetFile=new File(path, f.getOriginalFilename());
f.transferTo(targetFile);
}
} return "success";
}
五、Spring MVC 资源文件的引入
在配置文件中:
<mvc:resources location="/images/" mapping="/images/**" />
<mvc:resources location="/css/" mapping="css/**" />
//也可以放在一个文件夹中,统一的引入,如
<mvc:resources location="/resource/" mapping="resource/**" />
六、Spring MVC 关于json数据处理
1) 传统方式的 ajax访问和响应
//请求发起页面 ajax_test.jsp
$(function(){
$("#btn1").click(function(){
$.ajax({
url:"testAjax",
type:"post",
data:{userName:'admin',password:'123'},
async:false,
cache:false,
dataType:"json",
success:function(data){
//alert(data);
alert(data.id);
alert(data.note);
}
});
});
}); @RequestMapping("/testAjax")
public void testAjax(HttpServletRequest request,HttpServletResponse response) throws IOException{
String userName=request.getParameter("userName");
String password=request.getParameter("password");
System.out.println(userName+":"+password); response.setContentType("text/html;charset=utf-8");
//response.getWriter().print("这是普通数据"); String jsonStr="{\"note\":\"这是备注\",\"id\":\"90\"}";
response.getWriter().print(jsonStr);
}
附注:
@RequestBody 用来处理application/json 类型的请求内容
使用 @ResponseBody 的方式返回json 格式的数据
要导入包 spring mvc 4.1.4
jackson-annotations-2.4.4.jar、
jackson-core-2.4.4.jar、
jackson-databind-2.4.4.jar。
附 3.1 用的是
jackson-core-asl-1.9.11.jar
jackson-mapper-asl-1.9.11.jar
//例一 从服务端查询出一个userInfo对象返回
//服务端
@RequestMapping("/testAjax2") @ResponseBody //这里用 @ResponseBody 注解声明返回json数据
public UserInfo testAjax2(UserInfo u) {
UserInfo user=new UserDao().getLoginUser(u.getUserName(), u.getPassword());
return user;
}
//客户端
$.ajax({
url:"testAjax2",
type:"post",
data:{userName:'aaa',password:'aaa'}, //在服务端,用的是pojo类型的对象接收的参数
async:false,
cache:false,
dataType:"json",
success:function(data){
alert(data.id);
alert(data.note);
}
});
//例二 从服务端查询一组对象返回
@RequestMapping("/testAjax3") @ResponseBody
public List<UserInfo> testAjax2() {
List<UserInfo> userList=new UserDao().getAllUser();
return userList;
} $("#btn3").click(function(){
$.ajax({
url:"testAjax3",
type:"post", async:false,
cache:false,
dataType:"json",
success:function(userList){ //从服务端返回的是一组对象
$.each(userList,function(key,user){
$("#div1").append(user.id +"-"+user.userName+"-"+user.password+"<br />");
});
}
});
});
七、Spring MVC中 RESTful 风格
REST这个词,是Roy Thomas Fielding在他2000年的博士论文中提出的,HTTP协议(1.0版和1.1版)的主要设计者、Apache服务器软件的作者之一、Apache基金会的第一任主席。长期以来,软件研究主要关注软件设计的分类、设计方法的演化,很少客观地评估不同的设计选择对系统行为的影响。而相反地,网络研究主要关注系统之间通信行为的细节、如何改进特定通信机制的表现,常常忽视了一个事实,那就是改变应用程序的互动风格比改变互动协议,对整体表现有更大的影响。我这篇文章的写作目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构
REST,即Representational State Transfer的缩写。我们对这个词组的翻译是"表现层状态转化"。
如果一个架构符合REST原则,就称它为RESTful架构。关于 Representational State Transfer (表现层状态转化) 的理解。
1)REST的名称"表现层状态转化"中,省略了主语。"表现层"其实指的是"资源"(Resources)的"表现层"。
网络上的一个具体信息,可以用一个URI(统一资源定位符)指向它
2) Representation 表现层
"资源"是一种信息实体,它可以有多种外在表现形式。我们把"资源"具体呈现出来的形式,叫做它的"表现层"(Representation)。
比如,文本可以用txt格式表现,也可以用HTML格式、XML格式
URI只代表资源的实体,不代表它的形式。严格地说,有些网址最后的".html"后缀名是不必要的 因为这个后缀名表示格式,属于 "表现层" 范畴,而URI应该只代表"资源"的位置。它的具体表现形式,应该在HTTP请求的头信息中用Accept和Content-Type字段指定,这两个字段才是对"表现层"的描述。
3) State Transfer 状态转化
HTTP协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(State Transfer)。而这种转化是建立在表现层之上的,所以就是"表现层状态转化"。客户端用到的手段,只能是HTTP协议。具体来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:
GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源。
综合上面的解释,我们总结一下什么是RESTful架构:
(1)每一个URI代表一种资源;
(2)客户端和服务器之间,传递这种资源的某种表现层;
(3)客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表现层状态转化"。
http://localhost:8080/myweb/userAction_show.action?id=5
rest 风格
http://www.douban.com/photos/album/49432287/ 这是豆瓣上的url rest风格
查询用户
http://localhost:8080/myweb/user/5
删除用户
http://localhost:8080/myweb/user/5/delete
//例子 @RequestMapping("/get_user/{id}/{flag}")
public String getUser(@PathVariable("id") int idAAA, @PathVariable("flag") String flagAAA){
System.out.println(idAAA +":"+flagAAA);
return "success";
}
请求的url
http://localhost:8080/springmvc-03/get_user/10/search
如果不传 PathVariable 指定的参数,会报404
八、Spring MVC 拦截器
SpringMVC 中的Interceptor 拦截器也是相当重要和相当有用的,它的主要作用是拦截用户的请求并进行相应的处理。比如通过它来进行权限验证,或者是来判断用户是否登陆,或者是像12306 那样子判断当前时间是否是购票时间。
方式一: 用实现 HandlerInterceptor 接口的方式
方式二: 继承抽象类HandlerInterceptorAdapter (Spring 提供的类,它实现了 HandlerInterceptor)
方式三: 实现WebRequestInterceptor 接口或继承实现了该接口的类
例子:
1) 拦截器声明
public class MyInterceptor implements HandlerInterceptor {
/*==在Controller 方法被调用之前执行,一般用来进行身份验证,授权等
== SpringMVC中的Interceptor 拦截器是链式的,可以同时存在多个Interceptor,并根据声明的前后顺序依次执行
== 而且所有的Interceptor中的 preHandle 方法都会在 Controller方法调用之前调用
== SpringMVC的这种Interceptor链式结构也是可以进行中断的,preHandle的返回值为false的时候整个请求就结束了 */
public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1,
Object arg2) throws Exception {
System.out.println("preHandle");
return true;
} /* 一般在处理公用的模型数据,统一指定视图等操作的时候用
== 在preHandle方法返回值为true的时候才会执行。
== 它的执行时间是在是在Controller的方法调用之后,DispatcherServlet进行视图的渲染之前执行
== 可以对ModelAndView进行操作。
== 这个方法的链式结构跟正常访问的方向是相反的,也就是说先声明的Interceptor拦截器该方法反而会后调用,这跟Struts2里面的拦截器的执行过程有点像
*/
public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1,
Object arg2, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle");
} /*
== 在preHandle方法返回值为true的时候才会执行。
== 该方法将在整个请求完成之后,也就是DispatcherServlet渲染了视图执行,
== 这个方法的主要作用是用于清理资源的,统一的日志及异常处理等 */
public void afterCompletion(HttpServletRequest arg0,
HttpServletResponse arg1, Object arg2, Exception arg3)
throws Exception {
System.out.println("afterCompletion");
}
}
2) 在配置文件中加入配置
<mvc:interceptors>
//如果有 多个拦截器,顺序执行
<mvc:interceptor>
// 如果不配置或/*,将拦截所有的Controller
<mvc:mapping path="/searchall_forupdate" /> path 配置拦的是请求的url
//<mvc:mapping path="/**" /> 所有的url包扩子url
<bean class="cat.interceptor.MyInterceptor"></bean>
</mvc:interceptor> </mvc:interceptors>
//例子,验证用户是否登录
public class MySessionInterceptor implements HandlerInterceptor {
public void afterCompletion(HttpServletRequest arg0,
HttpServletResponse arg1, Object arg2, Exception arg3)
throws Exception { } public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1,
Object handler, ModelAndView arg3) throws Exception {} public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object obj) throws Exception {
String url=request.getRequestURI();
System.out.println(url);
if(url.contains("login")){ //如果用户发的是登录的请求,则直接放行
return true;
}
else{
UserInfo user=(UserInfo)request.getSession().getAttribute("user");
if(user!=null){
return true;
}
else{
request.setAttribute("msg", "请登录");
request.getRequestDispatcher("login.jsp").forward(request, response);
return false;
}
}
}
上面的拦截器的配置文件
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean class="cat.interceptor.MyInterceptor"></bean>
</mvc:interceptor> <mvc:interceptor>
<mvc:mapping path="/**" />
<bean class="cat.interceptor.MySessionInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>