一、视图解析器
通过 HelloWorld 程序我们看到了 handler 方法的返回值表示:
请求处理完成后,请 SpringMVC 执行一个请求转发。转发的地址就是 handler 方法的返回值。
假设我们在/WEB-INF/pages 目录下有一组 JSP 页面,那么访问它们的路径应该如下表所示:
文件名 | 转发路径 |
---|
apple.jsp | /WEB-INF/pages/apple.jsp |
grape.jsp | /WEB-INF/pages/grape.jsp |
banana.jsp | /WEB-INF/pages/banana.jsp |
orange.jsp | /WEB-INF/pages/orange.jsp |
watermelon.jsp | /WEB-INF/pages/watermelon.jsp |
…… | …… |
这很明显是有规律的,转发路径都是以“/WEB-INF/pages/”开头,以“.jsp”结尾。
基于这样一种情况,SpringMVC 做了一种设计:它允许我们把转发路径中前面的固定部分和后面的固定部分以前缀、后缀的形式写到配置文件中,然后我们的 handler 方法就仅仅指定中间不一样的部分即可。中间部分和前缀、后缀做字符串拼接。这就是 SpringMVC 提供的视图解析器。
我们在 SpringMVC 的配置文件(spring-mvc.xml)中额外加入下面的配置,视图解析器就生效了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="viewResolver">
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/> </bean>
|
有了视图解析器,handler 方法的返回值就简单了:
1 2 3 4 5 6 7 8 9 10 11
| @RequestMapping("/hello") public String sayHello() { System.out.println("嘿!我来了!");
return "target"; }
|
SpringMVC 会使用上面方法的返回值“result”和前缀后缀做字符串拼接,从而得到转发路径。
1
| "/WEB-INF/pages/"+"target"+".jsp"→→→→→→→→→→"/WEB-INF/pages/target.jsp"
|
然后按照拼接得到的结果转发请求。
二、@RequestMapping 注解
假设我们在同一个模块有下面几个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package com.atguigu.mvc.controller;
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping;
@Controller public class UserHandler {
@RequestMapping("/user/login") public String login() { return "target"; }
@RequestMapping("/user/register") public String register() { return "target"; }
@RequestMapping("/user/logout") public String logout() { return "target"; } }
|
它们在@RequestMapping 注解中的映射地址都是以/user 开头,能否统一提取出来呢?很简单,在类上再使用一个@RequestMapping 注解把/user 部分提取出来即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @Controller
@RequestMapping("/user/") public class UserHandler {
@RequestMapping("login") public String login() { return "target"; }
@RequestMapping("register") public String register() { return "target"; }
@RequestMapping("logout") public String logout() { return "target"; } }
|
测试的 index.jsp:
1 2 3 4 5 6 7
| <h3>测试@RequestMapping注解放在类上</h3> <a href="${pageContext.request.contextPath}/user/login">[用户模块登录功能]</a ><br /> <a href="${pageContext.request.contextPath}/user/register">[用户模块注册功能]</a ><br /> <a href="${pageContext.request.contextPath}/user/logout">[用户模块退出功能]</a ><br />
|
三、获取原生 Servlet API 对象
1、提出问题
在 Servlet 的 doGet()方法中,我们可以拿到原生、本真的 HttpServletRequest 和 HttpServletResponse 这样的对象,那么在 SpringMVC 的 handler 方法中能够拿到吗?
1 2 3
| protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { …… }
|
完全可以,直接从 handler 方法的参数位置传入即可。
2、传入 handler 方法,获取原生 Servlet API 对象
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 31 32 33
| @Controller public class OriginalObjectHandler {
@Autowired private ServletContext servletContext;
@RequestMapping("/original/object") public String originalObject( HttpServletRequest request, HttpServletResponse response, HttpSession session ) {
System.out.println("request = " + request); System.out.println("response = " + response); System.out.println("session = " + session);
ServletContext servletContext1 = session.getServletContext(); System.out.println("servletContext = " + servletContext1);
return "target"; }
@RequestMapping("/original/servlet/context") public String originalServletContext() {
System.out.println("servletContext = " + servletContext);
return "target"; } }
|
3、获取 ServletContext 对象的两种方式
① 通过 HttpSession 对象获取
1
| session.getServletContext()
|
② 使用@Autowired 注解装配
1 2
| @Autowired private ServletContext servletContext;
|
测试的 index.jsp:
1 2 3 4 5 6 7
| <h3>测试获取原生的Servlet API</h3> <a href="${pageContext.request.contextPath}/original/object" >测试获取HttpServletRequest对象、HttpServletResponse对象、HttpSession对象</a ><br /> <a href="${pageContext.request.contextPath}/original/servlet/context" >测试获取ServletContext对象</a ><br />
|
返回的结果:
1 2 3 4 5 6
| request = org.apache.catalina.connector.RequestFacade@50727615 response = org.apache.catalina.connector.ResponseFacade@28312413 session = org.apache.catalina.session.StandardSessionFacade@300f8e6c servletContext = org.apache.catalina.core.ApplicationContextFacade@2959d9e6
servletContext = org.apache.catalina.core.ApplicationContextFacade@2959d9e6
|
分析:两个方法获取到的 servletContex 的 hashCode 相同。
一个 web 应用只能有一个 servletContext,用不同的方式拿到的是同一个应用。
四、获取请求参数
1、什么是请求参数?
比如说在 url 地址的后面写上 ?键值对 告诉后端程序要删除谁
1
| <a href="emp/remove?empId=3">删除</a>
|
再比如说下面的表单,用 name 属性的值带上请求参数。
1 2 3 4 5 6
| <form action="emp/save" method="post"> 姓名:<input type="text" name="empName" /><br /> 年龄:<input type="text" name="empAge" /><br /> 工资:<input type="text" name="empSalary" /><br /> <input type="submit" value="保存" /> </form>
|
2、请求参数的四种情况
① 一名一值
1
| <a href="emp/remove?empId=3">删除</a>
|
在 handler 方法的参数上使用@RequestParam 注解。
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 31 32
| package com.atguigu.mvc.controller;
@Controller public class ParamHandler {
@RequestMapping("/param/one/name/one/value") public String paramOneNameOneValue( // 使用@RequestParam注解接收请求中包含的请求参数 // SpringMVC会把获取到的请求参数从形参位置传入
// @RequestParam注解的required属性默认为true表示当前请求参数必须提供 // 如果没有提供会看到:HTTP Status 400 - Required String parameter 'userName' is not present错误 // 如果这个参数可有可无,那么可以把required属性设置为false,此时不提供参数也不会报错,会返回变量的默认值
// @RequestParam注解的defaultValue属性用于给这个请求参数设置默认值 // 当请求中不包含这个请求参数时,把默认值从形参位置传入 // 当我们设置了defaultValue属性提供了默认值,即使在required = true的情况下请求参数没有提供也不会报错,会返回我们设置的默认值 // @RequestParam(value = "userName", required = true, defaultValue = "dog") String userName
// 当前请求参数名和形参名一致时,可以省略@RequestParam,但是从代码可读性角度来说最好还是写上 // 即省略@RequestParam("userName"),如下 // String userName
@RequestParam("userName") String userName ) {
System.out.println("userName = " + userName);
return "target"; } }
|
测试的 index.jsp
1 2 3 4 5
| <h3>测试发送请求参数</h3> <a href="${pageContext.request.contextPath}/param/one/name/one/value?userName=Tom" >一个名字一个值</a ><br />
|
② 一名多值
测试的 index.jsp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <form action="${pageContext.request.contextPath}/param/one/name/multi/value" method="post" > 请选择你最喜欢的球队: <input type="checkbox" name="team" value="Brazil" />巴西 <input type="checkbox" name="team" value="German" />德国 <input type="checkbox" name="team" value="French" />法国 <input type="checkbox" name="team" value="Holland" />荷兰 <input type="checkbox" name="team" value="Italian" />意大利 <input type="checkbox" name="team" value="China" />中国 <br /> <input type="submit" value="保存" /> </form>
|
使用 List 或数组来接收。
1 2 3 4 5 6 7 8 9
| @RequestMapping("/param/one/name/multi/value")
public String paramOneNameMultiValue(@RequestParam("team") List<String> teamList) {
for (String team : teamList) { System.out.println("team = " + team); } return "target"; }
|
③ 表单数据正好对应一个实体类
测试的 index.jsp:
1 2 3 4 5 6
| <form action="${pageContext.request.contextPath}/param/entity" method="post"> 姓名:<input type="text" name="empName" /><br /> 年龄:<input type="text" name="empAge" /><br /> 工资:<input type="text" name="empSalary" /><br /> <input type="submit" value="保存" /> </form>
|
对应的实体类:
1 2 3 4 5 6 7 8 9
| public class Employee {
private Integer empId; private String empName; private int empAge; private double empSalary;
……
|
直接使用和表单对应的实体类类型接收
1 2 3 4 5 6 7
| @RequestMapping("/param/entity") public String paramEntity(Employee employee) {
System.out.println("employee = " + employee);
return "target"; }
|
结果:
分析:
Spring MVC 是使用请求参数的名字(上面 name 的属性值)去对应的实体类(Employee)找对应的 set 方法
Spring MVC 只会对可以对应上的 set 方法给注入进去。
④ 表单对应的实体类包含级联属性
[1]创建对应的测试用例:
School:
1 2 3 4 5 6 7 8
| package com.atguigu.mvc.entity;
public class School {
private String schoolName;
……
|
Subject:
1 2 3 4 5 6 7
| package com.atguigu.mvc.entity;
public class Subject {
private String subjectName;
……
|
Teacher:
1 2 3 4 5 6 7 8
| package com.atguigu.mvc.entity;
public class Teacher {
private String teacherName;
……
|
建立 Student
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| ……public class Student {
private String stuName; private School school; private List<Subject> subjectList; private Subject[] subjectArray; private Set<Teacher> tearcherSet; private Map<String, Double> scores;
public Student() { tearcherSet = new HashSet<>(); tearcherSet.add(new Teacher()); tearcherSet.add(new Teacher()); tearcherSet.add(new Teacher()); tearcherSet.add(new Teacher()); tearcherSet.add(new Teacher()); }
……
|
[2]测试的 index.jsp:
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 31 32 33 34 35 36
| <form action="${pageContext.request.contextPath}/param/nested" method="post"> 学生姓名:<input type="text" name="stuName" /><br /> 学校名称:<input type="text" name="school.schoolName" /><br /> List类型学科名称[0]:<input type="text" name="subjectList[0].subjectName" /><br /> List类型学科名称[1]:<input type="text" name="subjectList[1].subjectName" /><br /> List类型学科名称[2]:<input type="text" name="subjectList[2].subjectName" /><br /> 数组类型学科名称[0]:<input type="text" name="subjectArray[0].subjectName" /><br /> 数组类型学科名称[1]:<input type="text" name="subjectArray[1].subjectName" /><br /> 数组类型学科名称[2]:<input type="text" name="subjectArray[2].subjectName" /><br /> tearcherSet[0]:<input type="text" name="tearcherSet[0].teacherName" /><br /> tearcherSet[1]:<input type="text" name="tearcherSet[1].teacherName" /><br /> tearcherSet[2]:<input type="text" name="tearcherSet[2].teacherName" /><br /> 语文成绩:<input type="text" name="scores['chinese']" /><br /> 英语成绩:<input type="text" name="scores['english']" /><br /> 数学成绩:<input type="text" name="scores['math']" /><br />
<button type="submit">保存</button> </form>
|
[3]测试类:ParamHandler
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 31 32 33
| @RequestMapping("/param/nested") public String paramNested(Student student) {
String stuName = student.getStuName(); System.out.println("stuName = " + stuName);
String schoolName = student.getSchool().getSchoolName(); System.out.println("schoolName = " + schoolName);
List<Subject> subjectList = student.getSubjectList(); for (Subject subject : subjectList) { System.out.println("subject = " + subject.getSubjectName()); }
Subject[] subjectArray = student.getSubjectArray(); for (Subject subject : subjectArray) { System.out.println("subject = " + subject.getSubjectName()); }
Set<Teacher> tearcherSet = student.getTearcherSet(); for (Teacher teacher : tearcherSet) { System.out.println("teacher = " + teacher.getTeacherName()); }
Map<String, Double> scores = student.getScores(); Set<String> keySet = scores.keySet(); for (String key : keySet) {
Double value = scores.get(key); System.out.println(key + " = " + value); } return "target"; }
|