最近参与的一个微信公众号相关项目的开发中,业务包含大量的页面跳转逻辑,以及拦截器的数据获取校验。其间也遇到一些困惑,在探究 Spring MVC 中 redirect 和 forward 的源码后,把
顺晟科技
2021-06-16 10:58:28
380
最近在一个微信微信官方账号相关项目的开发中,业务包含了很多截击机的页面跳转逻辑和数据采集验证。在此期间,我遇到了一些困惑。在Spring MVC中探索了重定向和转发的源代码后,总结了自己的经验,完成了这篇文章。
例如,当客户端请求进入控制器方法时,我们将判断当前用户状态,并可能跳转到用户中心页面、等待页面或错误页面。有许多类似的场景,需要重定向和转发请求。Sping MVC实现重定向或转发的方法有很多。我先整理一下,然后通过源代码加深理解。
常见的治疗方法
控制器视图方法之间的跳转只不过是带参数跳转和不带参数跳转。常用的方法是通过字符串映射请求映射进行重定向,或者通过模型视图对象或重定向视图对象进行重定向,下面逐一说明。
字符串重定向
返回映射到另一个控制器方法的字符串。如果有一个请求参数,它将在RequestMapping映射的字符串之后拼接。
//返回字符串映射方式
@RequestMapping('hello ')
公共字符串hello(HttpServletRequest请求,HttpServletResponse响应){
dosomesing();
返回“redirect :/bye”;
//return 'redirect:/bye?username=sudoz ';
}
模型和视图重定向
另一种方法是通过返回模型视图对象来跳转。同样,如果有一个请求参数,也可以以类似于GET参数的方式进行拼接:
//返回ModelAndView对象
@RequestMapping('hello ')
公共模型和视图hello(HttpServletRequest请求,HttpServletResponse resp) {
dosomesing();
返回新模型和视图(“重定向:/bye”);
//返回新ModelAndView(' redirect :/bye?username=sudoz ');
}
重定向视图重定向
另一种方法是通过返回RedirectView对象来跳转。此方法与上述方法的区别在于,重定向视图对象不需要设置重定向前缀:
//返回RedirectView对象
@RequestMapping('hello ')
public RedirectView hello() {
dosomesing();
返回新的ReDirectView('/bye ');
//返回新的RedirectView('bye?username=sudoz ');
}
带参数跳转
在做方法跳转的时候,如果想把参数带到下一个方法,上面代码中的URL参数拼接的方法有时候是不实用的。因为参数不一定是字符串,而且浏览器对URL的长度有限制。重定向属性对象可用于在请求重定向时保存参数。使用重定向属性重写上述代码:
@RequestMapping('/')
public ReDirectView hello(ReDirectAttributes attrs){
attrs.addAttribute('message ',' hello ');
attrs . AddFlashAttribute(' username ',' sudoz ');
返回新的ReDirectView(' hello ');
}
@RequestMapping('hello ')
映射字符串,字符串hello(@ModelAttribute('用户名')字符串用户名,
@ModelAttribute('message ')字符串消息){
MapString,String map=maps . new hashmap();
map.put('username ',username);
map.put('message ',message);
返回地图;
}
在上面的代码中,调用了addAttribute()和addFlashAttribute()方法,将两个值插入到RedirectAttributes对象中。如果查看源代码,可以知道RedirectAttributes接口的实现类继承了ModelMap,它本质上是HashMap的子类,所以可以用来存储键值对。这两种方法非常常见,在使用上肯定有区别:
addAttribute()方法将在网址后添加键值作为请求参数;
addFlashAttribute()方法在会话中临时存储键值。跳转到目标方法后,任务完成并从会话中删除。
使用curl命令测试:
curl -i http://localhost:8080/
HTTP/1.1 302
set-cookie : JSESSIONID=d1cc 5e 15 fa 8 ef 9474 C4 ced 7 D4 f 660 e 66;path=/;HttpOnly
location : http://localhost :8080/hello;jsessionid=d1cc 5e 15 fa 8 ef 9474 ced 7 D4 f 660 e 66?username=sudoz
内容-语言: en-US
内容-长度: 0
日期:2017年2月16日星期四12:33:46格林尼治标准时间
可以看到,通过addAttribute()添加的键值对变成了统一资源定位器后面的参数,addFlashAttribute()方法添加的键值对则没有出现在统一资源定位器上,而是存储在了会议中。跳转的目标方法通过@ModelAttribute('键)注解指定接收的参数。
再直接的和向前的区别
上面列出的3种方法,其实都是的实现在处理请求时的重定向,即再直接的跳转。另一种分发请求的方式是转发,即向前。(我不确定这么翻译是否正确,所以下面就直接用再直接的和向前来表述),二者的区别从超文本传送协议的规范中就明确了:
再直接的的超文本传送协议返回码是302,且跳转的新统一资源定位器会存储在超文本传输协议响应头的位置字段中。客户端在接收到反应后,会发起另一次请求,这次请求的统一资源定位器就是重定向的统一资源定位器
向前的转发过程只发生在服务端;小型应用程序容器会直接把源请求打向目标URL,而不会经由客户端发起请求;因此客户端接收到的响应是来自转发后的目标方法,但是浏览器呈现的统一资源定位器却并不会改变,且向前不能将参数转发出去。
弹簧靴测试
为了更清晰的比较各种方法,我把弹簧靴的测试代码贴出来。
@控制器
公共类家庭控制器{
@值(' sudoz ')
私有字符串用户名;
@GetMapping('/')
字符串索引(){
return 'redirect:hello?username=jimmessage=你好;
}
@GetMapping('redirectView ')
RedirectView redirectView() {
返回新的ReDirectView(' hello ');
}
@GetMapping('forward ')
模型和视图转发(重定向属性属性)
attrs。AddFlashAttribute(' username ',username);
attrs.addAttribute('message ',' hello ');
返回新模型和视图(' forward : hello ');
}
@GetMapping('重定向)
模型和视图重定向(重定向属性属性)
attrs。AddFlashAttribute(' username ',username);
attrs.addAttribute('message ',' hello ');
返回新的ModelAndView('重定向: hello ');
}
@GetMapping('hello ')
@ResponseBody
映射字符串,字符串你好(模型模型,@ModelAttribute('用户名)字符串用户名,
@ModelAttribute('消息')字符串消息){
映射字符串,字符串映射=映射。new hashmap();
map.put('username ',username);
map.put('message ',message);
返回地图;
}
}
分析源码
公共类UrlBasedViewResolver扩展了抽象视图解析器实现有序{
公共静态最终字符串重定向_ URL _前缀=' redirect:
公共静态最终字符串转发_网址前缀=' forward:
@覆盖
受保护视图创建视图(字符串视图名称,区域设置)引发异常{
//如果这个解析器不应该处理给定的视图,
//返回空传递给链中的下一个解析器。
if(!canHandle(视图名称,区域设置)){
返回空
}
//检查特殊的重定向: '前缀。
if(viewname。以(REDIRECT _ URL _ PREFFIX)开头){
string ReDirectURl=viewname。子字符串(REDIRECT _ URL _ PREFFREY。length());
重定向视图视图=新的重定向视图(重定向URL,isRedirectContextRelative(),isredirecthttp10 compatible());
返回applyLifecycleMethods(viewName,view);
}
//检查特殊的" forward: "前缀。
if(视图名称。以(FORWARD _ URL _ PREFFREY)开头){
string FORWARD URl=viewname。子串(FORWARD _ URL _ PREFFREY。length());
返回新的InternalResourceView(转发URL);
}
//否则退回到调用方法的超类实现:
返回super.createView(viewName,locale);
}
}
16
2021-06
16
2021-06
16
2019-08