刚接触Spring MVC时,被Controller眼花缭乱的入参搞得不知东南西北,尤其是从Servelet转过来,固定的入参变成了随意设置的入参,可是困惑了一阵.现在,我感觉自己理清了不少,所以做个总结.
Spring MVC Controller入参很灵活,分一下类,可以大致分为如下几种:
* 从请求url当中来的参数
* 从Session中来的参数
* 从Cookie中来的参数
* 用于前后端传递数据的Map(例如ModelAndView
)
* HttpServletRequest和HttpServletResponse
* 其他不常用的类型,比如InputStream/OutputStream 和 Reader/Writer,WebRequest/NativeWebRequest(本文不介绍,见文末参考文档2)
只要参数类型属于上述几种,那么无论Controller的路由处理函数的入参写成有多少个,都是合法的
接下来就上述几种参数,再细细说明
这应当是最普遍的参数来源,细分以下,从请求URL中来的参数,可以再分为如下几种:
使用@PathVariable
注解(Spring专属),或者@PathParam
注解(Java标准),从url中获取参数
而对应映射的url需要使用占位符来表示.我们来举个例子:
@GetMapping("/test/{openId}/{name}")
public String test(@PathVariable("openId") Long openId,@PathVariable("name") String name ){
// here we get openId,name from url placeHolder
return "test";
}
这样配置的Controller,可以接受形如/test/11/li
,/test/12/li?queryOption=12&...
的url
此外,url占位符形式的映射,可以配置在整个Controller类上,这样一个Controller下所有的路由处理方法都可以接收占位符的入参,省时省力
同时,占位符这种形式,也是RESTful提倡的一种形式
使用RequestParam
注解(Spring专属),或者QueryParam
注解(Java标准),从url的query部分(?后内容)获取参数
而对应的url映射,不需要像占位符那样列出入参的名字,我们举个例子:
@GetMapping("/test")
public String test(@RequestParam("openId") Long openId,@RequestParam("name") String name){
//here we get openId, name from url
return "test";
}
这样配置的Controller,可以接受形如/test?openId=11&name=li&....
形式的参数
可以看出,从url的query部分取得参数和从url的路径占位符部分取得参数,其实都是可以的(在我们的例子中,都可以取得openId 和name),那么如何选择呢?
个人感觉
* 如果参数是一个Controller下多个路由处理函数共用的,那么使用url路径占位符的形式,可以节省编码量
* 此外,对于RESTful有要求的url,url路径占位符有很大的发挥空间
因为query部分并不显示在配置的路径映射里面,但是默认要求所有@RequestParam
都需要出现在url的query部分,否则匹配不上,显示400 bad request.如果允许某一个被@RequestParam
注解的url的query部分的参数为空,那么可以使用如下形式:@RequestPparam(value="openId",required=false)
来声明,这样,就允许url的query部分不传递openId,同时可以映射到对应的路由处理函数
将url的参数绑定到自定义的对象上,这是Spring MVC一个很有用的功能,帮助开发人员省去不少胶水代码
自定义对象的绑定规则不多:
* 自定义对象符合JavaBean规范
* 请求的url的路径占位符部分(上面提到的url参数第一种)和query部分(上面提到的url参数第二种),参数名和自定义对象的属性名一致
这样就可以将url的参数组成一个对象,当作入参使用
比如url形如/test/111?fromSite=111
对应自定义对象和Controller如下
@GetMapping("/test/{openId}")
public String test(Pojo pojo){
//here we get a pojo instance with openId and fromSite
return "test";
}
class Pojo{
private Long openId;
private String fromSite;
public Long getOpenId() {
return openId;
}
public void setOpenId(Long openId) {
this.openId = openId;
}
public String getFromSite() {
return fromSite;
}
public void setFromSite(String fromSite) {
this.fromSite = fromSite;
}
}
更多关于绑定自定义对象的内容,可以参考另一片文章:Spring MVC 复杂数据绑定
因为Session不在页面和url上体现,所以前端页面和url都不需要做变动
Spring MVC提供了两种读取HttpSession的方法
自由度很大的一种方式,可以操作所有存放在Session中的对象,代码如下
@GetMapping("/test")
public String test(HttpSession session){
//here we get HttpSession,just write HttpSession session in param
return "test";
}
只能获得先前存在Session中的指定的对象,如果指定的对象不存在,则会返回400 bad request.如果指定对象的类型和Session中存放的对象类型不一致,则会报类型转化的异常.
示例代码如下:
@GetMapping("/test")
public String test(@SessionAttribute("pojo") Pojo pojo){
//here we get pojo from Session
return "test";
}
从Cookie中取得参数,相比于Session,就显得简单粗暴了,只有一种方式,只能读取,不能设置,如果要设置,需要入参传入HttpServletRequest和HttpServletResponse设置,关于这两个类的使用,请在接下来的第二个Section.
示例代码:
@GepMapping("/test")
public String test(@CookieAttribute("test") String test/*cookie 只能存放String*/){
//here we get String test from cookie
return "test";
}
这里指的是用于前后端传递参数的Map,当前端使用JSP,或者Velocity等模板技术时,模板上是可以获得后台参数,用于渲染(填充)页面的,这个时候后台传来的参数放到哪里传到页面模板呢?放到HttpServletRequest里当然可以,此外还可以使用Map.
当Controller的路由处理函数入参声明一个Map后,处理函数就可以把需要传递到页面模板的数据放到Map当中去,当函数执行完return
到指定的页面后,页面就可以从Map中拿到数据了.页面拿数据的方式不需要任何不同,就是使用Map的key就可以
示例代码:
//路由处理函数
@GepMapping("/test")
public String test(Map map){
Pojo pojo=new Pojo();
pojo.setOpenId(100L);
pojo.setFromSite("www.test.com");
map.put("testPojo",pojo);
return "test";
}
//一个自定义的对象
class Pojo{
private Long openId;
private String fromSite;
public Long getOpenId() {
return openId;
}
public void setOpenId(Long openId) {
this.openId = openId;
}
public String getFromSite() {
return fromSite;
}
public void setFromSite(String fromSite) {
this.fromSite = fromSite;
}
}
//页面模板,使用EL表达式
here will has a pojo : openId= ${testPojo.openId} ,and fromSite=${testPojo.fromSite}
当你想拥有和操作Servlet一样的自由度的时候,可以尝试路由处理函数中传入HttpServletRequest和HttpServletResponse.
示例代码如下:
@GepMapping("/test")
public String test(HttpServletRequest request,HttpServletResponse response){
//here we get HttpServletRequest and HttpServletResponse,we can do something with request and response
//有没有觉得这个方法和Servlet的doGet和doPost很像了
//比如,我们实践一下在 **从Cookie中来的参数** 中提到的,增加Cookie的操作
Cookie cookie=new Cookie("test","this is a test Cookie");
cookie.setPath("/");
cookie.setMaxAge(-1);//代表页面关闭即销毁Cookie
response.addCookie(cookie);
return "test";
}
Controller的路由处理函数,可以传入的参数五花八门,从Servlet转过来的时候的确理不清头绪.当然,这其中的灵活性也是Spring MVC的优势.
不过只要入参的来源属于上述几种,那么无论入参写成什么样,有多少个都是合法的,下面举一个比较极端的,但是合法的例子:
@GepMapping("/test/{openId}/{name}")
public String test(@PathVariable("openId") Long openId,@PathVariable("name") String name,
@RequestParam(value="fromSite",required=false) String fromSite,
HttpSession session,@CookieAttribute("test") String test,@ModelAttribute Pojo pojo/*自定义JavaBean对象*/,
HttpServletRequest request,HttpServletResponse response){
return "test";
}