重定向
forward前缀
若要返回/WEB-INF/pages/success.jsp,则直接return "success";即可。
若要返回webapp下的helloworld.jsp页面:
相对路径 ../../hello,需return "../../helloworld";
forward前缀,转发一个页面,不会进行拼串。需return "forward:/helloworld.jsp";
格式: forward:转发的路径
@RequestMapping("/hello")
public String hello(){
return "forward:/helloworld.jsp";
}
@RequestMapping("/hello1")
public String hello1(){
return "forward:/hello";
}
redirect前缀
重定向 redirect:重定向的路径 视图解析器不会进行拼串
原生的Servlet重定向/路径需要加上项目名才能成功。
/helloworld.jsp:代表的是从当前项目下开始,SpringMVC会为路径自动的拼接上项目名。
//重定向到helloworld.jsp页面
@RequestMapping("/hello2")
public String hello2(){
return "redirect:/helloworld.jsp";
}
@RequestMapping("/hello3")
public String hello3(){
return "redirect:/hello2";
}
SpringMvc视图解析流程
1、方法执行后的返回值会作为页面地址参考,转发或者重定向到页面 2、视图解析器可能会进行页面地址的拼串; 具体流程 1、任何方法的返回值,最终都会被包装成ModelAndView对象 2、processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);来到页面的方法 视图渲染流程:将域中的数据在页面展示;页面就是用来渲染模型数据的;
3、调用render(mv, request, response);渲染页面
4、View(interface)与ViewResolver(interface) ViewResolver的作用是根据视图名(方法的返回值)得到View对象;
那么ViewReslover是如何根据方法的返回值(视图名),得到View对象的
protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
HttpServletRequest request) throws Exception {
//遍历所有的ViewResolver;
for (ViewResolver viewResolver : this.viewResolvers) {
//viewResolver视图解析器根据方法的返回值,得到一个View对象;
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
return null;
}
resolveViewName(viewName, locale)方法的实现
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
if (!isCache()) {
return createView(viewName, locale);
}
else {
Object cacheKey = getCacheKey(viewName, locale);
View view = this.viewAccessCache.get(cacheKey);
if (view == null) {
synchronized (this.viewCreationCache) {
view = this.viewCreationCache.get(cacheKey);
if (view == null) {
// Ask the subclass to create the View object.
//根据方法的返回值创建出视图View对象;
**view = createView(viewName, locale);**
if (view == null && this.cacheUnresolved) {
view = UNRESOLVED_VIEW;
}
if (view != null) {
this.viewAccessCache.put(cacheKey, view);
this.viewCreationCache.put(cacheKey, view);
if (logger.isTraceEnabled()) {
logger.trace("Cached view [" + cacheKey + "]");
}
}
}
}
}
return (view != UNRESOLVED_VIEW ? view : null);
}
}
创建视图对象的方法实现createView(viewName, locale)
@Override
protected View createView(String viewName, Locale locale) throws Exception {
// If this resolver is not supposed to handle the given view,
// return null to pass on to the next resolver in the chain.
if (!canHandle(viewName, locale)) {
return null;
}
// Check for special "redirect:" prefix.
**if (viewName.startsWith(REDIRECT_URL_PREFIX))** {
String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
**RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());**
return applyLifecycleMethods(viewName, view);
}
// Check for special "forward:" prefix.
**if (viewName.startsWith(FORWARD_URL_PREFIX))** {
String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
return **new InternalResourceView(forwardUrl);**
}
// Else fall back to superclass implementation: calling loadView.
//如果没有前缀就使用父类默认创建一个View;
**return super.createView(viewName, locale);**
}
返回View对象:
1、视图解析器得到View对象的流程:所有已经配置了的解析器通过增强for循环尝试根据视图名(方法的返回值)得到View对象(视图对象);若能够得到就返回,得不到就继续进行for循环,切换到下一个视图解析器; 2、调用View对象的render方法
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isTraceEnabled()) {
logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
" and static attributes " + this.staticAttributes);
}
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
prepareResponse(request, response);
//渲染要给页面输出的所有数据
renderMergedOutputModel(mergedModel, request, response);
}
InternalResourceView中有renderMergedOutputModel方法
@Override
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine which request handle to expose to the RequestDispatcher.
HttpServletRequest requestToExpose = getRequestToExpose(request);
// Expose the model object as request attributes.
//将模型中的数据放在请求域中
exposeModelAsRequestAttributes(model, requestToExpose);
// Expose helpers as request attributes, if any.
exposeHelpers(requestToExpose);
// Determine the path for the request dispatcher.
String dispatcherPath = prepareForRendering(requestToExpose, response);
// Obtain a RequestDispatcher for the target resource (typically a JSP).
RequestDispatcher rd = getRequestDispatcher(requestToExpose, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
"]: Check that the corresponding file exists within your web application archive!");
}
// If already included or response already committed, perform include, else forward.
if (useInclude(requestToExpose, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.include(requestToExpose, response);
}
else {
// Note: The forwarded resource is supposed to determine the content type itself.
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.forward(requestToExpose, response);
}
}
将模型中的所有数据取出来全放在request域中
- 这也就是为什么隐含域中的数据能够在request域中
protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {
for (Map.Entry<String, Object> entry : model.entrySet()) {
String modelName = entry.getKey();
Object modelValue = entry.getValue();
if (modelValue != null) {
request.setAttribute(modelName, modelValue);
if (logger.isDebugEnabled()) {
logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() +
"] to request in view with name '" + getBeanName() + "'");
}
}
else {
request.removeAttribute(modelName);
if (logger.isDebugEnabled()) {
logger.debug("Removed model object '" + modelName +
"' from request in view with name '" + getBeanName() + "'");
}
}
}
}
总结
视图解析器只是为了得到View对象 对于转发操作(将数据模型全部放在请求域中)或者重定向到页面的操作是视图(View)对象做的事情 因此,视图对象才是真正渲染视图
不论控制器返回一个String,ModelAndView,View都会转换为ModelAndView对象,由视图解析器解析视图,然后,进行页面的跳转
流程图
视图和视图解析器
- 请求处理方法执行完成后,最终返回一个 ModelAndView 对象。对于那些返回 String,View 或 ModeMap 等类型的处理方法,Spring MVC 也会在内部将它们- 装配成一个 ModelAndView 对象,它包含了逻辑名和模型对象的视图 Spring MVC 借助视图解析器(ViewResolver)得到最终的视图对象(View),最终的视图可以是 JSP ,也可能是 Excel、JFreeChart等各种表现形式的视图
- 对于最终究竟采取何种视图对象对模型数据进行渲染,处理器并不关心,处理器工作重点聚焦在生产模型数据的工作上,从而实现 MVC 的充分解耦
视图
- 图的作用是渲染模型数据,将模型里的数据以某种形式呈现给客户。
- 为了实现视图模型和具体实现技术的解耦,Spring 在 org.springframework.web.servlet 包中定义了一个高度抽象的 View 接口
- 视图对象由视图解析器负责实例化。由于视图是无状态的,所以他们不会有线程安全的问题
常用的视图实现类
视图解析器
SpringMVC 为逻辑视图名的解析提供了不同的策略,可以在 Spring WEB 上下文中配置一种或多种解析策略,并指定他们之间的先后顺序。每一种映射策略对应一个具体的视图解析器实现类。
- 视图解析器的作用比较单一:将逻辑视图解析为一个具体的视图对象。
- 所有的视图解析器都必须实现 ViewResolver 接口:
常用的视图解析器实现类
- 程序员可以选择一种视图解析器或混用多种视图解析器
- 每个视图解析器都实现了 Ordered 接口并开放出一个 order 属性,可以通过 order 属性指定解析器的优先顺序,order 越小优先级越高。
- SpringMVC 会按视图解析器顺序的优先顺序对逻辑视图名进行解析,直到解析成功并返回视图对象,否则将抛出 ServletException 异常
- InternalResourceViewResolver JSP 是最常见的视图技术,可以使用 InternalResourceViewResolve作为视图解析器