在POJO类定义出标注@Controller,在通过
用户可以创建数量不限的控制器,分别处理不同的业务请求,如LogonController、UserController等。每个控制器可拥有多个处理请求的方法,每个方法负责不同的请求操作。如何将请求映射到对应的控制器方法中是SpringMVC框架的重要任务之一,这项任务由@RequestMapping承担。
在控制器的类定义及方法定义处都可以标注@RequestMapping,类定义处的@RequestMapping提供初步的请求映射信息,方法定以处的@RequestMapping提供进一步的细分映射信息。DispatcherServlet截获请求后,就通过控制器上的@RequestMapping提供的映射信息确定请求锁对应的处理方法。
将请求映射到控制器处理方法的工作包含一系列映射规则,这些规则是根据请求中的各种信息指定的,具体包括请求URL、请求参数、请求方法、请求头这4个方面的信息项。
① 通过请求URL进行映射
@RequestMapping使用value值指定请求的URL,如@RequestMapping("/user")。需要注意的是,@RequestMapping在类定义处指定的URL相对于Web应用的部署路径,而在方法定义处指定的URL则相对于类定义处指定的URL。如果在类定义处未标出@RequestMapping,则仅在处理方法处标注@RequestMapping,此时方法处指定的URL则相对于Web应用的部署路径。
同一控制器的多个处理方法负责处理相同业务模块的不同操作,但凡涉及合理的Web应用都会讲这些操作请求安排在某一相同的URL之下。所以除非特别的原因,建议不要舍弃类定义出的@RequestMapping。同一控制器的多个处理方法负责处理相同业务模块的不同操作,但凡涉及合理的Web应用都会讲这些操作请求安排在某一相同的URL之下。所以除非特别的原因,建议不要舍弃类定义出的@RequestMapping。
@RequestMapping不但支持标准的URL,还支持Ant风格(?、*、**)和xxx占位符的URL。例:
[1] /user/*/createUser:匹配/user/aaa/createUser、/user/bbb/createUser 等URL请求
[2] /user/**/createUser:匹配/user/createUser、/user/aaa/bbb/createUser 等URL请求
[3] /user/createUser??:匹配/user/createUseraaa、/user/createUserbbb 等URL请求
[4] /user/{userId}:匹配/user/123、/user/456 等URL请求
[5] /user/**/{userId}:匹配/user/aaa/bbb/123、/user/aaa/456 等URL请求
通过@PathVariable可以将URL中的占位符参数绑定到控制器处理方法的入参中:
@Controller
@RequestMapping("user")
public class UserController{
@RequestMapping("/{userId}")
@ResponseBody
public Object showDetail(@PathVariable("userId")String userId){
return userId;
}
}
URL中的{xxx}占位符可以通过@PathVariable注解绑定到操作方法的入参中。类定位出@RequestMapping的URL如果使用占位符的参数则也可以绑定到处理方法的入参中。
@Controller
@RequestMapping("user/{userId}")
public class UserController{
@RequestMapping("addr/{name}")
@ResponseBody
public Object showDetail(@PathVariable String userId,@PathVariable String name){
return userId + name;
}
}
在默认情况下,Java类的反射对象并未包含方法入参的名称,在Java 8中可以通过javac -parameters生成发发入参的元数据信息,在低版本的Java中则可以通过javac -g打开生成所有调试信息的开发,这样也会包含发发入参名的原数据信息。所以要使上面代码中入参成功绑定URL中的占位符参数,必须保证在编译时输出方法名元信息。在Maven中可以显示配置maven-compiler-plugin编译插件,开启编译输出调试信息的开关。
org.apache.maven.plugins
maven-compiler-plugin
${java.version}
UTF-8
true
不过编译时打开debug开关会使目标类变大,对运行效率也有一定的负面影响。正式编译部署时往往会将此开关取消,所以最好在@PathVariable中显示指定绑定的参数名,以避免因编译方式不同造成参数绑定失败的隐患。
② 通过请求参数、请求方法或请求头进行映射
HTTP请求报文除URL外,还拥有其他众多的信息。以下是一个标准的HTTP请求报文
①是请求方法,GET和POST是最常见的HTTP方法,除此以外还包括DELETE、HEAD、OPTIONS、PUT、TRACE、不过,当前大多数浏览器只支持GET和POST,Spring提供了一个HiddenHttpMethodFilter,允许通过_method表单参数制定这些特殊的HTTP方法,实际上还是通过POST提交表单实现。服务器端配置了HiddenHttpMethodFilter后,Spring会根据_method参数制定的值模拟出相应的HTTP方法,这样就可以使用这些HTTP方法对处理方法进行映射了。
② 是请求对应的URL地址,它和报文头的Host属性组成完整的请求URL。
③ 是协议名称及版本号信息。
④ 是HTTP的报文头,报文头包含若干个属性,格式为属性名:属性值,服务器端据此获取客户端的信息。
⑤ 是报问题,它将一个页面表单中的组件值通过param1=value1¶m2=value2的键值对形式编码成一个格式化串,它承载多个请求参数的数据。不但报问题可以传递请求参数,请求URL也可以通过类似于这样的方式传递请求参数。
@RequestMapping除了可以使用请求URL映射请求外,还可以使用请求方法、请求头参数及请求参数映射请求:
@Controller
@RequestMapping("user")
public class UserController{
//使用请求方法及请求参数映射请求
@RequestMapping(path="delete",method=RequestMethod.POST,params="userId")
public String deleteUser(@RequestParam("userId")String userId){
return "user/list";
}
//使用报文头映射请求
@RequestMapping(path="info",headers="content-type=text/*")
public String deleteUser(@RequestParam("userId")String userId){
return "user/info";
}
}
@RequestMapping的value、method、params及headers分别标识请求URL、请求方法、请求参数及报文头的映射条件,他们之间是与的关系,联合使用多个条件项可让请求映射更加精准化。
params和headers分别通过请求参数及报文头属性进行映射,他们支持简单的映射表达式。下面以params表达式为例进行说明,headers可以参照params来理解。
[1] "param":表示请求需包含名为param的请求参数。
[2] "!param":表示请求不能包含名为param的请求参数。
[3] "param != 123":表示请求包含名为param的请求参数,但值不能为123。
[4] {"param1 = 123","param2"}:表示请求包含名为param1和param2的请求参数,并且param1的值为123。
SpringMVC通过分析处理方法的签名,将HTTP请求信息绑定到处理方法的相应入参中,然后再调用处理方法得到返回值,最后对返回值进行处理并返回响应。
SpringMVC对控制器处理方法签名的限制是很宽松的,用于几乎可以按自己喜欢的方式进行方法签名。在必要时对方法及方法入参标注响应的注解即可,SpringMVC会优雅的完成剩下的工作,将HTTP请求的信息绑定到响应的方法入参中,并根据方法返回值类型做出响应的后续处理。
一般情况下,处理方法的返回值类型为ModelAndView或String,前者包含模型和逻辑视图名,而后者仅代表一个逻辑视图名。
① 使用@RequestParam绑定请求参数值
在之前我们说过,Java类反射对象默认不记录方法入参的名称,因此需要在方法入参处使用@RequestParam注解指定其对应的请求参数。@RequestParam注解有一下三个参数:
[1] value:参数名
[2] required:是否必须,默认为true,标识请求中必须包含对应的参数名,如果不存在则抛出异常。
[3] defaultValue:默认参数名,在设置该参数时,自动将required设置为false。极少情况需要使用该参数,也不推荐使用该参数。
下面的实例中将userName和age请求参数分别绑定到handlell()方法的userName和age中,并自动完成类型转换。如果不存在age参数,则抛出异常
@RequestMapping
public String handlell(@RequestParamv(value="userName",required=false)String userName,
@RequestParam("age")int age){
}
② 使用@CookieValue绑定请求中的Cookie值
使用@CookieValue可让处理方法入参绑定某个Cookie的值,它和@RequestParam拥有3个一样的参数。来看一个使用@CookieValue的例子:
@RequestMapping(path="/handle")
public String handle(@CookieValue(value="sessionId",required=false)String sessionId,
@RequestParam("age")int age){
}
@CookieValue的value属性指定了Cookie的名称,required为false,标识请求中没有相应的Cookie时也不会报错。
③ 使用@RequestHeader绑定请求报文头的属性值
@RequestMapping(path="/handle")
public String handle(@RequestHeader("Accept-Encoding")String encoding,
@RequestHeader("Keep-Alive")long keepAlive){
}
如上所示,请求报文包含了多个报文头属性,服务器可据此获知客户端的信息,通过@RequestHeader即可将报文头的属性值绑定到处理方法的入参中。
④ 使用命令/表单对象绑定请求参数值
所谓命令/表单对象并不需要实现任何接口,仅是一个拥有若干属性的POJO。SpringMVC会按请求参数名和命令/表单对象属性名匹配的方式,自动为该对象填充属性值。支持级联的属性名,如dept.deptId等。
@RequestMapping(path="handle")
public void handle(User user){}
⑤ 使用ServletAPI对象作为入参
在SpringMVC中,控制器类可以不依赖任何ServletAPI对象,但是SpringMVC并不能阻止我们使用ServletAPI的类作为处理方法的入参。以下处理方法都可以正确的工作:
@Controller
@RequestMapping
public class UserController{
/**
* 同时使用HttpSevletRequest和HttpServletResponse作为参数
* @param request
* @param response
*/
@RequestMapping(path="queryUser")
public void queryUser(HttpSevletRequest request, HttpServletResponse response){
String userId = WebUtils.findParameterValue(request,"userId");
response.addCookie(new Cookie("userId",userId));
}
/**
* 仅使用HttpSevletRequest作为参数
* @param request
* @return
*/
@RequestMapping(path="/removeUser")
public Object removeUser(HttpServletRequest request){
String userId = WebUtils.findParameterValue(request,"userId");
ModelAndView mav = new ModelAndView();
mav.setViewName("success");
mav.addObject("userId",userId);
return mav;
}
/**
* 使用HttpSession作为参数
* @param session
* @return
*/
@RequestMapping(path="/queryUserSessionId")
public Object removeUser(HttpSession session){
session.setAttribute("sessionId",123);
return session;
}
}
在使用ServletAPI的类作为参数时,SpringMVC会自动将Web层对应的Servlet对象传递给处理方法的参数。处理方法参数可以同时使用ServletAPI类的参数和其他符合要求的参数,他们之间的位置顺讯没有特殊要求。
值得注意的是,如果处理方法自行使用HttpServletResponse返回响应,则处理方法的返回值设置成void即可。
SpringMVC在org.springframework.web.context.request包中定义了若干个可代理Servlet原生API类的接口,如WebRequest和NativeWebRequest,他们也允许作为处理类的参数,通过这些代理类可访问请求对象的任何信息
⑥ 使用I/O对象做为参数
Servlet的ServletRequest拥有getInputStream()和getReader()方法,可以通过他们读取请求的信息。相应的,Servlet的ServletResponse拥有getOutputStream()和getWriter()方法,可以通过他们输出响应信息。
SpringMVC允许控制器的处理方法使用java.io.InputStream/java.io.Reader及java.io.OutputStream/java.io.Writer作为方法的参数,SpringMVC将获取ServletRequest的InputStream/Reader或ServletResponse的OutputStream/Writer,然后传递给控制器的处理方法。
@Controller
@RequestMapping
public class UserController{
/**
* 同时使用HttpSevletRequest和HttpServletResponse作为参数
* @param request
* @param response
*/
@RequestMapping(path="queryUserImage")
public void queryUser(OutputStream os)throws IOException{
Resource res = new ClassPathResource("/image.jpg");//读取类路径下的图片
FileCopyUtils.copy(res.getInputStream(),os);//将图片写入输出流中
}
}
⑦ 其他类型的参数
控制器处理方法的参数除支持以上类型的参数外,还支持java.util.Locale、java.security.Principal,可以通过Servlet的HttpServletRequest的getLocale()和getUserPrincipal()方法得到相应的值。如果处理方法的参数类型为Locale或Principal,则SpringMVC自动从请求对象中获取响应的对象并传递给处理方法的参数中。
RFC3986定义了在URI中包含name-value的规范。随之在SpringMVC3.2中出现了@MatrixVariable注解,该注解的出现使得开发人员能够将请求中的矩阵变量绑定到处理器的方法参数中。而Spring4.0 更全面的支持这个规范,这也是Spring4.0 众多吸引人的特性之一。接下来我们一起了解下这个特性的使用方式。
在Matrix Variable中,多个变量可以使用 ; 分割,例如:
/books;author=yongqi.wang;year=2019
如果一个变量对应多个值,那么可以使用 , 分割,例如:
author = user1,user2,user3
或者使用重复变量名:
author = user1;author = user2;author = user3
来看一个复杂的例子:
//假如请求为:GET /books/42;a=11/authors/26;b=22
@RequestMapping(value="/books/{bookId}/authors/{authorId}",method=RequestMethod.GET)
public void findBook(@MatrixVariable(value="a",pathVar="bookId")int a,
@MatrixVariable(value="b",pathVar="authorId")int b,){
//a == 11 , b == 22
}
针对每个PathVariable绑定一个MatrixVariable,然后使用value和pathVar属性找到该值。
另外MatrixVariable也自带了一些属性可供选择:
@MatrixVariable(required=true,defaultValue="1")int a
默认MatrixVariable功能是开启的,如果不希望开启该功能,则需要手工将RequestMappingHandlerMapping中的removeSemicolonContent属性设置为true: