2. 使用SpringMVC框架开发WEB项目
2.5. 显示页面
在SpringMVC中,当控制器接收到请求后,可以通过JSP页面向客户端呈现数据,或者使用其它的页面技术,例如Thymeleaf等。
如果需要使用Thymeleaf来呈现页面与数据,则需要在项目中添加Thymeleaf依赖,与Thymeleaf整合Spring的依赖:
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>3.0.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring4</artifactId>
<version>3.0.11.RELEASE</version>
</dependency>
此前在控制器中返回String
类型的数据,其值就是ModelAndView
中的View
,即返回的字符串表示视图名称,则需要通过ViewResolver
得到视图组件,在使用Thymeleaf
时,需要使用的ViewResolver
是ThymeleafViewResolver
:
<!-- 配置模版解析器 -->
<!-- 1. ClassLoaderTemplateResolver,文件需要放在resources下 -->
<!-- 2. ServletContextTemplateResolver,文件需要放在webapp下 -->
<bean id="templateResolver"
class="org.thymeleaf.templateresolver.ClassLoaderTemplateResolver">
<property name="prefix"
value="/templates/" />
<property name="suffix"
value=".html" />
<property name="characterEncoding"
value="utf-8" />
<property name="templateMode"
value="HTML" />
<property name="cacheable"
value="false" />
</bean>
<!-- 配置模版引擎 -->
<bean id="templateEngine"
class="org.thymeleaf.spring4.SpringTemplateEngine">
<property name="templateResolver"
ref="templateResolver" />
</bean>
<!-- 配置视图解析器 -->
<bean class="org.thymeleaf.spring4.view.ThymeleafViewResolver">
<property name="templateEngine"
ref="templateEngine" />
<property name="characterEncoding"
value="utf-8" />
</bean>
最后,在src/main/resources/
下创建templates
文件夹,并在文件夹下创建hello.html
文件,并将控制器中处理请求的方法的返回值改为"hello"
即可:
@RequestMapping("hello.do")
public String showHello() {
System.out.println("UserController.showHello()");
return "hello";
}
3. 创建新的案例
3.1. 案例目标
在浏览器中,输入http://localhost:8080/SpringMVC02/reg.do
,在页面中将显示用户注册页面,在页面中,至少包含用户名、密码、年龄、手机号码、电子邮箱这5个输入框,和1个提交按钮。
3.2. 创建项目
创建Maven Project
,创建时勾选Create a simple project
,Group Id输入cn.tedu.spring
,Artifact Id输入SpringMVC02
,Packaging选择war
。
当项目创建完成后,需要:
-
生成
web.xml
文件; -
添加Tomcat运行环境;
-
从前序项目的
pom.xml
中复制依赖的代码到当前项目中; -
将前序项目中的
spring.xml
复制到当前项目中; -
将前序项目中的
web.xml
中关于DispatcherServlet
的配置复制到当前项目中; -
检查
web.xml
中关于DispatcherSerlvet
配置中加载的Spring配置文件的名称是否正确。
3.3. 通过控制器接收并处理请求
创建cn.tedu.spring
包(请检查spring.xml中配置的组件扫描是否与之一致),并在该包之下创建UserController
,在类的声明之前添加@Controller
注解:
package cn.tedu.spring;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
}
然后,在类中添加处理请求的方法:
@RequestMapping("reg.do")
public String showReg() {
// /templates/reg.html
return "user/reg"; // 返回值可以理解为即将要创建的html文件的名称
}
3.4. 显示页面
先检查spring.xml
是否存在关于视图解析器的配置,如果没有,从前序项目中复制过来!
检查关于视图解析器中的配置,如果使用的模版解析器是ClassLoaderTemplateResolver
,则需要将HTML文件创建到src/main/resource
下,如果使用的是ServletContextTemplateResolver
,则需要将HTML文件创建到webapp
下!
再检查模版解析器中配置的前缀,以决定是否需要创建文件夹!
然后,在指定的文件夹中创建HTML页面。
4. 接收客户端提交的请求参数
4.1. 【不推荐】通过HttpServletRequest接收请求参数
可以在处理请求的方法的参数列表中添加HttpServletRequest
类型的参数,在处理过程中,调用该参数对象的getParameter()
方法即可获取客户端(例如网页)中提交的请求参数:
@RequestMapping("handle_reg.do")
public String handleReg(HttpServletRequest request) {
System.out.println("UserController.handleReg()");
String username = request.getParameter("username");
String password = request.getParameter("password");
String age = request.getParameter("age");
String phone = request.getParameter("phone");
String email = request.getParameter("email");
System.out.println("username=" + username);
System.out.println("password=" + password);
System.out.println("age=" + age);
System.out.println("phone=" + phone);
System.out.println("email=" + email);
return null;
}
一般,并不推荐使用这种方式接收请求参数!主要原因有:
-
获取参数的过程比较麻烦;
-
如果需要的参数不是
String
类型,则需要自行转换数据类型; -
不便于执行单元测试。
4.2. 【推荐】直接将请求参数声明为处理请求的方法的参数
可以将请求参数声明为处理请求的方法的参数,使用时,可以直接将参数的类型声明为所期望的类型,例如希望age
是数值型的,就可以直接声明为Integer
或int
类型:
@RequestMapping("handle_reg.do")
public String handleReg(String username, String password, Integer age, String phone, String email) {
System.out.println("UserController.handleReg()");
System.out.println("username=" + username);
System.out.println("password=" + password);
System.out.println("age=" + (age + 1));
System.out.println("phone=" + phone);
System.out.println("email=" + email);
return null;
}
使用这种做法时,需要保证请求参数的名称,与处理请求的方法中的参数名称是一致的!如果不一致,则处理请求的方法的参数值是null
。
这种做法简单,直接,便于执行单元测试,但是,不适用于请求参数的数量较多的应用场景!
4.3. 【推荐】使用封装的类型作用请求参数
当请求参数的数量较多时,可以先创建User
类,在类中声明所有需要接收的请求参数:
public class User {
private String username;
private String password;
private Integer age;
private String phone;
private String email;
}
注意:以上这种类,必须存在无参数的构造方法,并且,相关属性必须有正确的SET方法!
然后,将User
作为处理请求的方法的参数即可:
@RequestMapping("handle_reg.do")
public String handleReg(User user) {
System.out.println("UserController.handleReg()");
System.out.println(user);
System.out.println(user.getUsername());
System.out.println(user.getPassword());
System.out.println(user.getAge());
System.out.println(user.getPhone());
System.out.println(user.getEmail());
return null;
}
这种做法也是SpringMVC推荐的做法!
4.4. 小结
以上3种获取请求参数的做法中,首先,始终不使用第1种做法!
当请求参数的数量较少,并且相对固定时,优先使用第2种(逐一声明所有参数)做法,否则,请求参数数量较多,或参数可能调整时,优先使用第3种(封装所有请求参数)做法!
并且,以上第2种做法和第3种做法可以同时使用!
5. 数据转发
5.1. 【不推荐】通过HttpServletRequest转发数据
在处理请求的方法的参数列表中添加HttpServletRequest
参数,然后,在处理过程中,将需要转发的数据封装在HttpServletRequest
对象中即可,例如:
String errorMessage = "登录失败!密码错误!";
request.setAttribute("msg", errorMessage);
完整代码示例:
@RequestMapping("handle_login.do")
public String handleLogin(
HttpServletRequest request,
String username, String password) {
System.out.println("UserController.handleLogin()");
System.out.println("username=" + username);
System.out.println("password=" + password);
// 假设root/1234是正确的用户名和密码
// 先判断用户名是否正确
if ("root".equals(username)) {
// 用户名正确,判断密码
if ("1234".equals(password)) {
// 密码也正确,则登录成功
} else {
// 密码错误
String errorMessage = "登录失败!密码错误!";
request.setAttribute("msg", errorMessage);
return "error";
}
} else {
// 用户名错误
String errorMessage = "登录失败!用户名错误!";
request.setAttribute("msg", errorMessage);
return "error";
}
return null;
}
后续,在HTML模版页面中,可以通过Thymeleaf表达式显示这些数据,例如:
<h3 th:text="${msg}"></h3>
如果处理请求的方法中有多个参数,在设计参数时,各参数并不区分先后顺序!
使用HttpServletRequest
依然存在不便于执行单元测试的问题!
5.2. 【推荐】通过ModelMap转发数据
关于ModelMap
的使用方式,与HttpServletRequest
基本一致!
代码示例:
@RequestMapping("handle_login.do")
public String handleLogin(
ModelMap modelMap,
String username, String password) {
System.out.println("UserController.handleLogin()");
System.out.println("username=" + username);
System.out.println("password=" + password);
// 假设root/1234是正确的用户名和密码
// 先判断用户名是否正确
if ("root".equals(username)) {
// 用户名正确,判断密码
if ("1234".equals(password)) {
// 密码也正确,则登录成功
} else {
// 密码错误
String errorMessage = "登录失败!密码错误!";
modelMap.addAttribute("msg", errorMessage);
return "error";
}
} else {
// 用户名错误
String errorMessage = "登录失败!用户名错误!";
modelMap.addAttribute("msg", errorMessage);
return "error";
}
return null;
}
使用ModelMap
时,相比HttpServletRequest
,更加易于执行单元测试,并且,ModelMap
的对象更加轻量级。
5.2. 【不推荐】使用ModelAndView作为处理请求的方法的返回值
将方法的返回值类型声明为ModelAndView
,创建ModelAndView
对象时,调用其ModelAndView(String viewName, Map<String, ?> model)
构造方法,就可以快速的设置返回的视图名称(viewName)和数据(model)。
@RequestMapping("handle_login.do")
public ModelAndView handleLogin(
String username, String password) {
System.out.println("UserController.handleLogin()");
System.out.println("username=" + username);
System.out.println("password=" + password);
ModelAndView mav;
// 假设root/1234是正确的用户名和密码
// 先判断用户名是否正确
if ("root".equals(username)) {
// 用户名正确,判断密码
if ("1234".equals(password)) {
// 密码也正确,则登录成功
} else {
// 密码错误
String errorMessage = "登录失败!密码错误!";
Map<String, Object> model = new HashMap<String, Object>();
model.put("msg", errorMessage);
mav = new ModelAndView("error", model);
return mav;
}
} else {
// 用户名错误
String errorMessage = "登录失败!用户名错误!";
Map<String, Object> model = new HashMap<String, Object>();
model.put("msg", errorMessage);
mav = new ModelAndView("error", model);
return mav;
}
return null;
}
-------------------------
附1:关于No Mapping Found错误的解决方案
该错误是在控制台提示的错误:
No mapping found for HTTP request with URI [/SpringMVC01/hello.do] in DispatcherServlet with name 'SpringMVC'
同时,在浏览器中,会显示404错误。
出现这种错误的原因可能有:
-
在Spring的配置文件中没有配置组件扫描;
-
控制器类没有放在组件扫描的包或其子包中;
-
控制器类没有添加
@Controller
注解,或者,添加了其它例如Component
注解; -
在控制器类中,处理请求的方法没有添加
@RequestMapping
注解; -
在浏览器中输入的请求路径与控制器中配置的路径不一致。