一、页面跳转控制

1、转发指令

1
2
3
4
5
6
7
8
9
@RequestMapping("/dispatch/forward")
public String testForward() {

// 物理视图:/WEB-INF/view/page.jsp
// 这个物理视图通过拼前后缀无法达到
// 所以使用forward指令
// 格式:forward:物理视图地址
return "forward:/WEB-INF/view/page.jsp";
}

测试的 index.jsp:

1
2
<a href="${pageContext.request.contextPath}/dispatch/forward">测试forward指令</a
><br />

之前我们使用的 return “target” 本身就是转发。

Spring MVC 又提供给我们一个 forward 方式来实现转发。

forward 后面跟的是一个完整的转发路径,后面跟的是一个物理视图,而 return “target” 返回的是一个逻辑视图。

forward 一般用在某个拼前后缀无法到达的地址。

2、重定向指令

1
2
3
4
5
6
7
8
@RequestMapping("/dispatch/redirect")
public String testRedirect() {

// 重定向指令格式:redirect:要重定向的目标地址
// 这里重定向中使用的路径和我们以前的写法不同,没有以Web应用名称开头
// 注意:SpringMVC会自动在这个路径前附加contextPath,所以我们不能再添加了,再加就错了
return "redirect:/feature/user/show.jsp";
}

测试的 index.jsp:

1
2
3
<a href="${pageContext.request.contextPath}/dispatch/redirect"
>测试redirect指令</a
><br />

3、使用原生对象完成转发

