1简单的实例
在Spring MVC中,DiapatcherServlet是处于核心的位置,它负责协调不同的组件以完成请求处理并返回响应的工作。DiapatcherServlet必须在web.xml中进行配置。
下面我们在web.xml中配置DispatchServlet来处理*.do的请求。在web.xml增加下面的配置:
如果找不到org.springframework.web.servlet.DispatcherServlet,则需要spring-webmvc.jar包。如果报jstl的异常,则导入jstl.jar包。
<!-- 配置Spring MVC -->
<servlet>
<servlet-name>Dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-name>/WEB-INF/Config.xml</param-name>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Dispatcher</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
Servlet定义
这里我们定义了请求分发Servlet,即:
org.springframework.web.servlet.DispatcherServlet
DispatcherServlet 是Spring MVC 中负责请求调度的核心引擎,所有的请求将由此Servlet 根据配置分发至各个逻辑处理单元。其内部同时也维护了一个ApplicationContext实例。
我们在<init-param>节点中配置了名为“contextConfigLocation”的Servlet参数,此参数指定了Spring配置文件的位置“/WEB-INF/Config.xml”。如果忽略此设定,则默认为“/WEB-INF/<servlet name>-servlet.xml”,其中<servlet name>以Servlet 名替换(在当前环境下,默认值也就是/WEB-INF/Dispatcher-servlet.xml)。
我们将所有以.do结尾的请求交给Spring MVC进行处理。
一个web.xml中可以配置多个DispatcherServlet,通过<servlet-mapping>的配置,让每个DispatcherServlet处理不同的请求。
Servlet拦截匹配规则可以自已定义:
当映射为@RequestMapping("/user/add.do")时:
拦截*.do,例如:/user/add.do,弊端:所有的url都要以.do结尾。不会影响访问静态文件。
拦截/app/*,例如:/app/user/add,弊端:请求的url都要包含/app,@RequestMapping("/user /add")中不须要包含/app。
拦截/,例如:/user/add,弊端:对jpg,js,css静态文件的访问也被拦截不能正常显示。
拦截/*,可以走到Action中,但转发到jsp时再次被拦截,不能访问到jsp。
一个简单的实例:
@Controller()
@RequestMapping("/user.do")
public class TestAction{
IUserService userService;
@RequestMapping(method=RequestMethod.POST)
public ModelAndView test(User user){
System.out.println("user test");
user.setCreateDate(new Date());
ModelAndView mav=new ModelAndView();
mav.setViewName("success.jsp");
mav.addObject("user",user);
userService.addOne(user);
return mav;
}
test(User user)方法被@RequestMapping(method=RequestMethod.POST)注解,表示以post方式访问web项目根路径下/user.do是就会调用此方法,默认是POST方式。如果写成@RequestMapping(value="test",method=RequestMethod.POST)则表示访问web项目根路径下/user/test.do时调用test方法。
如果写成@RequestMapping("/user")会匹配/user.*,/user路径
下面是页面提交到user.do
<%@page contentType="text/html; charset=utf-8"%>
<html>
<head>
<title>测试</title>
</head>
<body>
<form action="${pageContext.request.contextPath }/user.do" method="post">
用户名:<input type="text" name="name"/><br/>
密码:<input type="password" name="password"/><br/>
<input type="submit" name="提交"/>
</form>
</body>
</html>
表单的name属性与test方法的User参数的属性保持一致,那么Spring MVC既可以将表单组件填充到User的属性中。
2@RequestMapping
@RequestMapping使用value值指定请求的url,如果@ResourceMapping("/user"),需要注意的是@RequestMapping在类定义处制定的URL是相对于Web应用的部署路径,而在方法处指定的URL则相对于类定义处指定的URL。如果在类定义处没有标出@RequestMapping,仅在处理方法处标注@RequestMapping,则方法处指定的URL则相对于Web应用的部署路径。
@RequestMapping不但支持标注的URL,还支持?,**,*的字符:
/user/*/createUser,匹配/user/aaa/createUser,/user/bbb/createUser等URL。
/user/**/createUser,匹配/user/createUser,/user/aaa/bbb/User等URL
/user/createuser??,匹配/user/createUseraa,/user/createuserbb等URL
/user/{userId},匹配user/123,user/456等url。
/user/**/{userId},匹配user/aaa/bbb/123,user/aaa/fdf778等url。
/company/{companyId}/user/{userId}/detail,匹配company/111/user/4556/detail等url。
带占位符的url是spring3.0新增的功能,该功能在Spring mvc向REST靠近。通过@PathVariable可以将URL中的占位符参数绑定到控制器的处理方法的如参数中。
例如:当我们在页面中<form action="${pageContext.request.contextPath }/user/123.do" method="post">来请求时,{userid}对应的值就是123。
@RequestMapping(value="{userid}",method=RequestMethod.POST)
public ModelAndView test(@PathVariable("userid")String userid,User user){
@RequestMapping处理像上面那样使用请求的URL映射请求外,还可以使用请求方法,请求头参数以及请求参数映射请求。
下面的例子对请求方法为POST的,请求参数中含有userid的进行映射
@RequestMapping(value="delete",method=RequestMethod.POST,params="userid")
public ModelAndView test(@RequestParam("userid")String userid){...}
下面的例子对请求头(报文头content-type)进行映射
@RequestMapping(value="/show",headers="content-type=text/*")
public ModelAndView test2(@RequestParam("userid")String userid){...}
params和headers分别通过请求参数以及报文头属性进行映射,它们支持简单的表达式。下面以params表达式为例说明,headers可以参照params。
"param1",表示请求必须包含名为param1的请求参数。
"!param1",表示请求不能包含名为param1的请求参数。
"param1!=value1",表示请求参数包含名为param1的请求参数,但是其值不能为value1。
{"param1=value1","param2"},表示请求参数必须包含名为param1和param2的两个请求参数且param1参数值必须为value1。
请求方法签名
Spring MVC对控制器处理方法签名的限制是很宽松,可以按照自己喜欢的方式使用@PathVariabl,@RequestParam,@RequestHeader对方法参数进行注解。
3@RequestParam
@RequestParam注解得到对其对应的请求参数,@RequestParam有三个参数:
value,参数名。
required,是否必需,默认为true,表示请求参数中必须包含对应的参数名,如果不存在将抛出异常。
defaultValue,默认参数,设置该参数时,自动将required设置为false,一般不使用。
例如:下面将userName和age请求参数绑定到handle()方法的userName和age中
@RequestMapping(value="/handle1")
public String handle1(@RequestParam(value="userName",required=false)String userName,
@RequestParam("age")int age{...}
4使用@CookieValue绑定请求中的cookie值
使用@CookieValue可以让处理方法参数绑定某个cookie的值,它和@RequestParam拥有三个一样的参数。
@RequestMapping(value="/handle1")
public String handle1(@RequestParam(value="sessionId",required=false)String sessionId,
@RequestParam("age")int age{...}
5使用@RequestHeader绑定请求报文头的属性值
服务器可以据此获取客户端的信息,通过@RequestHeader就可以将报文头属性值banding到处理方法的入参中,。
@RequestMapping(value="/handle13")
public String handle13(@RequestHeader("Accept-Encoding")String encoding,
@RequestHeader("Keep-Alive")long keepAlive)
@RequestHeader和@RequestParam拥有3个一样的参数。
6使用命令/表单对象绑定请求参数值
所谓命令/表单对象并不需要实现任何接口,仅仅是一个拥有若干属性的POJO。spring MVC会按照请求参数名和命令/表单对象属性名匹配的方法,自动为该对象填充属性值。支持级联的属性,如dept.deptId,dept.address.tel等。例如User中有dept对象,dept对象有address属性,address对象有一个tel属性,那么下面的URL请求将正确的填充到User对象中。
/handle14.html?userName=tom&dept.deptId=1&dept.address.tel=102
@RequestMapping(value="/handle14")
public String handle14(User user){ ... }
7使用servlet API对象作为入参
在Spring MVC中,控制器类可以不依赖任何Servlet API对象,但是Spring MVC并不阻止我们使用Servlet API的类作为处理方法的入参,以下的处理方法都可以正确工作:
@RequestMapping(value="/handle14")
public String handle14(HttpServletRequest request,HttpServletResponse response){
String userName=WebUtil.findParamValue(request,"userName");
}
@RequestMapping(value="/handle14")
public void handle14(HttpServletResponse response,@RequestParam("userName")String userName){
response.addCookie(new Cookie("userName",userName));
}
使用Servlet API类作为入参时,SpringMVC会自动将Web层对应的servlet对象传递给处理方法的入参。它们之间的位置没有特殊要求。注意处理方法自行使用HttpServletResponse返回响应,则处理方法返回值返回成void即可。
Spring MVC在org.spring.framework.web.context.request包中定义了若干个可代理Servlet原生API类的接口,如WebRequest和NativeWebRequest,它们也允许作为处理类的入参,通过这些代理类可以访问请求对象的任何信息.如:
@RequestMapping(value="/handle25")
public void handle14(WebRequest request){
String userName=request.getParameter("userName");
}
使用I/O对象作为入参数
servlet的serveltRequest拥有getInputStream()和getReader()方法,可以通过它们读取请求的信息。相应Servlet的ServletRequest拥有getOutputStream()和getWriter()方法,可以通过它们输出响应信息。Spring MVC允许控制器的处理方法使用java.io.InputStream/java.io.Reader以及java.io.OutputStream/java.io.Writer作为方法入参:
@RequestMapping(value="/handle31")
public void handle14(OutputStream os)Throws IOException{
Resource res=new ClassPathResource("/image.jpg");//读取类路径下的图片文件
FileCopyUtils.copy(res.getInputStream(),os);//将图片写到输出流中
}
Spring控制器处理方法的入参除支持以上类型的参数还支持java.util.Locale,java.security.Principal,可以通过servlet的HttpServletRequest的getLocale()以及getUserPrincipal()得到相应的值。如果处理方法的入参类型为Locale或Principal,Spring MVC自动从请求对象中获取相应的对象并传递给处理方法的入参。
@RequestMapping(value="/handle32")
public void handle31(Locale locale)throws IOException{
...
}
@RequestBody和@ResponseBody
将HttpServletRequest的getInputStream()内容绑定到入参;将处理方法返回值写入到HttpServletResponse的getOutputStream()中。
@RequestMapping(value="/handle41.do")
public String handle41(@RequestBody String requestBody){
System.out.println(requestBody);
return "success";
}
@ResponseBody
@RequestMapping(value="/handle42/{imageId}")
public byte[] handle42(@PathVarable("imageId")String imageId)throws IOException{
System.out.println("load image of"+imageId);
Resource res=new ClassPathResource("/image.jpg");
byte[] fileData=FileCopyUtils.copyToByteArray(res.getInputStream());
return fileData;
}
返回json格式的数据:
@RequestMapping(value="tt")
@ResponseBody
public String tt(HttpServletRequest request,HttpServletResponse response){
List<User> lu=new ArrayList<User>();
for(int i=0;i<100;i++){
User user=new User();
user.setName("tt"+i);
user.setPassword("pp"+i);
user.setSex("male");
user.setUserID("id"+i);
user.setCreateDate(new Date());
lu.add(user);
}
Map<String,Object> data=new HashMap<String,Object>();
data.put("total", lu.size());
data.put("rows", lu);
JSONObject jsonObject = JSONObject.fromObject(data);
return jsonObject.toString();
}
//通过HttpServletResponse返回服务端响应
public String tt(HttpServletRequest request,HttpServletResponse response){
...
try{
response.setContentType("text/html");
response.getWriter().println("saveSucess");
}catch(IOException e){
}
}
8处理模型数据
ModelAndView
如何将模型数据暴露给视图是Spring MVC框架的一项重要工作,Spring MVC提供了多种途径输出模型数据:
ModelAndView,处理方法返回值类型为ModelAndView时,方法体即可通过该对象添加模型数据。
控制器处理方法返回值如果为ModelAndView,则其及包括视图信息,也包含模型数据信息,它是为了方便一次返回这两个对象,Model与View两者仍是分离的。
最简单的ModelAndView是持有View的名称返回,之后View名称被view resolver,也就是实作org.springframework.web.servlet.View接口的实例解析。
ModelAndView(String viewName)
例如:
@RequestMapping(value="test.do",method=RequestMethod.POST)
public ModelAndView test(@ModelAttribute("user")User user){
user.setCreateDate(new Date());
....
//相对于web项目根路径,直接return "/success.jsp"是一样的
return new ModelAndView("/success.jsp");
}
如果您想要返回Model对象,则可以使用Map来收集这些Model对象,然后设定给ModelAndView,使用下面的构造方法
ModelAndView(String viewName,Map Model)
例如:
@RequestMapping(value="test.do",method=RequestMethod.POST)
public ModelAndView test(User user){
user.setCreateDate(new Date());
Map<String,Object> datamap=new HashMap<String,Object>();
datamap.put("user", user);
return new ModelAndView("/success.jsp",datamap);
}
Map对象中设定好key与value值,之后可以在视图中取出,如果您只是要返回一个Model对象,则可以使用下面这个ModelAndView构造方法:
ModelAndView(String viewName, String modelName, Object modelObject)
通过modelName,您可以在视图中取出Model并显示。
ModelAndView(String viewName, Map model)
如果您要返回Model对象,则可以使用Map来收集这些Model对象,然后设定给ModelAndView。
ModelAndView(String viewName)
最简单的ModelAndView是持有View的名称返回,之后View名称被view resolver,也就是实现org.springframework.web.servlet.View接口的实例解析,例如 InternalResourceView或JstlView等等
ModelAndView(View view)
ModelAndView类别提供实作View接口的对象来作构造函数的参数。
ModelAndView()
这个构造方法构造出来的ModelAndView ,不能直接使用,应为它没有指定view,也没有绑定对应的model对象。当然,model对象不是必须的,但是view确实必须的。
在处理方法的方法体中,可以使用如下方法添加模型数据:
ModelAndView addObject(String attributeName,Object attributeValue);
ModelAndView addAllObjects(Map<String,?> modelMap>
可以通过如下方法设置视图:
void setView(View view),指定一个具体的视图对象。
void setViewName(String viewName),指定一个逻辑视图名。
@ModelAttribute
方法入参数标注该注解后,入参的对象就会与ModelMap属性绑定。
如果希望将方法入参对象添加到模型中,仅仅需要在相应入参前使用@ModelAttribute注解即可。如果@ModelAttribute没有制定值,则默认以入参类名的第一个字母小写作为key。
@RequestMapping(value="/handle61")
public String handle61(@ModelAttribute("user")User user){
user.setuserId("1000");
return "/user/createSuccess.jsp";//相对于web项目根路径
}
Spring MVC将消息绑定到User对象中,然后再以"user"为键将User对象放到模型中,在贮备对视图进行渲染前,SpringMVC还会近一步将模型中数据转储到视图的上下文中以暴露给视图对象。对于JSP视图来说,SpringMVC会将模型数据转储到ServeletRequest的属性列表中。
除了可以在方法入参上使用@ModelArribute注解,还可以在方法定义中使用@ModelAttribute注解。Spring MVC在调用目标处理方法前,会先逐个调用该方法级上标注了@ModelAttribute的方法,并将这些方法返回值添加到模型中,下面在方法级上使用@ModelAttribute注解:
@ModelAttribute("user")
public User getuser(){
User user=new User();
user.setUserId("10001");
return user;
}
在访问UserAction中任何一个请求处理方法前,Spring MVC先执行该方法,并将返回值以user为键添加到模型中。然后在调用handle61时将其映射到入参中,然后再根据Http请求消息进一步填充覆盖user对象。
@RequestMapping(value="/handle2")
public String handle61(@ModelAttribute("user")User user){
user.setUserName("tom");
return "/user/crateSuccess.jsp";//相对于web项目根路径
}
Map及Model
入参为org.springframework.ui.Model、org.springframework.ui.ModelMap或java.util.Map时,处理方法返回时,Map中的数据自动保存到request对象中,作用类似于request对象的setAttribute方法的作用
例如:
@RequestMapping(value="test.do",method=RequestMethod.POST)
public String test(User user,ModelMap model){
user.setCreateDate(new Date());
//将user保存到ModelMap中,然后在页面就可用通过${user.name}来取出数据
model.addAttribute("user",user );
System.out.println("model map testdd");
return "/success.jsp";
}
@SessionAttributes
ModelMap 中的属性作用域是 request 级别是,也就是说,当本次请求结束后,ModelMap 中的属性将销毁。如果希望在多个请求中共享 ModelMap 中的属性,必须将其属性转存到 session 中,这样 ModelMap 的属性才可以被跨请求访问。
Spring 允许我们有选择地指定 ModelMap 中的哪些属性需要转存到 session 中,以便下一个请求属于对应的 ModelMap 的属性列表中还能访问到这些属性。
这一功能是通过类定义处标注 @SessionAttributes 注解来实现的。
@SessionAttributes 只能声明在类上,而不能声明在方法上。
@SessionAttributes("currUser") // 将ModelMap 中属性名为currUser 的属性
@SessionAttributes允许指定多个属性。你可以通过字符串数组的方式指定多个属性
@SessionAttributes({"attr1","attr2"})
@SessionAttributes 还可以通过属性类型指定要 session 化的 ModelMap 属性
@SessionAttributes(types = User.class)
@SessionAttributes(types = {User.class,Dept.class})
还可以联合使用属性名和属性类型指定:
@SessionAttributes(types = {User.class,Dept.class},value={"attr1","attr2"})
例如:下面的类中,当访问/user/test.do时,我们将ModelMap中的user对象存放到session中,然后访问/user/tt.do是将session中的user对象取出来
@Controller()
@RequestMapping("/user")
@SessionAttributes("user")
public class TestAction{
@RequestMapping(value="test.do",method=RequestMethod.POST)
public String test(User user,ModelMap model){
user.setCreateDate(new Date());
model.addAttribute("user",user );
System.out.println("model map testdd");
return "/success.jsp";
}
@RequestMapping(value="tt")
public void tt(@ModelAttribute("user")User user){
System.out.println(user.getName()+"/"+user.getPassword()+"/"+user.getCreateDate());
}
}
SpringMVC 文档提到了@SessionAttributes 和 @ModelAttribute配合使用可以往 Session 中存或者从 Session 中取指定属性名的具体对象。 在需要访问 Session 属性的 controller 上加上 @SessionAttributes,然后在 action 需要的 User 参数上加上 @ModelAttribute,并保证两者的属性名称一致。SpringMVC 就会自动将 @SessionAttributes 定义的属性注入到 ModelMap 对象,在 setup action 的参数列表时,去 ModelMap 中取到这样的对象,再添加到参数列表。只要我们不去调用 SessionStatus 的 setComplete() 方法,这个对象就会一直保留在 Session 中,从而实现 Session 信息的共享。
采用@SessionAttributes(types={指定class})
采用这种方式的话系统会默认保存指定class的名字(头字母小写),如UserBean.class,modelmap会仅仅会保存名字为 userBean或者userbean的对应的class,其余则不会保存,
重定向
return "redirect:/user/crateSuccess.jsp";
转发
return "forward:/user/crateSuccess.jsp";
9数据校验
Spring3.0拥有自己独立的数据校验框架,同时支持JSR303标准的校验框架,Spring的DataBinder在进行数据绑定时,可同时调用校验框架完成数据校验工作。在Spring MVC中,可以直接通过注解驱动的方式进行数据校验。
注意Spring本身没有提供JSR303的实现,所以必须将JSR303的实现者(如hibernate validator)的jar文件放到类路径下,Spring将自动加载并装配好JSR303的实现者。 将hibernate-validator与spring mvc3结合需要添加以下jar:
hibernate-validator-4.2.0.Final.jar
log4j-1.2.16.jar
slf4j-api-1.5.10.jar
slf4j-log4j12-1.5.10.jar
validation-api-1.0.0.GA.jar
以下常用JSR 303 规范验证,可参考hibernate-validator
@Null,被注解的元素必须为null。
@NotNull,被注解的元素必须不为null。
@AssertTrue,被注解的元素必须为true。
@AssertFalse,被注解的元素必须为false。
@Min(value=)/DecimalMin(value=),被注解的元素必须是一个数字,其值必须大于等于制定的最小值。
@Max(value=)/DecimalMax(value=),被注解的元素必须是一个数字,其值必须小于等于制定的最大值
@Size(max=, min=) ,被注解的String, Collection, Map and arrays元素的大小必须在指定的范围内(Min<=Size<=max )。
@Digits(Integer,fraction),被注解的元素必须是一个数字,其值必须在可接受的范围内。
@Past,被注解的元素必须是一个过去的日期。
@Future,被注解的元素必须是一个将来的日期。
@Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式。
@Pattern(regexp = "[A-Za-z0-9]{5,20}", message = "请输入字母和数字,长度为5-20")
Hibernate Validator 附加的 constraint
@NotBlank(message =) 验证字符串非null,且长度必须大于0
@Email 被注释的元素必须是电子邮箱地址
@Length(min=,max=) 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range(min=,max=,message=) 被注释的元素必须在合适的范围内
@Url 被注释的元素必须是url地址
@Valid(对象)
在一个关联对象上递归的执行检验.如果对象是一个数组或者集合,对象将被递归的检验. 如果对象是一个map,元素将被递归的验证.
<mvc:annotation-driven/>会默认装配好一个LocalValidatorFactoryBean,通过在处理方法的入参上标注@Valid注解就可以让Spring MVC在完成数据绑定后执行数据校验的工作。
public Class User{
//用户名称
@NotBlank(message="用户名不能为空")
@Column(nullable=false,length=32)
private String name;
@DateTimeFormat(pattern="yyyy-MM-dd")
@Temporal(TemporalType.TIMESTAMP)
@Column(nullable=true)
private Date createDate;
//没有给出message属性的值,会按照hiberate-validator.jar中的org\hibernate\validator\ValidationMessages_zh_CN.properties文件中的默认信息
//用户密码
@NotBlank
@Column(nullable=false,length=32)
private String password;
}
@Controller()
@RequestMapping("/user.do")
@SessionAttributes("user")
public class TestAction{
public String test(@Valid User user,BindingResult userBindingResult,ModelMap model){
user.setCreateDate(new Date());
model.addAttribute("user",user );
System.out.println("valid dd testdd");
//判断是否验证失败
if(userBindingResult.hasErrors()){
model.addAttribute("user",user);
//得到所有验证失败的字段
List<FieldError> ls=userBindingResult.getFieldErrors();
FieldError fe;
String field;
String errorMessage;
for (int i = 0; i < ls.size(); i++) {
fe=ls.get(i);
field=fe.getField();//得到哪个字段验证出错
errorMessage=fe.getDefaultMessage();//得到错误消息
System.out.println("错误字段消息:"+field +" : "+errorMessage);
}
return "/index.jsp";
}else{
return "/success.jsp";
}
}
在已经标注了JSR303注解的表单/命令对象前标注一个@Valid,Spring MVC框架在将请求数据绑定到该入参对象后,就会调用校验框架根据注解声明的校验规则实施校验.
Spring MVC是通过对处理方法签名的规约来保存校验结果的:前一个表单/命令对象的校验结果保存在其后的入参中,这个保存校验结果的入参必须是BindingResult或Errors类型,这两个类都位于org.springframework.validation包中。如:
public String handle91(@Valid User user,BindingResult userBindingResult,String sessionId,ModelMap mm,@Valid Dept dept,Errors deptErrors){
...
}
前两个参数是User和其绑定结果的对象。后两个参数是Dept和其校验的结果对象。
spring将绑定错误信息,验证错误信息都保存到隐含模型中,然后我们在页面上取出验证错误信息,这里的modelAttribute="user"(或者是commandName="user")是必须的,否则页面取不到错误信息。BindingResult有一个ObjectName属性用来binds list of errors with commandName in your jsp。
那么如何设置modelAttribute和commandName的值呢,它们的值应该与传递到页面的命令/表单对象的名称name属性一致。上面的例子传递到页面的命令/表单名称是user。<form:form action="${ctx }/user/test.do" method="post" modelAttribute="user">
<!-- 在用户名后面显示验证错误信息 -->
用户名:<input type="text" name="name"/><form:errors path="name" cssClass="error"/><br/>
密码:<input type="password" name="password"/><br/>
<input type="submit" name="提交"/><br/>
<form:errors path="*"/>
</form:form>
通过path和表单对象特定属性错误信息进行绑定,一个表单对象属性可能包括一个或多个错误信息,也可以没有错误信息,<form:errors>会根据错误信息的情况进行合理的展示。此外,path还支持通配符匹配的表示方式:
path="*": 显示所有的错误信息;
path="lastName*": 显示所有属性名前缀为lastName的错误信息。
Spring中的用于格式化的annotation
Spring 3 提供了两个可以用于格式化数字、日期和时间的注解@NumberFormat和@DateTimeFormat,这两个标签可以用于bean的属性或方法参数上。@NumberFormat可以用来格式化任何的数字的基本类型(如int,long)或java.lang.Number的实例(如 BigDecimal, Integer)。@DateTimeFormat可以用来格式化java.util.Date、java.util.Calendar和 java.util.Long类型,也可以用于Joda Time类型的字段或参数。(Joda Time是一个开源的包,提供了对date和time类的一些替代类)。
要指定数字或日期/时间类型的属性,只需要在其上添加 @NumberFormat或@DateTimeFormat注解接可以了。例如下面的代码:
/**
* numeric fields using @NumberFormat annotation for formatting.
*/
@NumberFormat(style = Style.CURRENCY)//将输出$30,100.50;
private double salary= 30100.50;
@NumberFormat(style = Style.PERCENT)//将输出2%
private double w4AdditionalWithdraw= 0.02;
@NumberFormat//将输出5
private int dependents=5;
@NumberFormat(pattern = "0.00")//将输出0.10
private BigDecimal visualAcuity= new BigDecimal(".1");
注解的属性决定了@NumberFormat和@DateTimeFormat注解怎样格式化相应的属性。 @NumberFormat注解有两个可选的属性:style和pattern。style属性是一个NumberFormat.Style枚举值,可以是以下的三个值之一:
NumberFormat.Style枚举值取值为NUMBER,CURRENCY,PERCENT。默认为NUMBER。
一个double类型的字段,如果style是CURRENCY,那么在en-us的区域显示的时候前面会加上$,在zh-cn的区域显示的时候前面会加上¥。可以使用pattern属性来指定特殊的输出格式。Pattern的值要遵循Java标准的numeric formatting pattern。对于@NumberFormat来说缺省的是没有pattern的。
/**
* date and time fields using @DateTimeFormat annotation for formatting.
*/
@DateTimeFormat(style = "M-")//如Aug 30, 1964
private Date birthDate;
@DateTimeFormat(pattern = "w:yyyy")//如(week:year)-> 16:2012
private Calendar hireDate;
@DateTimeFormat(style = "-S")//如8:0 AM
private LocalTime startTime;
@DateTimeFormat(iso = ISO.DATE_TIME)//如2000-10-31 01:30:00.000-05:00.
private long lastTimeEntry;
@DateTimeFormat 注解有3个可选的属性:style,pattern和iso。属性style允许我们使用两个字符的字符串来表明怎样格式化日期和时间。第一个字符表明了日期的格式,第二个字符表明了时间的格式。下面的表格中列出了可用的选择以及相应的输出的例子:
style 属性允许我们使用两个字符的字符串来表明怎样格式化日期和时间。第一个字符表明了日期的格式,第二个字符表明了时间的格式。下面的表格中列出了可用的选择以及相应的输出的例子:
描述 字符串值 示例输出
短格式(这是缺省值) SS 8/30/64 11:24 AM
中等格式 MM Aug 30, 1964 11:24:41 AM
长格式 LL August 30, 1964 11:24:41 AM CDT
完整格式 FF Sunday, August 30, 1964 11:24:41 AM CDT
使用短横线省略日期或时间 M- Aug 30, 1964
Pattern 属性允许我们使用自定义的日期/时间格式。该属性的值遵循java标准的date/time格式规范。缺省的该属性的值为空,也就是不进行特殊的格式化。
ISO枚举值 输出
DATE 2000-10-31
TIME 01:30:00.000-05:00(最后的是时区)
DATE_TIME 2000-10-31 01:30:00.000-05:00.
NONE 不进行ISO标准的格式化
要使用上面介绍的格式化注解,需要进行如下的配置:
1.下载Spring 3.0的jar包并加入到类路径中。
2. 将下面的配置信息加入到spring bean的XML配置文件中。
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd"> <mvc:annotation-driven /> </beans>
解析参数
我们也可以使用这些注解来告诉Spring怎样解析输入的数据。下面的例子显示了怎样使用@DateTimeFormat来解析以ISO格式输入的时间信息:
public String getEmployeesOnTheClock(@DateTimeFormat(iso=ISO.TIME) java.util.Date time) { ... }
10Spring与json
Spring在解析视图的时候有两个重要的接口:ViewResolver和View,ViewResolver 负责怎么去解析, 而View只代表一种视图层的技术。对于一个请求,对于一个请求,应该什么样的视图是ViewResolver来决定的,spring3.0提供的 ViewResolver 包括 AbstractCachingViewResolver,XmlViewResolver,ResourceBundleViewResolver,UrlBasedViewResolver,InternalResourceViewResolver等,我们平时使用ResourceBundleViewResolver或者InternalResourceViewResolver来返回JSP页面,他们就是其中的两个 ViewResolver 。大部分的ViewResolver实现类,除了org.springframework.web.servlet.view.BeanName-ViewResolver是直接实现ViewResolver接口,都直接或者间接继承自org.springframe work. web.servlet.view.AbstractCachingViewResolver。
UrlBasedViewResolver
UrlBasedViewResolver(它们都直接地或者间接地继承自该类)。使用该类别的ViewResolver,我们不需要为它们配置具体的逻辑视图名到具体View的映射关系。通常只要指定一下视图模板所在的位置,这些ViewResolver就会按照逻辑视图名,抓取相应的模板文件、构造对应的View实例并返回。之所有又将它们称之为面向单一视图类型的ViewResolver,是因为该类别中,每个具体的ViewResolver实现都只负责一种View类型的映射,ViewResolver与View之间的关系是一比一。比如,我们之前一直使用的InternalResourceView- Resolver,它通常就只负责到指定位置抓取JSP模板文件,并构造InternalResourceView类型的View实例并返回。而VelocityViewResolver则只关心指定位置的Velocity模板文件(.vm),并会将逻辑视图名映射到视图模板的文件名,然后构造VelocityView类型的View实例返回,诸如此类。
属于该类别的主要ViewResolver实现类为如下几个:
org.springframework.web.servlet.view包下有好几个ViewResolver实现,
InternalResourceViewResolver为UrlBasedViewResolver的子类,spring默认采用InternalResourceViewResolver,配置文件如下:
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"><value>org.springframework.web.servlet.view.JstlView</value></property>
<property name="prefix"><value>/WEB-INF/jsp/</value></property>
<property name="suffix"><value>.jsp</value></property>
</bean>
上面的配置表示,将在Controller返回的ModelAndView的基础上,加上目录前缀/WEB-INF/jsp/,
加后文件名称后缀.jsp,由此返回的页面如/WEB-INF/jsp/showCats.jsp
ResourceBundleViewResolver,
ResourceBundleViewResolver。ResourceBundleViewResolver构建在ResourceBundle上,继承了ResourceBundle国际化支持的能力,也是所有的ViewResolver实现类中唯一提供视图国际化支持的ViewResolver。ResourceBundleViewResolver管理的视图的逻辑名称与具体视图的映射关系保存在properties文件中。比如配置文件如下:
<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
<property name="basename"><value>views-countries</value></property>
<property name="defaultParentView"><value>modelView</value></property>
</bean>
basename 的配置表示将从classes目录下的views-countries.properties文件中获取资源,这样
我们可以很方便的实现国际化与本地化;defaultParentView表示所有的view都将从modelView中继承。如果我们没有指定properties配置文件从何处加载的话,ResourceBundleViewResolver默认将从classpath的根路径加载以views为basename的properties文件。
XmlViewResolver
XmlViewResolver与ResourceBundleViewResolver之间最主要的区别就是,它们所采用的配置文件格式不同。
<bean id="xmlViewResolver" class="org.springframework.Web.servlet.view.XmlViewResolver">
<property name="location" value="classpath:views.xml"/>
</bean>
该配置表示XmlViewResolver将从Classpath的根路径加载名为views.xml的配置文件。
FreeMarkerViewResolver/VelocityViewResolver。FreeMarkerViewResolver和Velo- cityViewResolver分别负责对应FreeMarkerView和VelocityView类型视图的查找工作,它们将根据逻辑视图名到指定的位置获取对应的模板文件,并构造FreeMarkerView和VelocityView的实例返回给DispatcherServlet使用。
JasperReportsViewResolver。JasperReportsViewResolver只关心根据逻辑视图名到指定位置查找JasperReport类型模板文件,并返回AbstractJasperReportsView的具体子类型View实例,例如JasperReportsCsvView或者JasperReportsHtml
XsltViewResolver。只负责根据逻辑视图名查找并返回XsltView类型的View实例。
启用以上这些ViewResolver,与使用InternalResourceViewResolver一样简单。最基本的方法是,使用prefix属性指定模板所在路径,使用suffix属性指定模板文件的后缀名。这样,在获取逻辑视图名之后,相应的ViewResolver内部就能够根据[prefix]+viewName+[suffix]这样的URL找到对应的模板文件,并构造对应的View实例而返回了。下面给出了针对VelocityViewResolver的配置代码示例:
<bean id="viewResolver" class="org.springframework.Web.servlet.view.velocity.VelocityViewResolver">
<property name="prefix" value="../velocity/"/>
<property name="suffix" value=".vm"/>
</bean>
现在DispatcherServlet对视图的请求将会由VelocityViewResolver接管,Velocity- ViewResolver将根据传入的逻辑视图名,到指定目录下查找.vm类型的Velocity模板文件,并构造VelocityView实例返回给DispatcherServlet使用。
ContentNegotiatingViewResolver
这个类它实现了ViewResolver。但它并不直接解析视图,而是委托给别人。默认情况,它是从spring 上下文,查找视图解析器,并调用这些解析器。也可以在初始化这个bean的时候,设置它的解析器属性(viewResolvers),这是个list类型的属性。
请注意,要让这个视图解析器正常工作,需要设置比别人更高的优先级(默认为Ordered.HIGHEST_PRECEDENCE)。
RESTful服务中很重要的一个特性即是同一资源,多种表述我们使用ContentNegotiatingViewResolver就可以做到,如下所示:
使用扩展名
http://www.test.com/user.xml 呈现xml文件
http://www.test.com/user.json 呈现json格式
http://www.test.com/user 使用默认view呈现,比如jsp等
使用http request header的Accept
GET /user HTTP/1.1
Accept:application/xml
GET /user HTTP/1.1
Accept:application/json
根据请求的accpet,来返回信息。
由于浏览器的差异,发送上来的Accept Header头将是不一样的. 将导致服务器不知要返回什么格式的数据给你.
StringHttpMessageConverter一文说到的@ResponseBody String返回类型,google chrome下会有问题就是这个原因。
firefox IE6-7由于第一个accept是text/html,StringHttpMessageConverter处理的还是text/html。
所以我们将不建议根据请求的accpet来返回信息。
使用参数
http://www.test.com/user?format=xml
http://www.test.com/user?format=json
如何使用ContentNegotiatingViewResolver,假设我们有这么一个目标:
/user/{userid}.json 用于返回一个描述User的JSON
/user/{userid} 用于返回一个展示User的JSP页面
/user/{userid}.xml 用于返回一个展示User的XML文件
这里是是否启用扩展名支持,默认就是true
例如 /user/{userid}.json
<property name="favorPathExtension" value="true"></property>
这里是是否启用参数支持,默认就是true
例如 /user/{userid}?format=json
<property name="favorParameter" value="false"></property>
不启动参数,现在很多open API是使用这种方式,但可能由于要编写的字符较多(占用更多带宽),所以较少使用.
这里是否忽略掉accept header,默认就是false
例如 GET /user HTTP/1.1
Accept:application/json
<property name="ignoreAcceptHeader" value="true"></property>
我们的例子是采用.json , .xml结尾的,所以关掉两个。
这里是扩展名到mimeType的映射,
例如 /user/{userid}.json 中的.json就会映射到 application/json
<property name="mediaTypes">
<map>
<entry key="json" value="application/json" />
<entry key="xml" value="application/xml" />
</map>
</property>
ContentNegotiatingViewResolver是根据客户提交的MimeType(如 text/html,application/xml)来跟服务端的一组viewResover的MimeType相比较,如果符合,即返回 viewResover的数据.
而 /user/123.xml, ContentNegotiatingViewResolver会首先将 .xml 根据mediaTypes属性将其转换成 application/xml,
spring3.0中提供了一种View来支持JSON:MappingJacksonJsonView。
为了得到服务器端返回的json,需要使用MappingJacksonJsonView,这个view使用jackson lib将java对象转换成json,我们需要jackson-mapper-lgpl-1.4.5.jar和jackson-core-lgpl-1.4.5.jar的jar包。注意使用其他高版本的jackson-all包有问题。
首先我们在spring的配置文件添加如下配置,我们启用参数支持:
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<!-- true,开启扩展名支持,false关闭支持 -->
<property name="favorPathExtension" value="false" />
<!-- 用于关闭参数/userinfo/123?format=json的支持 -->
<property name="favorParameter" value="true" />
<!--如果所有的mediaType都没匹配上,就会使用defaultContentType-->
<property name="defaultContentType" value="text/html" />
<!--
参数值至mimeType的映射,即 /userinfo/123?format=json json是key,application/json就是value
暂时只支持json和xml
-->
<property name="mediaTypes">
<map>
<entry key="json" value="application/json" />
<entry key="xml" value="application/xml" />
</map>
</property>
<property name="viewResolvers">
<list>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/"/>
<property name="suffix" value=".jsp"/>
</bean>
</list>
</property>
<property name="defaultViews">
<list>
<bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView">
<property name="prefixJson" value="true"/>
</bean>
</list>
</property>
</bean>
如下是spring的controller层的方法,返回一个List<User>到response中:
@RequestMapping(value="tt")
@ResponseBody
public List<User> getJson(HttpServletRequest request,HttpServletResponse response,ModelMap modelmap){
List<User> lu=new ArrayList<User>();
for(int i=0;i<15;i++){
User user=new User();
user.setName("tt"+i);
user.setPassword("pp"+i);
user.setSex("male");
user.setUserID("id"+i);
user.setCreateDate(new Date());
lu.add(user);
}
return lu;
}
然后我们使用ajax请求getJson方法得到List<User>转换后的json对象,使用JSON.stringify转换成字符串alert出来:
function tt(){
$.ajax({
type: "POST",
url: "${ctx}/user/tt.do?format=json",
success: function(msg){
alert(JSON.stringify(msg));
}
});
}
<input type="button" value="json" οnclick="tt()"/>
使用实例:
@RequestMapping(value="/menu.do",params="deleteMenu")
@ResponseBody
Map<String,String> deleteMenu(String code,HttpServletRequest req, HttpServletResponse resp){
Menu menu=menuDao.findOneByCode(code);
Map<String,String> data=new HashMap<String,String>();
if(menu!=null){
List<Menu> nextMenu=menuDao.findNextMenuByCode(code);
if(nextMenu.size()>0){
data.put("code", "error");
data.put("info", "不能删除该菜单,该菜单下还有子菜单!菜单名称:"+menu.getMenuName());
}else{
//menuDao.removeOneById(menu.getMenuID());
data.put("code", "success");
data.put("info", "成功删除该菜单!菜单名称:"+menu.getMenuName());
}
}else{
data.put("code", "noExist");
data.put("info", "对不起,系统不存在该菜单!");
}
return data;
}
请求上面的方法:
jQuery.getJSON("${ctx}/menu.do?deleteMenu&format=json&code=" + menuCode, function(data) {
alert(JSON.stringify(data));
//结果:{"code":"success","info":"成功删除该菜单!菜单名称:我的办公室"}
});
10spring mvc拦截器
在controller层,Spring为我们提供了org.springframework.web.servlet.handler.HandlerInterceptorAdapter这个适配器,继承此类,可以非常方便的实现自己的拦截器。
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
}
public void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
分别实现预处理、后处理(调用了Service并返回ModelAndView,但未进行页面渲染)、返回处理(已经渲染了页面)
在preHandle中,可以进行编码、安全控制等处理;这个方法在业务处理器处理请求之前被调用,在该方法中对用户请求request进行处理。如果程序员决定该拦截器对请求进 行拦截处理后还要调用其他的拦截器,或者是业务处理器去进行处理,则返回true;如果程序员决定不需要再调用其他的组件去处理请求,则返回false。
在postHandle中,有机会修改ModelAndView;这个方法在业务处理器处理完请求后,但是DispatcherServlet向客户端返回请求前被调用,在该方法中对用户请求request进行处理。
在afterCompletion中,可以根据ex是否为null判断是否发生了异常,进行日志记录。这个方法在DispatcherServlet完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作。
下面ControllerInterceptor 继承自HandlerInterceptorAdapter
public class ControllerInterceptor extends HandlerInterceptorAdapter {
/**
* 在Controller方法前进行拦截
*/
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
System.out.println("ControllerInterceptor.preHandle()");
return true;
}
/**
* This implementation is empty.
*/
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("ControllerInterceptor.postHandle()");
}
/**
* 在Controller方法后进行拦截
*/
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("ControllerInterceptor.afterCompletion()");
}
}
<bean id="controllerInterceptor" class="com.web.spring.mvc.interceptor.ControllerInterceptor"/
发起请求,进入拦截器链,运行所有拦截器的preHandle方法:
当preHandle方法返回false时,从当前拦截器往回执行所有拦截器的afterCompletion方法,再退出拦截器链。
当preHandle方法全为true时,执行下一个拦截器,直到所有拦截器执行完。再运行被拦截的Controller。然后进入拦截器链,运行所有拦截器的postHandle方法,完后从最后一个拦截器往回执行所有拦截器的afterCompletion方法.
当有拦截器抛出异常时,会从当前拦截器往回执行所有拦截器的afterCompletion方法
xml配置:
<bean id="handlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="interceptors">
<list>
<ref bean="controllerInterceptor" />
</list>
</property>
<property name="mappings">
<props>
<prop key="/hao/hello.do">helloWorldController</prop>
</props>
</property>
</bean>
关于spring拦截器配置:
如果基于xml配置使用Spring MVC,可以利用SimpleUrlHandlerMapping、BeanNameUrlHandlerMapping进行Url映射(相当于struts的path映射)和拦截请求(注入interceptors),如果基于注解使用Spring MVC,可以使用DefaultAnnotationHandlerMapping注入interceptors。注意无论基于xml还是基于注解(指的是spring的controller是配置还是注解),HandlerMapping bean(如上面的ControllerInterceptor)都是需要在xml中配置的。
在这个例子中,我们假设UserController中的注册操作只在9:00-12:00开放,那么就可以使用拦截器实现这个功能。
public class TimeInterceptor extends HandlerInterceptorAdapter {
//继承HandlerInterceptorAdapter类
private int openingTime; //openingTime 属性指定上班时间
private int closingTime; //closingTime属性指定下班时间
private String outsideOfficeHoursPage;
//outsideOfficeHoursPage属性指定错误提示页面的URL
public void setOpeningTime(int openingTime) {
this.openingTime = openingTime;
}
public void setClosingTime(int closingTime) {
this.closingTime = closingTime;
}
public void setOutsideOfficeHoursPage(String outsideOfficeHoursPage) {
this.outsideOfficeHoursPage = outsideOfficeHoursPage;
}
//重写 preHandle()方法,在业务处理器处理请求之前对该请求进行拦截处理
public boolean preHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler)
throws Exception {
Calendar cal = Calendar.getInstance();
int hour = cal.get(Calendar.HOUR_OF_DAY); //获取当前时间
if (openingTime<=hour && hour<closingTime) { //判断当前是否处于工作时间段内
return true;
} else {
response.sendRedirect(outsideOfficeHoursPage); //返回提示页面
return false;
}
}
}
上面的代码重载了preHandle()方法,该方法在业务处理器处理请求之前被调用。在该方法中,首先获得当前的时间,判断其是否在 openingTime和closingTime之间,如果在,返回true,这样才会调用业务控制器去处理该请求;否则直接转向一个静态页面,返回 false,这样该请求就不会被处理。
xxx-servlet.xml中对拦截器进行的配置:
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="helloWorld.do">helloWorldAction</prop>
<prop key="login.do">loginController</prop>
</props>
</property>
<property name="interceptors">
<!--在interceptors 属性中定义所有的拦截器-->
<list>
<ref bean="officeHoursInterceptor"/>
<!--引用officeHoursInterceptor 拦截器-->
</list>
</property>
</bean>
<!--定义TimeInterceptor拦截器,id为officeHoursInterceptor -->
<bean id="officeHoursInterceptor" class="com.examp.ch23.TimeInterceptor">
<!--openingTime 属性指定上班时间-->
<property name="openingTime"><value>9</value></property>
<!--closingTime属性指定下班时间-->
<property name="closingTime"><value>18</value></property>
<!--outsideOfficeHoursPage属性指定提示页面的URL-->
<property name="outsideOfficeHoursPage"><value>http://localhost:8080/ch23/outsideOfficeHours.html</value></property>
</bean>
上面代码用bean标签去定义TimeInterceptor,令其id为officeHoursInterceptor,并给它的3个 属性赋值。SimpleUrlHandlerMapping的mappings属性指定了拦截器要拦截的路径,interceptors属性<property name="interceptors">去指定officeHoursInterceptor为一个拦截器,可以在<list> 和</list>之间定义多个拦截器。
当然如果不定义mappings,则默认拦截所有对Controller的请求。DefaultAnnotationHandlerMapping与SimpleUrlHandlerMapping的配置属性一样。
基于注解的DefaultAnnotationHandlerMapping配置:
<beans>
<bean id="handlerMapping" class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="interceptors">
<bean class="com.cgodo.daowole.web.interceptor.TimeBasedAccessInterceptor "/>
</property>
</bean>
<beans>
由于interceptors是一个list因此可以这么设置它的属性
<property name="interceptors">
<list>
<ref bean="officeHoursInterceptor"/>
</list>
</property>
如果配置了<mvc:annotation-driven/>,那么DefaultHandlerInterceptor拦截器将不会起作用。
http://wsj356428476.iteye.com/blog/1396512
http://www.cnblogs.com/zhaoyang/archive/2012/01/07/2315428.html
*********************************
在spring的web项目中常常会在tomcat启动的时候出现这种提示:
log4j:WARN No appenders could be found for logger (org.springframework.web.context.ContextLoader).
log4j:WARN Please initialize the log4j system properly.
web应用程序的log4j.properties文件之前就报出来了。是在加载 org.springframework.web.context.ContextLoader这个listener的时候没找到log4j的配置文件造成的。
仔细查看web.xml发现在加载org.springframework.web.context.ContextLoader这个 listener之后才加载org.springframework.web.util.Log4jConfigListener,把log4j的配置放到org.springframework.web.context.ContextLoader之前,就可以解决这个问题了。
************************
在使用spring MVC时,我们一般将controller层的相关配置放到xxx-servlet.xml中(如controller层的包扫描),将service,dao,数据源,事务,包扫描等配置放在applicationContext.xml中。
********************
Spring3.0 MVC @ResponseBody 的作用是把返回值直接写到HTTP response body里。具体实现AnnotationMethodHandlerAdapter类handleResponseBody方法,具体实现代码:
结果输出乱码:???????
如果用的是<mvc:annotation-driven />则不会有UTF-8乱码,如果是使用AnnotationMethodHandlerAdapter则可能会出现乱码。
有人跟踪@ResponseBody 的实现类发现其默认的编码是 iso-8859-1,
解决办法,在spring mvc的配置文件中手工配置bean:
<!-- 启动Spring MVC的注解功能,完成请求和注解POJO的映射 -->
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" >
<property name="messageConverters">
<list>
<bean class = "org.springframework.http.converter.StringHttpMessageConverter">
<property name = "supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
</list>
</property>
</bean>
</list>
</property>
</bean>
这样通过配置AnnotationMethodHandlerAdapter类messageConverters属性来指定编码。
记住,需要把bean部分加入到<context:component-scan base-package="com.zlscw.mvc" />前面,这样就可以在jquery中直接调用而不出现乱码了。
上面的解决办法只对使用spring3.0有效,Spring 3.1后AnnotationMethodHandlerAdapter已经过期了,现在用的是RequestMappingHandlerAdapter。
第一种办法就是增加produces="text/plain;charset=UTF-8;"
@RequestMapping(value="/",produces="text/plain;charset=UTF-8;")
@ResponseBody
public String paymentxx() {
return "返回中文字符";
}
RequestMapping的属性:
consumes: 指定处理请求的提交内容类型(Content-Type),例如application/json, text/html;
produces: 指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回;
params: 指定request中必须包含某些参数值是,才让该方法处理。
headers: 指定request中必须包含某些指定的header值,才能让该方法处理请求
value: 指定请求的实际地址,指定的地址可以是URI Template 模式(后面将会说明);
method: 指定请求的method类型, GET、POST、PUT、DELETE等;
上面的解决方案有点不好,要每一个方法都添加这么一句代码,虽然灵活性高,优雅,但是却麻烦。
第二种解决办法:
在声明注解驱动器(<mvc:annotation-driven>)的时候的控制编码的转换增加<mvc:message-converters>元素
<!-- 解决@ResponseBody 的实现类其默认的编码是 iso-8859-1的问题 -->
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes" value="text/html;charset=UTF-8"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
如果<mvc:annotation-driven>中又配置了conversion-service属性,不会影响mvc:message-converters的功能。
********************
spring mvc 表单映射date类型字段的问题
在mvc中如果表单属性的类型是日期型时,从页面绑定字符串数据会出错
Failed to convert property value of type [java.lang.String] to required type [java.util.Date] for property 'expert.birthdate'; nested exception is java.lang.IllegalArgumentException: Cannot convert value of type [java.lang.String] to required type [java.util.Date] for property 'birthdate': no matching editors or conversion strategy found
解决方法
在spring-servlet.xml配置文件中增加:
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
.....
<property name="webBindingInitializer">
<bean class="com.util.MyWebBinding" />
</property>
</bean>
public class MyWebBinding implements WebBindingInitializer {
public void initBinder(WebDataBinder binder, WebRequest request) {
//1. 使用spring自带的CustomDateEditor
//SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
//binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
//2. 自定义的PropertyEditorSupport
binder.registerCustomEditor(Date.class, new CustomerDateTimeEditor());
}
}
public Class CustomerDateTimeEditor extends PropertyEditorSupport{
public void setAsText(String text)throws IllegalArgumentException{
if(StringUtils.hasText(text){
setValue(DateUtil.StringToDate(text);
}else{
setValue("");
}
}
public String getAsText(){
Date value=(Date)getValue();
if(value==null){
return "";
}else{
Calendar calendar=Calendar.getInstance();
calendar.setTime(value);
if(calendar.get(Calendar.HOUR)==0 && calendar.get(Calendar.MINUTE)==0){
return DateUtil.DateToString(value.DateStyle.YYYY_MM_DD);
}else{
return DateUtil.DateToString(value.DateStyle.YYYY_MM_DD_HH_MM_SS);
}
}
}
}
以上DateUtil,DateStyle见21day日期工具类。
注意上面的方法只适用spring3.0,而spring3.1以后发生了改变,已经不适用。因为:
在spring mvc 3.1中,对应变更为
DefaultAnnotationHandlerMapping -> RequestMappingHandlerMapping
AnnotationMethodHandlerAdapter -> RequestMappingHandlerAdapter
AnnotationMethodHandlerExceptionResolver -> ExceptionHandlerExceptionResolver
以上都在使用了annotation-driven后自动注册。而且对应分别提供了AbstractHandlerMethodMapping , AbstractHandlerMethodAdapter和 AbstractHandlerMethodExceptionResolver以便于让用户更方便的实现自定义的实现类。
<mvc:annotation-driven/>相当于注册了DefaultAnnotationHandlerMapping和AnnotationMethodHandlerAdapter两个bean,配置一些messageconverter。即解决了@Controller注解的使用前提配置。
spring mvc <mvc:annotation-driven />会自动启动Spring MVC的注解功能,但实际它做了哪些工作呢?如下:
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="order" value="1" />
</bean>
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="webBindingInitializer">
<bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
<property name="conversionService" ref="conversionService" />
<property name="validator" ref="validator" />
</bean>
</property>
</bean>
<bean id="conversionService" class="org.springframework.samples.petclinic.util.PetclinicConversionServiceFactory" />
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
从上面的配置可以看出,我的配置应该是被sping的配置覆盖了,<mvc:annotation-driven />配置中已经包含了webBindingInitializer的配置,看来使用<mvc:annotation-driven />后与原来的配置出现了重复,这种情况下不管<mvc:annotation-driven />放在上面还是放在下面都会出现问题。
使用conversion-service来注册自定义的converter
DataBinder实现了PropertyEditorRegistry, TypeConverter这两个interface,而在spring mvc实际处理时,返回值都是return binder.convertIfNecessary(见HandlerMethodInvoker中的具体处理逻辑)。因此可以使用customer conversionService来实现自定义的类型转换。
从<mvc:annotation-driven />中配置可以看出,AnnotationMethodHandlerAdapter已经配置了webBindingInitializer,我们可以通过设置其属性conversionService来实现自定义类型转换。
先修改配置文件:
需要修改spring service context xml配置文件中的annotation-driven,增加属性conversion-service指向新增的conversionService bean。
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="project3cs.pub.utils.XmlDateConverter" />
</list>
</property>
</bean>
<mvc:annotation-driven conversion-service="conversionService" />
XmlDateConverter.java类代码如下:
public class XmlDateConverter implements Converter<String, Date> {
@Override
public Date convert(String source) {
try {
return DateUtil.StringToDate(source);
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
}
这样就解决springMVC Date日期问题和日期类型字符串传输到后台Date字段会报出400 bad request错误。Spring 400 bad request就是Spring认为传输的内容与后台字段类型不匹配造成的。
在Spring MVC中,DiapatcherServlet是处于核心的位置,它负责协调不同的组件以完成请求处理并返回响应的工作。DiapatcherServlet必须在web.xml中进行配置。
下面我们在web.xml中配置DispatchServlet来处理*.do的请求。在web.xml增加下面的配置:
如果找不到org.springframework.web.servlet.DispatcherServlet,则需要spring-webmvc.jar包。如果报jstl的异常,则导入jstl.jar包。
<!-- 配置Spring MVC -->
<servlet>
<servlet-name>Dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-name>/WEB-INF/Config.xml</param-name>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Dispatcher</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
Servlet定义
这里我们定义了请求分发Servlet,即:
org.springframework.web.servlet.DispatcherServlet
DispatcherServlet 是Spring MVC 中负责请求调度的核心引擎,所有的请求将由此Servlet 根据配置分发至各个逻辑处理单元。其内部同时也维护了一个ApplicationContext实例。
我们在<init-param>节点中配置了名为“contextConfigLocation”的Servlet参数,此参数指定了Spring配置文件的位置“/WEB-INF/Config.xml”。如果忽略此设定,则默认为“/WEB-INF/<servlet name>-servlet.xml”,其中<servlet name>以Servlet 名替换(在当前环境下,默认值也就是/WEB-INF/Dispatcher-servlet.xml)。
我们将所有以.do结尾的请求交给Spring MVC进行处理。
一个web.xml中可以配置多个DispatcherServlet,通过<servlet-mapping>的配置,让每个DispatcherServlet处理不同的请求。
Servlet拦截匹配规则可以自已定义:
当映射为@RequestMapping("/user/add.do")时:
拦截*.do,例如:/user/add.do,弊端:所有的url都要以.do结尾。不会影响访问静态文件。
拦截/app/*,例如:/app/user/add,弊端:请求的url都要包含/app,@RequestMapping("/user /add")中不须要包含/app。
拦截/,例如:/user/add,弊端:对jpg,js,css静态文件的访问也被拦截不能正常显示。
拦截/*,可以走到Action中,但转发到jsp时再次被拦截,不能访问到jsp。
一个简单的实例:
@Controller()
@RequestMapping("/user.do")
public class TestAction{
IUserService userService;
@RequestMapping(method=RequestMethod.POST)
public ModelAndView test(User user){
System.out.println("user test");
user.setCreateDate(new Date());
ModelAndView mav=new ModelAndView();
mav.setViewName("success.jsp");
mav.addObject("user",user);
userService.addOne(user);
return mav;
}
test(User user)方法被@RequestMapping(method=RequestMethod.POST)注解,表示以post方式访问web项目根路径下/user.do是就会调用此方法,默认是POST方式。如果写成@RequestMapping(value="test",method=RequestMethod.POST)则表示访问web项目根路径下/user/test.do时调用test方法。
如果写成@RequestMapping("/user")会匹配/user.*,/user路径
下面是页面提交到user.do
<%@page contentType="text/html; charset=utf-8"%>
<html>
<head>
<title>测试</title>
</head>
<body>
<form action="${pageContext.request.contextPath }/user.do" method="post">
用户名:<input type="text" name="name"/><br/>
密码:<input type="password" name="password"/><br/>
<input type="submit" name="提交"/>
</form>
</body>
</html>
表单的name属性与test方法的User参数的属性保持一致,那么Spring MVC既可以将表单组件填充到User的属性中。
2@RequestMapping
@RequestMapping使用value值指定请求的url,如果@ResourceMapping("/user"),需要注意的是@RequestMapping在类定义处制定的URL是相对于Web应用的部署路径,而在方法处指定的URL则相对于类定义处指定的URL。如果在类定义处没有标出@RequestMapping,仅在处理方法处标注@RequestMapping,则方法处指定的URL则相对于Web应用的部署路径。
@RequestMapping不但支持标注的URL,还支持?,**,*的字符:
/user/*/createUser,匹配/user/aaa/createUser,/user/bbb/createUser等URL。
/user/**/createUser,匹配/user/createUser,/user/aaa/bbb/User等URL
/user/createuser??,匹配/user/createUseraa,/user/createuserbb等URL
/user/{userId},匹配user/123,user/456等url。
/user/**/{userId},匹配user/aaa/bbb/123,user/aaa/fdf778等url。
/company/{companyId}/user/{userId}/detail,匹配company/111/user/4556/detail等url。
带占位符的url是spring3.0新增的功能,该功能在Spring mvc向REST靠近。通过@PathVariable可以将URL中的占位符参数绑定到控制器的处理方法的如参数中。
例如:当我们在页面中<form action="${pageContext.request.contextPath }/user/123.do" method="post">来请求时,{userid}对应的值就是123。
@RequestMapping(value="{userid}",method=RequestMethod.POST)
public ModelAndView test(@PathVariable("userid")String userid,User user){
@RequestMapping处理像上面那样使用请求的URL映射请求外,还可以使用请求方法,请求头参数以及请求参数映射请求。
下面的例子对请求方法为POST的,请求参数中含有userid的进行映射
@RequestMapping(value="delete",method=RequestMethod.POST,params="userid")
public ModelAndView test(@RequestParam("userid")String userid){...}
下面的例子对请求头(报文头content-type)进行映射
@RequestMapping(value="/show",headers="content-type=text/*")
public ModelAndView test2(@RequestParam("userid")String userid){...}
params和headers分别通过请求参数以及报文头属性进行映射,它们支持简单的表达式。下面以params表达式为例说明,headers可以参照params。
"param1",表示请求必须包含名为param1的请求参数。
"!param1",表示请求不能包含名为param1的请求参数。
"param1!=value1",表示请求参数包含名为param1的请求参数,但是其值不能为value1。
{"param1=value1","param2"},表示请求参数必须包含名为param1和param2的两个请求参数且param1参数值必须为value1。
请求方法签名
Spring MVC对控制器处理方法签名的限制是很宽松,可以按照自己喜欢的方式使用@PathVariabl,@RequestParam,@RequestHeader对方法参数进行注解。
3@RequestParam
@RequestParam注解得到对其对应的请求参数,@RequestParam有三个参数:
value,参数名。
required,是否必需,默认为true,表示请求参数中必须包含对应的参数名,如果不存在将抛出异常。
defaultValue,默认参数,设置该参数时,自动将required设置为false,一般不使用。
例如:下面将userName和age请求参数绑定到handle()方法的userName和age中
@RequestMapping(value="/handle1")
public String handle1(@RequestParam(value="userName",required=false)String userName,
@RequestParam("age")int age{...}
4使用@CookieValue绑定请求中的cookie值
使用@CookieValue可以让处理方法参数绑定某个cookie的值,它和@RequestParam拥有三个一样的参数。
@RequestMapping(value="/handle1")
public String handle1(@RequestParam(value="sessionId",required=false)String sessionId,
@RequestParam("age")int age{...}
5使用@RequestHeader绑定请求报文头的属性值
服务器可以据此获取客户端的信息,通过@RequestHeader就可以将报文头属性值banding到处理方法的入参中,。
@RequestMapping(value="/handle13")
public String handle13(@RequestHeader("Accept-Encoding")String encoding,
@RequestHeader("Keep-Alive")long keepAlive)
@RequestHeader和@RequestParam拥有3个一样的参数。
6使用命令/表单对象绑定请求参数值
所谓命令/表单对象并不需要实现任何接口,仅仅是一个拥有若干属性的POJO。spring MVC会按照请求参数名和命令/表单对象属性名匹配的方法,自动为该对象填充属性值。支持级联的属性,如dept.deptId,dept.address.tel等。例如User中有dept对象,dept对象有address属性,address对象有一个tel属性,那么下面的URL请求将正确的填充到User对象中。
/handle14.html?userName=tom&dept.deptId=1&dept.address.tel=102
@RequestMapping(value="/handle14")
public String handle14(User user){ ... }
7使用servlet API对象作为入参
在Spring MVC中,控制器类可以不依赖任何Servlet API对象,但是Spring MVC并不阻止我们使用Servlet API的类作为处理方法的入参,以下的处理方法都可以正确工作:
@RequestMapping(value="/handle14")
public String handle14(HttpServletRequest request,HttpServletResponse response){
String userName=WebUtil.findParamValue(request,"userName");
}
@RequestMapping(value="/handle14")
public void handle14(HttpServletResponse response,@RequestParam("userName")String userName){
response.addCookie(new Cookie("userName",userName));
}
使用Servlet API类作为入参时,SpringMVC会自动将Web层对应的servlet对象传递给处理方法的入参。它们之间的位置没有特殊要求。注意处理方法自行使用HttpServletResponse返回响应,则处理方法返回值返回成void即可。
Spring MVC在org.spring.framework.web.context.request包中定义了若干个可代理Servlet原生API类的接口,如WebRequest和NativeWebRequest,它们也允许作为处理类的入参,通过这些代理类可以访问请求对象的任何信息.如:
@RequestMapping(value="/handle25")
public void handle14(WebRequest request){
String userName=request.getParameter("userName");
}
使用I/O对象作为入参数
servlet的serveltRequest拥有getInputStream()和getReader()方法,可以通过它们读取请求的信息。相应Servlet的ServletRequest拥有getOutputStream()和getWriter()方法,可以通过它们输出响应信息。Spring MVC允许控制器的处理方法使用java.io.InputStream/java.io.Reader以及java.io.OutputStream/java.io.Writer作为方法入参:
@RequestMapping(value="/handle31")
public void handle14(OutputStream os)Throws IOException{
Resource res=new ClassPathResource("/image.jpg");//读取类路径下的图片文件
FileCopyUtils.copy(res.getInputStream(),os);//将图片写到输出流中
}
Spring控制器处理方法的入参除支持以上类型的参数还支持java.util.Locale,java.security.Principal,可以通过servlet的HttpServletRequest的getLocale()以及getUserPrincipal()得到相应的值。如果处理方法的入参类型为Locale或Principal,Spring MVC自动从请求对象中获取相应的对象并传递给处理方法的入参。
@RequestMapping(value="/handle32")
public void handle31(Locale locale)throws IOException{
...
}
@RequestBody和@ResponseBody
将HttpServletRequest的getInputStream()内容绑定到入参;将处理方法返回值写入到HttpServletResponse的getOutputStream()中。
@RequestMapping(value="/handle41.do")
public String handle41(@RequestBody String requestBody){
System.out.println(requestBody);
return "success";
}
@ResponseBody
@RequestMapping(value="/handle42/{imageId}")
public byte[] handle42(@PathVarable("imageId")String imageId)throws IOException{
System.out.println("load image of"+imageId);
Resource res=new ClassPathResource("/image.jpg");
byte[] fileData=FileCopyUtils.copyToByteArray(res.getInputStream());
return fileData;
}
返回json格式的数据:
@RequestMapping(value="tt")
@ResponseBody
public String tt(HttpServletRequest request,HttpServletResponse response){
List<User> lu=new ArrayList<User>();
for(int i=0;i<100;i++){
User user=new User();
user.setName("tt"+i);
user.setPassword("pp"+i);
user.setSex("male");
user.setUserID("id"+i);
user.setCreateDate(new Date());
lu.add(user);
}
Map<String,Object> data=new HashMap<String,Object>();
data.put("total", lu.size());
data.put("rows", lu);
JSONObject jsonObject = JSONObject.fromObject(data);
return jsonObject.toString();
}
//通过HttpServletResponse返回服务端响应
public String tt(HttpServletRequest request,HttpServletResponse response){
...
try{
response.setContentType("text/html");
response.getWriter().println("saveSucess");
}catch(IOException e){
}
}
8处理模型数据
ModelAndView
如何将模型数据暴露给视图是Spring MVC框架的一项重要工作,Spring MVC提供了多种途径输出模型数据:
ModelAndView,处理方法返回值类型为ModelAndView时,方法体即可通过该对象添加模型数据。
控制器处理方法返回值如果为ModelAndView,则其及包括视图信息,也包含模型数据信息,它是为了方便一次返回这两个对象,Model与View两者仍是分离的。
最简单的ModelAndView是持有View的名称返回,之后View名称被view resolver,也就是实作org.springframework.web.servlet.View接口的实例解析。
ModelAndView(String viewName)
例如:
@RequestMapping(value="test.do",method=RequestMethod.POST)
public ModelAndView test(@ModelAttribute("user")User user){
user.setCreateDate(new Date());
....
//相对于web项目根路径,直接return "/success.jsp"是一样的
return new ModelAndView("/success.jsp");
}
如果您想要返回Model对象,则可以使用Map来收集这些Model对象,然后设定给ModelAndView,使用下面的构造方法
ModelAndView(String viewName,Map Model)
例如:
@RequestMapping(value="test.do",method=RequestMethod.POST)
public ModelAndView test(User user){
user.setCreateDate(new Date());
Map<String,Object> datamap=new HashMap<String,Object>();
datamap.put("user", user);
return new ModelAndView("/success.jsp",datamap);
}
Map对象中设定好key与value值,之后可以在视图中取出,如果您只是要返回一个Model对象,则可以使用下面这个ModelAndView构造方法:
ModelAndView(String viewName, String modelName, Object modelObject)
通过modelName,您可以在视图中取出Model并显示。
ModelAndView(String viewName, Map model)
如果您要返回Model对象,则可以使用Map来收集这些Model对象,然后设定给ModelAndView。
ModelAndView(String viewName)
最简单的ModelAndView是持有View的名称返回,之后View名称被view resolver,也就是实现org.springframework.web.servlet.View接口的实例解析,例如 InternalResourceView或JstlView等等
ModelAndView(View view)
ModelAndView类别提供实作View接口的对象来作构造函数的参数。
ModelAndView()
这个构造方法构造出来的ModelAndView ,不能直接使用,应为它没有指定view,也没有绑定对应的model对象。当然,model对象不是必须的,但是view确实必须的。
在处理方法的方法体中,可以使用如下方法添加模型数据:
ModelAndView addObject(String attributeName,Object attributeValue);
ModelAndView addAllObjects(Map<String,?> modelMap>
可以通过如下方法设置视图:
void setView(View view),指定一个具体的视图对象。
void setViewName(String viewName),指定一个逻辑视图名。
@ModelAttribute
方法入参数标注该注解后,入参的对象就会与ModelMap属性绑定。
如果希望将方法入参对象添加到模型中,仅仅需要在相应入参前使用@ModelAttribute注解即可。如果@ModelAttribute没有制定值,则默认以入参类名的第一个字母小写作为key。
@RequestMapping(value="/handle61")
public String handle61(@ModelAttribute("user")User user){
user.setuserId("1000");
return "/user/createSuccess.jsp";//相对于web项目根路径
}
Spring MVC将消息绑定到User对象中,然后再以"user"为键将User对象放到模型中,在贮备对视图进行渲染前,SpringMVC还会近一步将模型中数据转储到视图的上下文中以暴露给视图对象。对于JSP视图来说,SpringMVC会将模型数据转储到ServeletRequest的属性列表中。
除了可以在方法入参上使用@ModelArribute注解,还可以在方法定义中使用@ModelAttribute注解。Spring MVC在调用目标处理方法前,会先逐个调用该方法级上标注了@ModelAttribute的方法,并将这些方法返回值添加到模型中,下面在方法级上使用@ModelAttribute注解:
@ModelAttribute("user")
public User getuser(){
User user=new User();
user.setUserId("10001");
return user;
}
在访问UserAction中任何一个请求处理方法前,Spring MVC先执行该方法,并将返回值以user为键添加到模型中。然后在调用handle61时将其映射到入参中,然后再根据Http请求消息进一步填充覆盖user对象。
@RequestMapping(value="/handle2")
public String handle61(@ModelAttribute("user")User user){
user.setUserName("tom");
return "/user/crateSuccess.jsp";//相对于web项目根路径
}
Map及Model
入参为org.springframework.ui.Model、org.springframework.ui.ModelMap或java.util.Map时,处理方法返回时,Map中的数据自动保存到request对象中,作用类似于request对象的setAttribute方法的作用
例如:
@RequestMapping(value="test.do",method=RequestMethod.POST)
public String test(User user,ModelMap model){
user.setCreateDate(new Date());
//将user保存到ModelMap中,然后在页面就可用通过${user.name}来取出数据
model.addAttribute("user",user );
System.out.println("model map testdd");
return "/success.jsp";
}
@SessionAttributes
ModelMap 中的属性作用域是 request 级别是,也就是说,当本次请求结束后,ModelMap 中的属性将销毁。如果希望在多个请求中共享 ModelMap 中的属性,必须将其属性转存到 session 中,这样 ModelMap 的属性才可以被跨请求访问。
Spring 允许我们有选择地指定 ModelMap 中的哪些属性需要转存到 session 中,以便下一个请求属于对应的 ModelMap 的属性列表中还能访问到这些属性。
这一功能是通过类定义处标注 @SessionAttributes 注解来实现的。
@SessionAttributes 只能声明在类上,而不能声明在方法上。
@SessionAttributes("currUser") // 将ModelMap 中属性名为currUser 的属性
@SessionAttributes允许指定多个属性。你可以通过字符串数组的方式指定多个属性
@SessionAttributes({"attr1","attr2"})
@SessionAttributes 还可以通过属性类型指定要 session 化的 ModelMap 属性
@SessionAttributes(types = User.class)
@SessionAttributes(types = {User.class,Dept.class})
还可以联合使用属性名和属性类型指定:
@SessionAttributes(types = {User.class,Dept.class},value={"attr1","attr2"})
例如:下面的类中,当访问/user/test.do时,我们将ModelMap中的user对象存放到session中,然后访问/user/tt.do是将session中的user对象取出来
@Controller()
@RequestMapping("/user")
@SessionAttributes("user")
public class TestAction{
@RequestMapping(value="test.do",method=RequestMethod.POST)
public String test(User user,ModelMap model){
user.setCreateDate(new Date());
model.addAttribute("user",user );
System.out.println("model map testdd");
return "/success.jsp";
}
@RequestMapping(value="tt")
public void tt(@ModelAttribute("user")User user){
System.out.println(user.getName()+"/"+user.getPassword()+"/"+user.getCreateDate());
}
}
SpringMVC 文档提到了@SessionAttributes 和 @ModelAttribute配合使用可以往 Session 中存或者从 Session 中取指定属性名的具体对象。 在需要访问 Session 属性的 controller 上加上 @SessionAttributes,然后在 action 需要的 User 参数上加上 @ModelAttribute,并保证两者的属性名称一致。SpringMVC 就会自动将 @SessionAttributes 定义的属性注入到 ModelMap 对象,在 setup action 的参数列表时,去 ModelMap 中取到这样的对象,再添加到参数列表。只要我们不去调用 SessionStatus 的 setComplete() 方法,这个对象就会一直保留在 Session 中,从而实现 Session 信息的共享。
采用@SessionAttributes(types={指定class})
采用这种方式的话系统会默认保存指定class的名字(头字母小写),如UserBean.class,modelmap会仅仅会保存名字为 userBean或者userbean的对应的class,其余则不会保存,
重定向
return "redirect:/user/crateSuccess.jsp";
转发
return "forward:/user/crateSuccess.jsp";
9数据校验
Spring3.0拥有自己独立的数据校验框架,同时支持JSR303标准的校验框架,Spring的DataBinder在进行数据绑定时,可同时调用校验框架完成数据校验工作。在Spring MVC中,可以直接通过注解驱动的方式进行数据校验。
注意Spring本身没有提供JSR303的实现,所以必须将JSR303的实现者(如hibernate validator)的jar文件放到类路径下,Spring将自动加载并装配好JSR303的实现者。 将hibernate-validator与spring mvc3结合需要添加以下jar:
hibernate-validator-4.2.0.Final.jar
log4j-1.2.16.jar
slf4j-api-1.5.10.jar
slf4j-log4j12-1.5.10.jar
validation-api-1.0.0.GA.jar
以下常用JSR 303 规范验证,可参考hibernate-validator
@Null,被注解的元素必须为null。
@NotNull,被注解的元素必须不为null。
@AssertTrue,被注解的元素必须为true。
@AssertFalse,被注解的元素必须为false。
@Min(value=)/DecimalMin(value=),被注解的元素必须是一个数字,其值必须大于等于制定的最小值。
@Max(value=)/DecimalMax(value=),被注解的元素必须是一个数字,其值必须小于等于制定的最大值
@Size(max=, min=) ,被注解的String, Collection, Map and arrays元素的大小必须在指定的范围内(Min<=Size<=max )。
@Digits(Integer,fraction),被注解的元素必须是一个数字,其值必须在可接受的范围内。
@Past,被注解的元素必须是一个过去的日期。
@Future,被注解的元素必须是一个将来的日期。
@Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式。
@Pattern(regexp = "[A-Za-z0-9]{5,20}", message = "请输入字母和数字,长度为5-20")
Hibernate Validator 附加的 constraint
@NotBlank(message =) 验证字符串非null,且长度必须大于0
@Email 被注释的元素必须是电子邮箱地址
@Length(min=,max=) 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range(min=,max=,message=) 被注释的元素必须在合适的范围内
@Url 被注释的元素必须是url地址
@Valid(对象)
在一个关联对象上递归的执行检验.如果对象是一个数组或者集合,对象将被递归的检验. 如果对象是一个map,元素将被递归的验证.
<mvc:annotation-driven/>会默认装配好一个LocalValidatorFactoryBean,通过在处理方法的入参上标注@Valid注解就可以让Spring MVC在完成数据绑定后执行数据校验的工作。
public Class User{
//用户名称
@NotBlank(message="用户名不能为空")
@Column(nullable=false,length=32)
private String name;
@DateTimeFormat(pattern="yyyy-MM-dd")
@Temporal(TemporalType.TIMESTAMP)
@Column(nullable=true)
private Date createDate;
//没有给出message属性的值,会按照hiberate-validator.jar中的org\hibernate\validator\ValidationMessages_zh_CN.properties文件中的默认信息
//用户密码
@NotBlank
@Column(nullable=false,length=32)
private String password;
}
@Controller()
@RequestMapping("/user.do")
@SessionAttributes("user")
public class TestAction{
public String test(@Valid User user,BindingResult userBindingResult,ModelMap model){
user.setCreateDate(new Date());
model.addAttribute("user",user );
System.out.println("valid dd testdd");
//判断是否验证失败
if(userBindingResult.hasErrors()){
model.addAttribute("user",user);
//得到所有验证失败的字段
List<FieldError> ls=userBindingResult.getFieldErrors();
FieldError fe;
String field;
String errorMessage;
for (int i = 0; i < ls.size(); i++) {
fe=ls.get(i);
field=fe.getField();//得到哪个字段验证出错
errorMessage=fe.getDefaultMessage();//得到错误消息
System.out.println("错误字段消息:"+field +" : "+errorMessage);
}
return "/index.jsp";
}else{
return "/success.jsp";
}
}
在已经标注了JSR303注解的表单/命令对象前标注一个@Valid,Spring MVC框架在将请求数据绑定到该入参对象后,就会调用校验框架根据注解声明的校验规则实施校验.
Spring MVC是通过对处理方法签名的规约来保存校验结果的:前一个表单/命令对象的校验结果保存在其后的入参中,这个保存校验结果的入参必须是BindingResult或Errors类型,这两个类都位于org.springframework.validation包中。如:
public String handle91(@Valid User user,BindingResult userBindingResult,String sessionId,ModelMap mm,@Valid Dept dept,Errors deptErrors){
...
}
前两个参数是User和其绑定结果的对象。后两个参数是Dept和其校验的结果对象。
spring将绑定错误信息,验证错误信息都保存到隐含模型中,然后我们在页面上取出验证错误信息,这里的modelAttribute="user"(或者是commandName="user")是必须的,否则页面取不到错误信息。BindingResult有一个ObjectName属性用来binds list of errors with commandName in your jsp。
那么如何设置modelAttribute和commandName的值呢,它们的值应该与传递到页面的命令/表单对象的名称name属性一致。上面的例子传递到页面的命令/表单名称是user。<form:form action="${ctx }/user/test.do" method="post" modelAttribute="user">
<!-- 在用户名后面显示验证错误信息 -->
用户名:<input type="text" name="name"/><form:errors path="name" cssClass="error"/><br/>
密码:<input type="password" name="password"/><br/>
<input type="submit" name="提交"/><br/>
<form:errors path="*"/>
</form:form>
通过path和表单对象特定属性错误信息进行绑定,一个表单对象属性可能包括一个或多个错误信息,也可以没有错误信息,<form:errors>会根据错误信息的情况进行合理的展示。此外,path还支持通配符匹配的表示方式:
path="*": 显示所有的错误信息;
path="lastName*": 显示所有属性名前缀为lastName的错误信息。
Spring中的用于格式化的annotation
Spring 3 提供了两个可以用于格式化数字、日期和时间的注解@NumberFormat和@DateTimeFormat,这两个标签可以用于bean的属性或方法参数上。@NumberFormat可以用来格式化任何的数字的基本类型(如int,long)或java.lang.Number的实例(如 BigDecimal, Integer)。@DateTimeFormat可以用来格式化java.util.Date、java.util.Calendar和 java.util.Long类型,也可以用于Joda Time类型的字段或参数。(Joda Time是一个开源的包,提供了对date和time类的一些替代类)。
要指定数字或日期/时间类型的属性,只需要在其上添加 @NumberFormat或@DateTimeFormat注解接可以了。例如下面的代码:
/**
* numeric fields using @NumberFormat annotation for formatting.
*/
@NumberFormat(style = Style.CURRENCY)//将输出$30,100.50;
private double salary= 30100.50;
@NumberFormat(style = Style.PERCENT)//将输出2%
private double w4AdditionalWithdraw= 0.02;
@NumberFormat//将输出5
private int dependents=5;
@NumberFormat(pattern = "0.00")//将输出0.10
private BigDecimal visualAcuity= new BigDecimal(".1");
注解的属性决定了@NumberFormat和@DateTimeFormat注解怎样格式化相应的属性。 @NumberFormat注解有两个可选的属性:style和pattern。style属性是一个NumberFormat.Style枚举值,可以是以下的三个值之一:
NumberFormat.Style枚举值取值为NUMBER,CURRENCY,PERCENT。默认为NUMBER。
一个double类型的字段,如果style是CURRENCY,那么在en-us的区域显示的时候前面会加上$,在zh-cn的区域显示的时候前面会加上¥。可以使用pattern属性来指定特殊的输出格式。Pattern的值要遵循Java标准的numeric formatting pattern。对于@NumberFormat来说缺省的是没有pattern的。
/**
* date and time fields using @DateTimeFormat annotation for formatting.
*/
@DateTimeFormat(style = "M-")//如Aug 30, 1964
private Date birthDate;
@DateTimeFormat(pattern = "w:yyyy")//如(week:year)-> 16:2012
private Calendar hireDate;
@DateTimeFormat(style = "-S")//如8:0 AM
private LocalTime startTime;
@DateTimeFormat(iso = ISO.DATE_TIME)//如2000-10-31 01:30:00.000-05:00.
private long lastTimeEntry;
@DateTimeFormat 注解有3个可选的属性:style,pattern和iso。属性style允许我们使用两个字符的字符串来表明怎样格式化日期和时间。第一个字符表明了日期的格式,第二个字符表明了时间的格式。下面的表格中列出了可用的选择以及相应的输出的例子:
style 属性允许我们使用两个字符的字符串来表明怎样格式化日期和时间。第一个字符表明了日期的格式,第二个字符表明了时间的格式。下面的表格中列出了可用的选择以及相应的输出的例子:
描述 字符串值 示例输出
短格式(这是缺省值) SS 8/30/64 11:24 AM
中等格式 MM Aug 30, 1964 11:24:41 AM
长格式 LL August 30, 1964 11:24:41 AM CDT
完整格式 FF Sunday, August 30, 1964 11:24:41 AM CDT
使用短横线省略日期或时间 M- Aug 30, 1964
Pattern 属性允许我们使用自定义的日期/时间格式。该属性的值遵循java标准的date/time格式规范。缺省的该属性的值为空,也就是不进行特殊的格式化。
ISO枚举值 输出
DATE 2000-10-31
TIME 01:30:00.000-05:00(最后的是时区)
DATE_TIME 2000-10-31 01:30:00.000-05:00.
NONE 不进行ISO标准的格式化
要使用上面介绍的格式化注解,需要进行如下的配置:
1.下载Spring 3.0的jar包并加入到类路径中。
2. 将下面的配置信息加入到spring bean的XML配置文件中。
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd"> <mvc:annotation-driven /> </beans>
解析参数
我们也可以使用这些注解来告诉Spring怎样解析输入的数据。下面的例子显示了怎样使用@DateTimeFormat来解析以ISO格式输入的时间信息:
public String getEmployeesOnTheClock(@DateTimeFormat(iso=ISO.TIME) java.util.Date time) { ... }
10Spring与json
Spring在解析视图的时候有两个重要的接口:ViewResolver和View,ViewResolver 负责怎么去解析, 而View只代表一种视图层的技术。对于一个请求,对于一个请求,应该什么样的视图是ViewResolver来决定的,spring3.0提供的 ViewResolver 包括 AbstractCachingViewResolver,XmlViewResolver,ResourceBundleViewResolver,UrlBasedViewResolver,InternalResourceViewResolver等,我们平时使用ResourceBundleViewResolver或者InternalResourceViewResolver来返回JSP页面,他们就是其中的两个 ViewResolver 。大部分的ViewResolver实现类,除了org.springframework.web.servlet.view.BeanName-ViewResolver是直接实现ViewResolver接口,都直接或者间接继承自org.springframe work. web.servlet.view.AbstractCachingViewResolver。
UrlBasedViewResolver
UrlBasedViewResolver(它们都直接地或者间接地继承自该类)。使用该类别的ViewResolver,我们不需要为它们配置具体的逻辑视图名到具体View的映射关系。通常只要指定一下视图模板所在的位置,这些ViewResolver就会按照逻辑视图名,抓取相应的模板文件、构造对应的View实例并返回。之所有又将它们称之为面向单一视图类型的ViewResolver,是因为该类别中,每个具体的ViewResolver实现都只负责一种View类型的映射,ViewResolver与View之间的关系是一比一。比如,我们之前一直使用的InternalResourceView- Resolver,它通常就只负责到指定位置抓取JSP模板文件,并构造InternalResourceView类型的View实例并返回。而VelocityViewResolver则只关心指定位置的Velocity模板文件(.vm),并会将逻辑视图名映射到视图模板的文件名,然后构造VelocityView类型的View实例返回,诸如此类。
属于该类别的主要ViewResolver实现类为如下几个:
org.springframework.web.servlet.view包下有好几个ViewResolver实现,
InternalResourceViewResolver为UrlBasedViewResolver的子类,spring默认采用InternalResourceViewResolver,配置文件如下:
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"><value>org.springframework.web.servlet.view.JstlView</value></property>
<property name="prefix"><value>/WEB-INF/jsp/</value></property>
<property name="suffix"><value>.jsp</value></property>
</bean>
上面的配置表示,将在Controller返回的ModelAndView的基础上,加上目录前缀/WEB-INF/jsp/,
加后文件名称后缀.jsp,由此返回的页面如/WEB-INF/jsp/showCats.jsp
ResourceBundleViewResolver,
ResourceBundleViewResolver。ResourceBundleViewResolver构建在ResourceBundle上,继承了ResourceBundle国际化支持的能力,也是所有的ViewResolver实现类中唯一提供视图国际化支持的ViewResolver。ResourceBundleViewResolver管理的视图的逻辑名称与具体视图的映射关系保存在properties文件中。比如配置文件如下:
<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
<property name="basename"><value>views-countries</value></property>
<property name="defaultParentView"><value>modelView</value></property>
</bean>
basename 的配置表示将从classes目录下的views-countries.properties文件中获取资源,这样
我们可以很方便的实现国际化与本地化;defaultParentView表示所有的view都将从modelView中继承。如果我们没有指定properties配置文件从何处加载的话,ResourceBundleViewResolver默认将从classpath的根路径加载以views为basename的properties文件。
XmlViewResolver
XmlViewResolver与ResourceBundleViewResolver之间最主要的区别就是,它们所采用的配置文件格式不同。
<bean id="xmlViewResolver" class="org.springframework.Web.servlet.view.XmlViewResolver">
<property name="location" value="classpath:views.xml"/>
</bean>
该配置表示XmlViewResolver将从Classpath的根路径加载名为views.xml的配置文件。
FreeMarkerViewResolver/VelocityViewResolver。FreeMarkerViewResolver和Velo- cityViewResolver分别负责对应FreeMarkerView和VelocityView类型视图的查找工作,它们将根据逻辑视图名到指定的位置获取对应的模板文件,并构造FreeMarkerView和VelocityView的实例返回给DispatcherServlet使用。
JasperReportsViewResolver。JasperReportsViewResolver只关心根据逻辑视图名到指定位置查找JasperReport类型模板文件,并返回AbstractJasperReportsView的具体子类型View实例,例如JasperReportsCsvView或者JasperReportsHtml
XsltViewResolver。只负责根据逻辑视图名查找并返回XsltView类型的View实例。
启用以上这些ViewResolver,与使用InternalResourceViewResolver一样简单。最基本的方法是,使用prefix属性指定模板所在路径,使用suffix属性指定模板文件的后缀名。这样,在获取逻辑视图名之后,相应的ViewResolver内部就能够根据[prefix]+viewName+[suffix]这样的URL找到对应的模板文件,并构造对应的View实例而返回了。下面给出了针对VelocityViewResolver的配置代码示例:
<bean id="viewResolver" class="org.springframework.Web.servlet.view.velocity.VelocityViewResolver">
<property name="prefix" value="../velocity/"/>
<property name="suffix" value=".vm"/>
</bean>
现在DispatcherServlet对视图的请求将会由VelocityViewResolver接管,Velocity- ViewResolver将根据传入的逻辑视图名,到指定目录下查找.vm类型的Velocity模板文件,并构造VelocityView实例返回给DispatcherServlet使用。
ContentNegotiatingViewResolver
这个类它实现了ViewResolver。但它并不直接解析视图,而是委托给别人。默认情况,它是从spring 上下文,查找视图解析器,并调用这些解析器。也可以在初始化这个bean的时候,设置它的解析器属性(viewResolvers),这是个list类型的属性。
请注意,要让这个视图解析器正常工作,需要设置比别人更高的优先级(默认为Ordered.HIGHEST_PRECEDENCE)。
RESTful服务中很重要的一个特性即是同一资源,多种表述我们使用ContentNegotiatingViewResolver就可以做到,如下所示:
使用扩展名
http://www.test.com/user.xml 呈现xml文件
http://www.test.com/user.json 呈现json格式
http://www.test.com/user 使用默认view呈现,比如jsp等
使用http request header的Accept
GET /user HTTP/1.1
Accept:application/xml
GET /user HTTP/1.1
Accept:application/json
根据请求的accpet,来返回信息。
由于浏览器的差异,发送上来的Accept Header头将是不一样的. 将导致服务器不知要返回什么格式的数据给你.
StringHttpMessageConverter一文说到的@ResponseBody String返回类型,google chrome下会有问题就是这个原因。
firefox IE6-7由于第一个accept是text/html,StringHttpMessageConverter处理的还是text/html。
所以我们将不建议根据请求的accpet来返回信息。
使用参数
http://www.test.com/user?format=xml
http://www.test.com/user?format=json
如何使用ContentNegotiatingViewResolver,假设我们有这么一个目标:
/user/{userid}.json 用于返回一个描述User的JSON
/user/{userid} 用于返回一个展示User的JSP页面
/user/{userid}.xml 用于返回一个展示User的XML文件
这里是是否启用扩展名支持,默认就是true
例如 /user/{userid}.json
<property name="favorPathExtension" value="true"></property>
这里是是否启用参数支持,默认就是true
例如 /user/{userid}?format=json
<property name="favorParameter" value="false"></property>
不启动参数,现在很多open API是使用这种方式,但可能由于要编写的字符较多(占用更多带宽),所以较少使用.
这里是否忽略掉accept header,默认就是false
例如 GET /user HTTP/1.1
Accept:application/json
<property name="ignoreAcceptHeader" value="true"></property>
我们的例子是采用.json , .xml结尾的,所以关掉两个。
这里是扩展名到mimeType的映射,
例如 /user/{userid}.json 中的.json就会映射到 application/json
<property name="mediaTypes">
<map>
<entry key="json" value="application/json" />
<entry key="xml" value="application/xml" />
</map>
</property>
ContentNegotiatingViewResolver是根据客户提交的MimeType(如 text/html,application/xml)来跟服务端的一组viewResover的MimeType相比较,如果符合,即返回 viewResover的数据.
而 /user/123.xml, ContentNegotiatingViewResolver会首先将 .xml 根据mediaTypes属性将其转换成 application/xml,
spring3.0中提供了一种View来支持JSON:MappingJacksonJsonView。
为了得到服务器端返回的json,需要使用MappingJacksonJsonView,这个view使用jackson lib将java对象转换成json,我们需要jackson-mapper-lgpl-1.4.5.jar和jackson-core-lgpl-1.4.5.jar的jar包。注意使用其他高版本的jackson-all包有问题。
首先我们在spring的配置文件添加如下配置,我们启用参数支持:
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<!-- true,开启扩展名支持,false关闭支持 -->
<property name="favorPathExtension" value="false" />
<!-- 用于关闭参数/userinfo/123?format=json的支持 -->
<property name="favorParameter" value="true" />
<!--如果所有的mediaType都没匹配上,就会使用defaultContentType-->
<property name="defaultContentType" value="text/html" />
<!--
参数值至mimeType的映射,即 /userinfo/123?format=json json是key,application/json就是value
暂时只支持json和xml
-->
<property name="mediaTypes">
<map>
<entry key="json" value="application/json" />
<entry key="xml" value="application/xml" />
</map>
</property>
<property name="viewResolvers">
<list>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/"/>
<property name="suffix" value=".jsp"/>
</bean>
</list>
</property>
<property name="defaultViews">
<list>
<bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView">
<property name="prefixJson" value="true"/>
</bean>
</list>
</property>
</bean>
如下是spring的controller层的方法,返回一个List<User>到response中:
@RequestMapping(value="tt")
@ResponseBody
public List<User> getJson(HttpServletRequest request,HttpServletResponse response,ModelMap modelmap){
List<User> lu=new ArrayList<User>();
for(int i=0;i<15;i++){
User user=new User();
user.setName("tt"+i);
user.setPassword("pp"+i);
user.setSex("male");
user.setUserID("id"+i);
user.setCreateDate(new Date());
lu.add(user);
}
return lu;
}
然后我们使用ajax请求getJson方法得到List<User>转换后的json对象,使用JSON.stringify转换成字符串alert出来:
function tt(){
$.ajax({
type: "POST",
url: "${ctx}/user/tt.do?format=json",
success: function(msg){
alert(JSON.stringify(msg));
}
});
}
<input type="button" value="json" οnclick="tt()"/>
使用实例:
@RequestMapping(value="/menu.do",params="deleteMenu")
@ResponseBody
Map<String,String> deleteMenu(String code,HttpServletRequest req, HttpServletResponse resp){
Menu menu=menuDao.findOneByCode(code);
Map<String,String> data=new HashMap<String,String>();
if(menu!=null){
List<Menu> nextMenu=menuDao.findNextMenuByCode(code);
if(nextMenu.size()>0){
data.put("code", "error");
data.put("info", "不能删除该菜单,该菜单下还有子菜单!菜单名称:"+menu.getMenuName());
}else{
//menuDao.removeOneById(menu.getMenuID());
data.put("code", "success");
data.put("info", "成功删除该菜单!菜单名称:"+menu.getMenuName());
}
}else{
data.put("code", "noExist");
data.put("info", "对不起,系统不存在该菜单!");
}
return data;
}
请求上面的方法:
jQuery.getJSON("${ctx}/menu.do?deleteMenu&format=json&code=" + menuCode, function(data) {
alert(JSON.stringify(data));
//结果:{"code":"success","info":"成功删除该菜单!菜单名称:我的办公室"}
});
10spring mvc拦截器
在controller层,Spring为我们提供了org.springframework.web.servlet.handler.HandlerInterceptorAdapter这个适配器,继承此类,可以非常方便的实现自己的拦截器。
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
}
public void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
分别实现预处理、后处理(调用了Service并返回ModelAndView,但未进行页面渲染)、返回处理(已经渲染了页面)
在preHandle中,可以进行编码、安全控制等处理;这个方法在业务处理器处理请求之前被调用,在该方法中对用户请求request进行处理。如果程序员决定该拦截器对请求进 行拦截处理后还要调用其他的拦截器,或者是业务处理器去进行处理,则返回true;如果程序员决定不需要再调用其他的组件去处理请求,则返回false。
在postHandle中,有机会修改ModelAndView;这个方法在业务处理器处理完请求后,但是DispatcherServlet向客户端返回请求前被调用,在该方法中对用户请求request进行处理。
在afterCompletion中,可以根据ex是否为null判断是否发生了异常,进行日志记录。这个方法在DispatcherServlet完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作。
下面ControllerInterceptor 继承自HandlerInterceptorAdapter
public class ControllerInterceptor extends HandlerInterceptorAdapter {
/**
* 在Controller方法前进行拦截
*/
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
System.out.println("ControllerInterceptor.preHandle()");
return true;
}
/**
* This implementation is empty.
*/
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("ControllerInterceptor.postHandle()");
}
/**
* 在Controller方法后进行拦截
*/
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("ControllerInterceptor.afterCompletion()");
}
}
<bean id="controllerInterceptor" class="com.web.spring.mvc.interceptor.ControllerInterceptor"/
发起请求,进入拦截器链,运行所有拦截器的preHandle方法:
当preHandle方法返回false时,从当前拦截器往回执行所有拦截器的afterCompletion方法,再退出拦截器链。
当preHandle方法全为true时,执行下一个拦截器,直到所有拦截器执行完。再运行被拦截的Controller。然后进入拦截器链,运行所有拦截器的postHandle方法,完后从最后一个拦截器往回执行所有拦截器的afterCompletion方法.
当有拦截器抛出异常时,会从当前拦截器往回执行所有拦截器的afterCompletion方法
xml配置:
<bean id="handlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="interceptors">
<list>
<ref bean="controllerInterceptor" />
</list>
</property>
<property name="mappings">
<props>
<prop key="/hao/hello.do">helloWorldController</prop>
</props>
</property>
</bean>
关于spring拦截器配置:
如果基于xml配置使用Spring MVC,可以利用SimpleUrlHandlerMapping、BeanNameUrlHandlerMapping进行Url映射(相当于struts的path映射)和拦截请求(注入interceptors),如果基于注解使用Spring MVC,可以使用DefaultAnnotationHandlerMapping注入interceptors。注意无论基于xml还是基于注解(指的是spring的controller是配置还是注解),HandlerMapping bean(如上面的ControllerInterceptor)都是需要在xml中配置的。
在这个例子中,我们假设UserController中的注册操作只在9:00-12:00开放,那么就可以使用拦截器实现这个功能。
public class TimeInterceptor extends HandlerInterceptorAdapter {
//继承HandlerInterceptorAdapter类
private int openingTime; //openingTime 属性指定上班时间
private int closingTime; //closingTime属性指定下班时间
private String outsideOfficeHoursPage;
//outsideOfficeHoursPage属性指定错误提示页面的URL
public void setOpeningTime(int openingTime) {
this.openingTime = openingTime;
}
public void setClosingTime(int closingTime) {
this.closingTime = closingTime;
}
public void setOutsideOfficeHoursPage(String outsideOfficeHoursPage) {
this.outsideOfficeHoursPage = outsideOfficeHoursPage;
}
//重写 preHandle()方法,在业务处理器处理请求之前对该请求进行拦截处理
public boolean preHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler)
throws Exception {
Calendar cal = Calendar.getInstance();
int hour = cal.get(Calendar.HOUR_OF_DAY); //获取当前时间
if (openingTime<=hour && hour<closingTime) { //判断当前是否处于工作时间段内
return true;
} else {
response.sendRedirect(outsideOfficeHoursPage); //返回提示页面
return false;
}
}
}
上面的代码重载了preHandle()方法,该方法在业务处理器处理请求之前被调用。在该方法中,首先获得当前的时间,判断其是否在 openingTime和closingTime之间,如果在,返回true,这样才会调用业务控制器去处理该请求;否则直接转向一个静态页面,返回 false,这样该请求就不会被处理。
xxx-servlet.xml中对拦截器进行的配置:
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="helloWorld.do">helloWorldAction</prop>
<prop key="login.do">loginController</prop>
</props>
</property>
<property name="interceptors">
<!--在interceptors 属性中定义所有的拦截器-->
<list>
<ref bean="officeHoursInterceptor"/>
<!--引用officeHoursInterceptor 拦截器-->
</list>
</property>
</bean>
<!--定义TimeInterceptor拦截器,id为officeHoursInterceptor -->
<bean id="officeHoursInterceptor" class="com.examp.ch23.TimeInterceptor">
<!--openingTime 属性指定上班时间-->
<property name="openingTime"><value>9</value></property>
<!--closingTime属性指定下班时间-->
<property name="closingTime"><value>18</value></property>
<!--outsideOfficeHoursPage属性指定提示页面的URL-->
<property name="outsideOfficeHoursPage"><value>http://localhost:8080/ch23/outsideOfficeHours.html</value></property>
</bean>
上面代码用bean标签去定义TimeInterceptor,令其id为officeHoursInterceptor,并给它的3个 属性赋值。SimpleUrlHandlerMapping的mappings属性指定了拦截器要拦截的路径,interceptors属性<property name="interceptors">去指定officeHoursInterceptor为一个拦截器,可以在<list> 和</list>之间定义多个拦截器。
当然如果不定义mappings,则默认拦截所有对Controller的请求。DefaultAnnotationHandlerMapping与SimpleUrlHandlerMapping的配置属性一样。
基于注解的DefaultAnnotationHandlerMapping配置:
<beans>
<bean id="handlerMapping" class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="interceptors">
<bean class="com.cgodo.daowole.web.interceptor.TimeBasedAccessInterceptor "/>
</property>
</bean>
<beans>
由于interceptors是一个list因此可以这么设置它的属性
<property name="interceptors">
<list>
<ref bean="officeHoursInterceptor"/>
</list>
</property>
如果配置了<mvc:annotation-driven/>,那么DefaultHandlerInterceptor拦截器将不会起作用。
http://wsj356428476.iteye.com/blog/1396512
http://www.cnblogs.com/zhaoyang/archive/2012/01/07/2315428.html
*********************************
在spring的web项目中常常会在tomcat启动的时候出现这种提示:
log4j:WARN No appenders could be found for logger (org.springframework.web.context.ContextLoader).
log4j:WARN Please initialize the log4j system properly.
web应用程序的log4j.properties文件之前就报出来了。是在加载 org.springframework.web.context.ContextLoader这个listener的时候没找到log4j的配置文件造成的。
仔细查看web.xml发现在加载org.springframework.web.context.ContextLoader这个 listener之后才加载org.springframework.web.util.Log4jConfigListener,把log4j的配置放到org.springframework.web.context.ContextLoader之前,就可以解决这个问题了。
************************
在使用spring MVC时,我们一般将controller层的相关配置放到xxx-servlet.xml中(如controller层的包扫描),将service,dao,数据源,事务,包扫描等配置放在applicationContext.xml中。
********************
Spring3.0 MVC @ResponseBody 的作用是把返回值直接写到HTTP response body里。具体实现AnnotationMethodHandlerAdapter类handleResponseBody方法,具体实现代码:
结果输出乱码:???????
如果用的是<mvc:annotation-driven />则不会有UTF-8乱码,如果是使用AnnotationMethodHandlerAdapter则可能会出现乱码。
有人跟踪@ResponseBody 的实现类发现其默认的编码是 iso-8859-1,
解决办法,在spring mvc的配置文件中手工配置bean:
<!-- 启动Spring MVC的注解功能,完成请求和注解POJO的映射 -->
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" >
<property name="messageConverters">
<list>
<bean class = "org.springframework.http.converter.StringHttpMessageConverter">
<property name = "supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
</list>
</property>
</bean>
</list>
</property>
</bean>
这样通过配置AnnotationMethodHandlerAdapter类messageConverters属性来指定编码。
记住,需要把bean部分加入到<context:component-scan base-package="com.zlscw.mvc" />前面,这样就可以在jquery中直接调用而不出现乱码了。
上面的解决办法只对使用spring3.0有效,Spring 3.1后AnnotationMethodHandlerAdapter已经过期了,现在用的是RequestMappingHandlerAdapter。
第一种办法就是增加produces="text/plain;charset=UTF-8;"
@RequestMapping(value="/",produces="text/plain;charset=UTF-8;")
@ResponseBody
public String paymentxx() {
return "返回中文字符";
}
RequestMapping的属性:
consumes: 指定处理请求的提交内容类型(Content-Type),例如application/json, text/html;
produces: 指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回;
params: 指定request中必须包含某些参数值是,才让该方法处理。
headers: 指定request中必须包含某些指定的header值,才能让该方法处理请求
value: 指定请求的实际地址,指定的地址可以是URI Template 模式(后面将会说明);
method: 指定请求的method类型, GET、POST、PUT、DELETE等;
上面的解决方案有点不好,要每一个方法都添加这么一句代码,虽然灵活性高,优雅,但是却麻烦。
第二种解决办法:
在声明注解驱动器(<mvc:annotation-driven>)的时候的控制编码的转换增加<mvc:message-converters>元素
<!-- 解决@ResponseBody 的实现类其默认的编码是 iso-8859-1的问题 -->
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes" value="text/html;charset=UTF-8"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
如果<mvc:annotation-driven>中又配置了conversion-service属性,不会影响mvc:message-converters的功能。
********************
spring mvc 表单映射date类型字段的问题
在mvc中如果表单属性的类型是日期型时,从页面绑定字符串数据会出错
Failed to convert property value of type [java.lang.String] to required type [java.util.Date] for property 'expert.birthdate'; nested exception is java.lang.IllegalArgumentException: Cannot convert value of type [java.lang.String] to required type [java.util.Date] for property 'birthdate': no matching editors or conversion strategy found
解决方法
在spring-servlet.xml配置文件中增加:
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
.....
<property name="webBindingInitializer">
<bean class="com.util.MyWebBinding" />
</property>
</bean>
public class MyWebBinding implements WebBindingInitializer {
public void initBinder(WebDataBinder binder, WebRequest request) {
//1. 使用spring自带的CustomDateEditor
//SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
//binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
//2. 自定义的PropertyEditorSupport
binder.registerCustomEditor(Date.class, new CustomerDateTimeEditor());
}
}
public Class CustomerDateTimeEditor extends PropertyEditorSupport{
public void setAsText(String text)throws IllegalArgumentException{
if(StringUtils.hasText(text){
setValue(DateUtil.StringToDate(text);
}else{
setValue("");
}
}
public String getAsText(){
Date value=(Date)getValue();
if(value==null){
return "";
}else{
Calendar calendar=Calendar.getInstance();
calendar.setTime(value);
if(calendar.get(Calendar.HOUR)==0 && calendar.get(Calendar.MINUTE)==0){
return DateUtil.DateToString(value.DateStyle.YYYY_MM_DD);
}else{
return DateUtil.DateToString(value.DateStyle.YYYY_MM_DD_HH_MM_SS);
}
}
}
}
以上DateUtil,DateStyle见21day日期工具类。
注意上面的方法只适用spring3.0,而spring3.1以后发生了改变,已经不适用。因为:
在spring mvc 3.1中,对应变更为
DefaultAnnotationHandlerMapping -> RequestMappingHandlerMapping
AnnotationMethodHandlerAdapter -> RequestMappingHandlerAdapter
AnnotationMethodHandlerExceptionResolver -> ExceptionHandlerExceptionResolver
以上都在使用了annotation-driven后自动注册。而且对应分别提供了AbstractHandlerMethodMapping , AbstractHandlerMethodAdapter和 AbstractHandlerMethodExceptionResolver以便于让用户更方便的实现自定义的实现类。
<mvc:annotation-driven/>相当于注册了DefaultAnnotationHandlerMapping和AnnotationMethodHandlerAdapter两个bean,配置一些messageconverter。即解决了@Controller注解的使用前提配置。
spring mvc <mvc:annotation-driven />会自动启动Spring MVC的注解功能,但实际它做了哪些工作呢?如下:
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="order" value="1" />
</bean>
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="webBindingInitializer">
<bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
<property name="conversionService" ref="conversionService" />
<property name="validator" ref="validator" />
</bean>
</property>
</bean>
<bean id="conversionService" class="org.springframework.samples.petclinic.util.PetclinicConversionServiceFactory" />
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
从上面的配置可以看出,我的配置应该是被sping的配置覆盖了,<mvc:annotation-driven />配置中已经包含了webBindingInitializer的配置,看来使用<mvc:annotation-driven />后与原来的配置出现了重复,这种情况下不管<mvc:annotation-driven />放在上面还是放在下面都会出现问题。
使用conversion-service来注册自定义的converter
DataBinder实现了PropertyEditorRegistry, TypeConverter这两个interface,而在spring mvc实际处理时,返回值都是return binder.convertIfNecessary(见HandlerMethodInvoker中的具体处理逻辑)。因此可以使用customer conversionService来实现自定义的类型转换。
从<mvc:annotation-driven />中配置可以看出,AnnotationMethodHandlerAdapter已经配置了webBindingInitializer,我们可以通过设置其属性conversionService来实现自定义类型转换。
先修改配置文件:
需要修改spring service context xml配置文件中的annotation-driven,增加属性conversion-service指向新增的conversionService bean。
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="project3cs.pub.utils.XmlDateConverter" />
</list>
</property>
</bean>
<mvc:annotation-driven conversion-service="conversionService" />
XmlDateConverter.java类代码如下:
public class XmlDateConverter implements Converter<String, Date> {
@Override
public Date convert(String source) {
try {
return DateUtil.StringToDate(source);
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
}
这样就解决springMVC Date日期问题和日期类型字符串传输到后台Date字段会报出400 bad request错误。Spring 400 bad request就是Spring认为传输的内容与后台字段类型不匹配造成的。