SpringMVC 通过注解实现的 Controller 常用的返回值类型主要有三种:返回字符串,返回 ModelAndView,返回 void。
再看一下 springmvc.xml
配置文件的内容:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.lyu.qjl.interview.controller" />
<mvc:annotation-driven />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/">property>
<property name="suffix" value=".jsp">property>
bean>
beans>
下面的 Controller 是用来处理用户页面的请求,gotoUserEdit 方法是用来处理跳转到用户编辑页面的请求。
package com.lyu.qjl.interview.controller;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import com.lyu.qjl.interview.entity.User;
import com.lyu.qjl.interview.vo.UserVO;
/**
* 类名称:处理用户请求的handler
* 全限定性类名: com.lyu.qjl.interview.controller.UserController
* @author 曲健磊
* @date 2018年5月21日下午7:24:07
* @version V1.0
*/
@Controller
public class UserController{
/**
* 跳转到编辑用户页面
* @return
*/
@RequestMapping("/gotoUserEdit")
public String gotoUserEdit() {
return "userEdit";
}
}
这个 gotoUserEdit 方法匹配 /gotoUserEdit 请求。返回值为一个 userEdit
字符串,表示的是一个视图名,但是由于我们在 springmvc.xml 中已经配置了视图名的前缀和后缀,所以前端控制器实际上获得的视图名为 prefix配置的值
+ returnStr(方法返回值)
+ suffix配置的值
,即 /userEdit.jsp
,所以页面最终会跳转到网站根目录下的 userEdit.jsp。
**Question:**我想访问某个页面我直接去输入这个页面的地址 /userEdit.jsp 不就可以了吗,为什么还要先去请求 SpringMVC,再由它返回?
**Answer:**对于 WebContent 这一层目录下面的页面,用户如果知道某个 jsp 页面可以直接访问。但是,通常情况下为了保证页面的安全,我们一般的做法是在 WebContent 这一层目录下只留一个引导页面(index.jsp)作为跳转,把网站相关的页面放入 WEB-INF
文件夹下保护起来。因为放在 WEB-INF
文件夹下的页面没有办法通过地址栏直接访问,只能通过后台的跳转来间接的访问,所以就需要请求 SpingMVC 来返回相应的页面。
注:login.jsp, main.jsp, userEdit.jsp, userList.jsp, userMain.jsp 都是放在 WEB-INF 这层目录下的,只有 index.jsp 是放在 WebContext 这一层下的。
测试:直接访问 WEB-INF 文件夹下的 login.jsp 页面的结果如下:
这样我们就没有办法直接从地址栏去访问 WEB-INF 下的 login.jsp 页面了,只能通过 SpringMVC 来进行页面的跳转了。
这里还需要注意一点:由于页面已经放到了 WEB-INF 目录下,所以 springmvc.xml 配置文件中的视图解析器中的前缀也要对应的有所改动:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
...
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/">property>
<property name="suffix" value=".jsp">property>
bean>
beans>
扩展:
大家注意到那个放在 WebContent 那一层目录下的 index.jsp 页面了吗?它在实际应用中有什么作用呢?
它一般配置为项目的欢迎页面,即 web.xml 中的 welcome-file
。当我们在地址栏输入 localhost:8080/interview 的时候,首先就会访问该页面,然后由该页面向后台发请求,通过后台的 Controller 跳转到被 WEB-INF 文件夹保护起来的登录页面 login.jsp,最后,展现在我们面前的就是一个登录页面。
index.jsp 中的内容如下:
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%
// 转发一个 "/login" 请求给后台的 Controller,由 Controller 返回 WEB-INF 目录下的 login.jsp 页面
request.getRequestDispatcher("/login").forward(request, response);
%>
String 类型的返回值除了返回一个可以被视图解析器解析的视图名以外,还可以返回 含有 redirect 或 forward 标签的字符串,如下:
package com.lyu.qjl.interview.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* 类名称:用于处理登录请求的处理器
* 全限定性类名: com.lyu.qjl.interview.controller.LoginController
* @author 曲健磊
* @date 2018年5月21日下午7:02:03
* @version V1.0
*/
@Controller
public class LoginController{
@RequestMapping("/loginUser")
public String login(String username, String password, Model model) throws Exception {
if (username != null && password != null) {
if ("admin".equals(username) && "123".equals(password)) {
// 登录成功,重定向到主页面
return "redirect:/main";
} else {
// 向 request 域中设置错误提示信息
model.addAttribute("loginFlag", "用户名或密码错误");
// 登录失败,转发到登录页面
return "forward:/login";
}
} else {
// 只是把模型放到request域里面,并没有真正的安排好
model.addAttribute("loginFlag", "用户名或密码错误");
// 登录失败,转发到登录页面
return "forward:/login";
}
}
@RequestMapping("/login")
// 对外部提供接口的时候需要指定某些参数
public String login() {
return "login";
}
@RequestMapping("/main")
public String main() {
return "main";
}
}
redirect:/main
表示这是一个重定向到 /main 的请求, forward:/login
表示这是一个转发到 /login 的请求。
注意:如果在返回的字符串前加上了 redirect 或者 forward 标签,就不会走视图解析器,而是直接转发或重定向到指定的路径,所以对于重定向标签后面就不能加 WEB-INF ,因为重定向是客户端重新发送的请求,WEB-INF 目录下的页面仍然访问不到,但是对于转发标签的话后面可以跟 WEB-INF。
以上就是 Controller 的返回值为 String 时的另一种常见用途。
**Question:**为什么登录成功需要重定向到主页面?
**Answer:**因为转发客户端只发送了一次请求,如果登录成功转发到主页面的话,浏览器的地址栏中的 url 保留的是提交表单的请求,此时如果按 F5 刷新,就会造成二次提交表单,增大服务端的压力不说,还影响用户的体验,所以登录成功时要重定向到主页面,这样浏览器的地址栏保留的就是当前主页面的 url,无论怎么刷新都没事。
**Question:**为什么登录失败需要转发回登录页面?
**Answer:**用户登录失败,我们需要给用户提供相应的提示信息,如:“用户名密码不匹配”。如果重定向到登录页面,用户就没有办法获取到服务端设置在 request 域里面的错误提示信息,因为重定向的本质是客户端发送了两次请求,在第二次请求里面获取不到第一次请求中的数据,所以没有办法给用户相应的提示信息。而转发只发送了一次请求,转发回登录页面的时候可以获取到请求中的数据,所以登录失败时需要转发会登录页面。
应用场景:其实这种返回值的应用场景比较随意,既可以用来做页面的跳转,也可以在跳转到页面的时候携带一些数据,例如:查询用户列表。
package com.lyu.qjl.interview.controller;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import com.lyu.qjl.interview.entity.User;
import com.lyu.qjl.interview.vo.UserVO;
/**
* 类名称:处理用户请求的handler
* 全限定性类名: com.lyu.qjl.interview.controller.UserController
* @author 曲健磊
* @date 2018年5月21日下午7:24:07
* @version V1.0
*/
@Controller
public class UserController{
@RequestMapping("/getUserList")
public ModelAndView getUserList() {
System.out.println("进入getUseList");
ModelAndView modelAndView = new ModelAndView();
List<User> userList = new ArrayList<User>();
User user1 = new User("arry", 18, "男");
user1.setUserId(1L);
User user2 = new User("cc", 28, "女");
user2.setUserId(2L);
User user3 = new User("dd", 38, "男");
user3.setUserId(3L);
userList.add(user1);
userList.add(user2);
userList.add(user3);
// 将数据放入modelAndView对象
modelAndView.addObject("userList", userList);
// 将返回的逻辑视图名称放入modelAndView对象
modelAndView.setViewName("userList");
return modelAndView;
}
}
如果想在跳转到某个页面的时候携带一些数据,调用 ModelAndView 对象的 addObject 方法设置一些属性即可,不想带数据也可以不设置,但是必须得设置视图名 setViewName ,要不然它哪知道返回到哪个页面去呢?
如果是返回 void 会,就必须要在方法的参数中添加 HttpServletRequest 和 HttpServletResponse 来进行页面的跳转,代码如下:
public void test(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 转发到指定页面
request.getRequestDispatcher("xxx").forward(request, response);
// 或者重定向到指定页面
response.sendRedirect("/xxx");
}
其实,从本质上来说前面两种方法(String,ModelAndView)就是间接调用的 request 和 response 这个两个对象来进行页面的跳转,以及数据的存储。
稍微的总结一下,这篇文章主要讲了 SpringMVC 的 Controller 的三种返回值类型:String,ModelAndView,void。在讲 String 的时候扩展了一些 redirect 和 forward 标签,提了一下在实际应用中转发和重定向的用处,以及实际项目中需要把页面放入 WEB-INF 目录里保护起来。ModelAndView 可以携带数据也可以不携带数据,以及前两种方法本质上都是在操作 request 和 response 对象。希望文章对大家能有所帮助。