1
2
3
4
5
6
7
8
9
@RequestMapping("/dispatch/original/forward")
public void originalForward(
HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {

// 调用原生request对象执行转发
request.getRequestDispatcher("/WEB-INF/view/page.jsp").forward(request, response);
}

测试的 index.jsp:

1
2
3
<a href="${pageContext.request.contextPath}/dispatch/original/forward"
>测试使用原生对象转发</a
><br />

你看,使用原生 request 对象执行转发后,handler 方法的返回值就必须是 void,意思是我们自己指定了响应方式,不需要 SpringMVC 再进行处理了。

一个请求只能有一个响应,不能在 handler 方法里面给一个,然后 SpringMVC 框架再给一个。

4、使用原生对象完成重定向

1
2
3
4
5
6
7
8
9
@RequestMapping("/dispatch/original/redirect")
public void originalRedirect(
HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {

// 调用原生response对象执行重定向
response.sendRedirect(request.getContextPath() + "/feature/user/show.jsp");
}

测试的 index.jsp:

1
2
3
<a href="${pageContext.request.contextPath}/dispatch/original/redirect"
>测试使用原生对象重定向</a
><br />

使用原生 response 对象执行重定向后,handler 方法的返回值同样需要设置为 void,原因同上。

二、属性域使用

1、request 域

在 SpringMVC 中,当我们想把一个对象存入请求域有很多种操作方式,用哪一个都可以。

① 使用 Model 对象

1
2
3
4
5
6
7
8
9
@RequestMapping("/model/model")
public String saveToModel(Model model) {

// 将数据存入模型:本例使用Model类型的对象
// 底层原理:我们将数据存入模型,SpringMVC后面会从模型中取出,然后存入请求域
model.addAttribute("modelName", "modelValue");

return "target";
}

测试的 index.jsp:

1
2
3
<a href="${pageContext.request.contextPath}/model/model"
>测试使用Model类型对象</a
><br />

② 使用 ModelMap

1
2
3
4
5
6
7
8
9
@RequestMapping("/model/modelMap")
public String saveToModelMap(ModelMap modelMap) {

// 将数据存入模型:本例使用ModelMap类型的对象
// 底层原理:我们将数据存入模型,SpringMVC后面会从模型中取出,然后存入请求域
modelMap.addAttribute("modelMapName", "modelMapValue");

return "target";
}

测试的 index.jsp:

1
2
3
<a href="${pageContext.request.contextPath}/model/modelMap"
>测试使用ModelMap类型对象</a
><br />

③ 使用 Map

1
2
3
4
5
6
7
8
9
@RequestMapping("/model/map")
public String saveToMap(Map<String, Object> map) {

// 将数据存入模型:本例使用Map类型的对象
// 底层原理:我们将数据存入模型,SpringMVC后面会从模型中取出,然后存入请求域
map.put("mapName", "mapValue");

return "target";
}

测试的 index.jsp:

1
2
<a href="${pageContext.request.contextPath}/model/map">测试使用Map类型对象</a
><br />

④ 使用 HttpServletRequest

1
2
3
4
5
6
7
8
@RequestMapping("/model/original/request")
public String saveToRequest(HttpServletRequest request) {

// 将数据之间放入请求域
request.setAttribute("requestName", "requestValue");

return "target";
}

测试的 index.jsp:

1
2
3
<a href="${pageContext.request.contextPath}/model/original/request"
>测试使用原生request对象</a
><br />

2、session 域

向 session 域存入数据,真正有效的办法只有一个

1
2
3
4
5
6
7
8
@RequestMapping("/session/scope")
public String testSession(HttpSession session) {

// 需要存入会话域的数据,直接存入原生对象
session.setAttribute("sessionName", "sessionValue");

return "target";
}

测试的 index.jsp:

1
2
<a href="${pageContext.request.contextPath}/session/scope">测试使用会话域</a
><br />

3、application 域

向 application 域存入数据需要先拿到 ServletContext 对象。

拿到 ServletContext 对象后调用 setAttribute()方法。

1
2
3
4
5
6
7
@RequestMapping("/application/scope")
public String testAppScope() {

servletContext.setAttribute("appName", "appValue");

return "target";
}

测试的 index.jsp:

1
2
<a href="${pageContext.request.contextPath}/application/scope">测试使用应用域</a
><br />

4、ModelAndView

在把数据存入请求域时还有一个方法:使用 ModelAndView 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@RequestMapping("/model/and/view")
public ModelAndView testModelAndView() {

// 1.创建ModelAndView对象
ModelAndView modelAndView = new ModelAndView();

// 2.存入模型数据
modelAndView.addObject("mavName", "mavValue");

// 3.设置视图名称

// 方法一:使用逻辑视图
// modelAndView.setViewName("target");

// 方法二:使用物理视图
// 使用转发或重定向指令
// 在重定向的情况下,SpringMVC会把模型数据附着在URL地址后面
// URL地址:feature/user/show.jsp?mavName=mavValue
modelAndView.setViewName("redirect:/feature/user/show.jsp");


// 4.返回ModelAndView对象
return modelAndView;
}

见名知意,ModelAndView 就是把模型和视图封装到一起。其实即使我们没有明确使用 ModelAndView,SpringMVC 也会在 handler 方法执行完成后把模型和视图封装到 ModelAndView 对象中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//……
//检查handler方法的返回值是否为ModelAndView类型
if (returnValue instanceof ModelAndView) {
ModelAndView mav = (ModelAndView) returnValue;
//合并模型对象
mav.getModelMap().mergeAttributes(implicitModel);
return mav;
}
//检查handler方法的返回值是否为Model类型
else if (returnValue instanceof Model) {
//创建ModelAndView对象并将之前收集的模型对象存进去
return new ModelAndView().addAllObjects(implicitModel).addAllObjects(((Model) returnValue).asMap());
}
//检查handler方法的返回值是否为View类型
else if (returnValue instanceof View) {
//创建ModelAndView对象并存入视图和模型
return new ModelAndView((View) returnValue).addAllObjects(implicitModel);
}
//……
//检查handler方法的返回值是否为Map类型
else if (returnValue instanceof Map) {
//创建ModelAndView对象并将之前收集的模型对象存进去
return new ModelAndView().addAllObjects(implicitModel).addAllObjects((Map<String, ?>) returnValue);
}
//检查handler方法的返回值是否为String类型
else if (returnValue instanceof String) {
//创建ModelAndView对象并把handler方法返回的字符串作为视图名称,且存入之前收集的模型数据
return new ModelAndView((String) returnValue).addAllObjects(implicitModel);
}
//……

三、静态资源访问

1.提出静态资源问题

Web 项目的开发不可避免的要使用静态资源。在我们的项目中图片文件、音视频文件、CSS 文件、JavaScript 文件、HTML 文件等等凡是浏览器直接可以使用且不需要 Tomcat 解析的资源都是静态资源。

那么静态资源在 SpringMVC 中有什么问题呢?

如果在 web.xml 中我们配置 ulr-pattern 为“/”,那么访问静态资源时会返回 404。

1
2
3
4
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

原因是参照<url-pattern>/</url-pattern>这个配置,任何请求都会先和<url-pattern>/</url-pattern>进行匹配,而 / 会匹配所有资源,所以SpringMVC 就交给 DispatcherServlet 来处理这个请求

我们又没有任何一个@RequestMapping 和我们要访问的静态资源对应,所以 SpringMVC 认为并不存在这个资源,所以返回 404。

2、解决办法

① 解决办法 1

在 web.xml 配置文件中配置 ulr-pattern 配置扩展名,例如:<url-pattern>*.mvc</url-pattern>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!-- 配置SpringMVC的前端控制器:DispatcherServlet -->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>

<!-- 正常配置Servlet的全类名 -->
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<!-- 使用DispatcherServlet的初始化参数指定Spring配置文件的位置 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>

<!-- Servlet生命周期中,在默认情况下,是在第一次请求时创建对象 -->
<!-- 但是DispatcherServlet作为SpringMVC框架的核心组件,在初始化要做的操作非常多 -->
<!-- 这么多初始化操作如果等到第一次请求时才做,就来不及了 -->
<!-- 所以需要借助load-on-startup标签将它设置为随Web应用一起启动 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>

<!-- 对于DispatcherServlet来说,url-pattern有两种方式配置 -->
<!-- 方式一:匹配所有请求,使用『/』 -->
<!-- 方式二:匹配请求扩展名,使用*.扩展名格式 -->
<!--<url-pattern>/</url-pattern>-->

<!-- 配置*.mvc表示请求扩展名为mvc时才交给SpringMVC处理,否则与SpringMVC无关 -->
<url-pattern>*.mvc</url-pattern>
</servlet-mapping>

比如说测试的 index.jsp:

1
2
3
<a href="${pageContext.request.contextPath}/application/scope.mvc"
>测试使用应用域</a
><br />

加上扩展名以后 @RequestMapping(“/application/scope”) 依然可以正常解析 scope.mvc。

这样配置之后会有一个重要的、严格的限制:所有希望由 SpringMVC 来处理的请求,末尾都必须加上指定的扩展名,比如我们这个例子中的*.mvc。反之没有以*.mvc 结尾的请求都和 SpringMVC 无关,还是由 Tomcat 处理。

② 解决办法 2

还是保持<url-pattern>/</url-pattern>的配置,然后在 Spring 配置文件中加入如下配置:

1
2
3
4
5
6
<!-- 配置default-servlet-handler,告诉SpringMVC不要多管闲事 -->
<mvc:default-servlet-handler/>

<!-- 开启SpringMVC中的注解驱动功能 -->
<!-- ※注意:mvc:annotation-driven被称为SpringMVC的标配,配上准没错;不配就会导致很多功能无效 -->
<mvc:annotation-driven/>

单独使用 mvc:default-servlet-handler 是不行的,必须配合 mvc:annotation-driven。

四、mvc:view-controller

假设有下面这样一个 handler 方法:

1
2
3
4
@RequestMapping("/test/view/controller")
public String testViewController() {
return "result";
}

这个方法内部没有做任何处理,仅仅是把一个 URL 地址”/direct”映射到视图”result”。那么有没有办法简化一下呢?使用 mvc:view-controller 配置即可。

1
2
3
4
<!-- 配置view-controller,通过地址直接访问视图 -->
<!-- 注意:path属性@RequestMapping注解的区别是:path属性不能忽略请求扩展名 -->
<!-- 注意:view-controller也必须配合mvc:annotation-driven使用 -->
<mvc:view-controller path="/test/view/controller.mvc" view-name="target"/>

测试的 index.jsp

1
2
3
<a href="${pageContext.request.contextPath}/test/view/controller.mvc"
>测试view-controller</a
><br />

注意:mvc:view-controller 也必须搭配 mvc:annotation-driven。