⭐表示重要。
第一章:获取原生 Servlet API 对象(⭐)
1.1 原生 Servlet API
- HttpServletRequest。
- HttpServletResponse。
- HttpSession。
ServletContext。
原生:最原始的、本真的,没有经过任何的加工、包装和处理。
- API:直接翻译过来是应用程序接口的意思。对我们来说,提到 API 这个词的时候,通常指的是在某个特定的领域,已经封装好可以直接使用的一套技术体系。很多时候,特定领域的技术规范都是对外暴露一组接口作为这个领域的技术标准,然后又在这个标准下有具体实现。
1.2 可以直接拿到的对象
- HttpServletRequest。
- HttpServletResponse。
HttpSession。
示例:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<!-- 提交数据的表单 -->
<a th:href="@{/original/api/direct}">可以直接得到的三个对象</a><br/>
</body>
</html>
package com.github.fairy.era.mvc.handler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-11 14:58
*/
@Controller
public class DemoHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@GetMapping("/original/api/direct")
public String getOriginalAPIDirect(
// 有需要使用的 Servlet API 直接在形参位置声明即可。
// 需要使用就写上,不用就不写,开发体验很好。
HttpServletRequest request,
HttpSession session,
HttpServletResponse response) {
logger.info("DemoHandler.getOriginalAPIDirect的HttpServletRequest对象:{}", request);
logger.info("DemoHandler.getOriginalAPIDirect的HttpSession对象:{}", session);
logger.info("DemoHandler.getOriginalAPIDirect的HttpServletResponse对象:{}", response);
return "target";
}
}
注意:ServletContext 对象没法通过形参声明的方式直接获取,如果非要在形参位置声明 ServletContext 类型的变量,那么会抛出下面的异常: java.lang.IllegalStateException: No primary or single public constructor found for interface javax.servlet.ServletContext - and no default constructor found either
1.3 获取 ServletContext
1.3.1 通过 HttpSession 或 HttpServletRequest 获取
- 示例:
package com.github.fairy.era.mvc.handler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-11 14:58
*/
@Controller
public class DemoHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@GetMapping("/original/api/direct")
public String getOriginalAPIDirect(
// 有需要使用的 Servlet API 直接在形参位置声明即可。
// 需要使用就写上,不用就不写,开发体验很好。
HttpServletRequest request,
HttpSession session,
HttpServletResponse response) {
logger.info("DemoHandler.getOriginalAPIDirect的HttpServletRequest对象:{}", request);
logger.info("DemoHandler.getOriginalAPIDirect的HttpSession对象:{}", session);
logger.info("DemoHandler.getOriginalAPIDirect的HttpServletResponse对象:{}", response);
// 通过HttpServletRequest获取ServletContext对象
ServletContext servletContext = request.getServletContext();
// 通过HttpSession获取ServletContext对象
ServletContext servletContext1 = session.getServletContext();
logger.info("DemoHandler.getOriginalAPIDirect的servletContext对象:{}", servletContext);
logger.info("DemoHandler.getOriginalAPIDirect的servletContext1对象:{}", servletContext1);
return "target";
}
}
1.3.2 通过 IOC 容器注入
- 示例:
package com.github.fairy.era.mvc.handler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-11 14:58
*/
@Controller
public class DemoHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
// 从 IOC 容器中直接注入
@Autowired
private ServletContext servletContext;
@GetMapping("/original/api/direct")
public String getOriginalAPIDirect(
// 有需要使用的 Servlet API 直接在形参位置声明即可。
// 需要使用就写上,不用就不写,开发体验很好。
HttpServletRequest request,
HttpSession session,
HttpServletResponse response) {
logger.info("DemoHandler.getOriginalAPIDirect的HttpServletRequest对象:{}", request);
logger.info("DemoHandler.getOriginalAPIDirect的HttpSession对象:{}", session);
logger.info("DemoHandler.getOriginalAPIDirect的HttpServletResponse对象:{}", response);
logger.info("DemoHandler.getOriginalAPIDirect的servletContext对象:{}", servletContext);
return "target";
}
}
1.4 原生 Servlet 对象和 IOC 容器的关系
第二章:属性域(⭐)
2.1 在整个应用中属性域的重要作用
2.2 请求域的操作方式
2.2.1 使用 Model 类型的形参
- 示例:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<a th:href="@{/attr/request/model}">测试通过model操作请求域</a><br/>
</body>
</html>
package com.github.fairy.era.mvc.handler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-11 14:58
*/
@Controller
public class DemoHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@GetMapping("/attr/request/model")
public String attrRequestModel(
// 在形参位置声明Model类型变量,用于存储模型数据
Model model) {
// 我们将数据存入模型,SpringMVC 会帮我们把模型数据存入请求域
// 存入请求域这个动作也被称为暴露到请求域
model.addAttribute("demo", "我是通过model类型传递模型数据的");
logger.info("model:{}", model);
return "target";
}
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>目标页面</title>
</head>
<body>
<h1>目标页面</h1>
<p th:text="${demo}"></p>
<a th:href="@{/}">回首页</a>
</body>
</html>
2.2.2 使用 ModelMap 类型的形参
- 示例:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<a th:href="@{/attr/request/modelMap}">测试通过modelMap操作请求域</a><br/>
</body>
</html>
package com.github.fairy.era.mvc.handler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-11 14:58
*/
@Controller
public class DemoHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@GetMapping("/attr/request/modelMap")
public String attrRequestModelMap(
// 在形参位置声明ModelMap类型变量,用于存储模型数据
ModelMap model) {
// 我们将数据存入模型,SpringMVC 会帮我们把模型数据存入请求域
// 存入请求域这个动作也被称为暴露到请求域
model.addAttribute("demo", "我是通过ModelMap类型传递模型数据的");
logger.info("model:{}", model);
return "target";
}
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>目标页面</title>
</head>
<body>
<h1>目标页面</h1>
<p th:text="${demo}"></p>
<a th:href="@{/}">回首页</a>
</body>
</html>
2.2.3 使用 Map 类型的形参
- 示例:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<a th:href="@{/attr/request/map}">测试通过map操作请求域</a><br/>
</body>
</html>
package com.github.fairy.era.mvc.handler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.Map;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-11 14:58
*/
@Controller
public class DemoHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@GetMapping("/attr/request/map")
public String attrRequestMap(
// 在形参位置声明Map类型变量,用于存储模型数据
Map<String, Object> model) {
// 我们将数据存入模型,SpringMVC 会帮我们把模型数据存入请求域
// 存入请求域这个动作也被称为暴露到请求域
model.put("demo", "我是通过Map类型传递模型数据的");
logger.info("model:{}", model);
return "target";
}
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>目标页面</title>
</head>
<body>
<h1>目标页面</h1>
<p th:text="${demo}"></p>
<a th:href="@{/}">回首页</a>
</body>
</html>
2.2.4 使用原生 request 对象
- 示例:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<a th:href="@{/attr/request/request}">测试通过request操作请求域</a><br/>
</body>
</html>
package com.github.fairy.era.mvc.handler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import javax.servlet.http.HttpServletRequest;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-11 14:58
*/
@Controller
public class DemoHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@GetMapping("/attr/request/request")
public String attrRequestRequest(
// 在形参位置声明HttpServletRequest类型变量,用于存储模型数据
HttpServletRequest request) {
// 我们将数据存入模型,SpringMVC 会帮我们把模型数据存入请求域
// 存入请求域这个动作也被称为暴露到请求域
request.setAttribute("demo", "我是通过HttpServletRequest类型传递模型数据的");
logger.info("model:{}", request);
return "target";
}
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>目标页面</title>
</head>
<body>
<h1>目标页面</h1>
<p th:text="${demo}"></p>
<a th:href="@{/}">回首页</a>
</body>
</html>
2.2.5 通过 ModelView 对象
- 示例:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<a th:href="@{/attr/request/modelview}">测试通过modelview操作请求域</a><br/>
</body>
</html>
package com.github.fairy.era.mvc.handler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-11 14:58
*/
@Controller
public class DemoHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@GetMapping("/attr/request/modelview")
public ModelAndView attrRequestRequest() {
// 1.创建ModelAndView对象
ModelAndView modelAndView = new ModelAndView();
// 2.存入模型数据
modelAndView.addObject("demo", "我是通过ModelAndView类型传递模型数据的");
// 3.设置视图名称
modelAndView.setViewName("target");
logger.info("model:{}", modelAndView);
return modelAndView;
}
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>目标页面</title>
</head>
<body>
<h1>目标页面</h1>
<p th:text="${demo}"></p>
<a th:href="@{/}">回首页</a>
</body>
</html>
2.3 模型的本质
- SpringMVC 传入的 Model 、ModelMap 、Map 类型的参数本质上都是 BindingAwareModelMap 类型的。
2.4 框架底层将模型存入请求域(了解)
2.4.1 最终找到源码位置
- 所在类:
org.thymeleaf.context.WebEngineContext
的内部类RequestAttributesVariablesMap
。 - 所在方法:
setVariable(String name, Object value)
。
2.4.2 过程中值得关注的点
2.5 会话域
- 使用会话域最简单直接的办法就是使用原生的 HttpSession 对象。
- 示例:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<a th:href="@{/attr/session}">会话域</a><br/>
</body>
</html>
package com.github.fairy.era.mvc.handler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import javax.servlet.http.HttpSession;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-11 14:58
*/
@Controller
public class DemoHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@GetMapping("/attr/session")
public String attrSession(HttpSession session) {
session.setAttribute("demo", "会话域");
logger.info("session:{}", session);
return "target";
}
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>目标页面</title>
</head>
<body>
<h1>目标页面</h1>
<p th:text="${session.demo}"></p>
<a th:href="@{/}">回首页</a>
</body>
</html>
2.6 应用域
使用应用域最简单直接的办法就是使用原生的 ServletContext 对象。
示例:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<a th:href="@{/attr/application}">应用域</a><br/>
</body>
</html>
package com.github.fairy.era.mvc.handler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import javax.servlet.ServletContext;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-11 14:58
*/
@Controller
public class DemoHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private ServletContext servletContext;
@GetMapping("/attr/application")
public String attrApplication() {
servletContext.setAttribute("demo", "应用域");
logger.info("servletContext:{}", servletContext);
return "target";
}
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>目标页面</title>
</head>
<body>
<h1>目标页面</h1>
<p th:text="${application.demo}"></p>
<a th:href="@{/}">回首页</a>
</body>
</html>
第三章:静态资源访问(⭐)
3.1 静态资源的概念
- 资源本身已经是可以直接拿到浏览器上使用的程度了,不需要在服务器端做任何运算、处理。典型的静态资源包括:
- ① 纯 HTML文件
- ② 图片
- ③ CSS 文件
- ④ JavaScript 文件
- ⑤ ……
3.2 SpringMVC 环境下静态资源问题
3.2.1 配置斜杠问题
- ① 情景描述:DispatcherServlet 的 url-pattern标签配置的是
/
,意味着整个 web 应用范围内的所有请求都将由 SpringMVC 来处理。
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
- ② 情景重现:在 web 应用中加入图片资源。
- ③ 构建项目:
mvn clean install
- ④ 访问静态资源:
- ⑤ 原因分析:
- ① DispatcherServlet 的 url-pattern 配置的是
/
,表示整个 Web 应用范围内所有请求都由 SpringMVC 来处理。 - ② 对 SpringMVC 来说,必须有对应的 @RequestMapping 才能找到处理请求的方法。
- ③ 现在的 images/girl.jpg 请求没有对应的 @RequestMapping ,所以返回 404。
- ① DispatcherServlet 的 url-pattern 配置的是
- ⑥ 解决办法:在 SpringMVC 的配置文件中增加如下的配置
<!-- 加入这个配置,SpringMVC 就会在遇到没有 @RequestMapping 的请求时放它过去 -->
<!-- 所谓放它过去就是让这个请求去找它原本要访问的资源 -->
<mvc:default-servlet-handler/>
- ⑦ 再次测试访问图片:
- ⑧ 新的问题:其它原本正常的请求访问不了。
- ⑨ 解决办法:在 SpringMVC 的配置文件中增加如下的配置
<!-- 开启 SpringMVC 的注解驱动功能。这个配置也被称为 SpringMVC 的标配。 -->
<!-- 标配:因为 SpringMVC 环境下非常多的功能都要求必须打开注解驱动才能正常工作。 -->
<mvc:annotation-driven/>
3.2.2 扩展名情况(了解)
- ① 情景描述:DispatcherServlet 的 url-pattern标签配置的是
*.html
,意味着整个 web 应用范围内的所有请求都将由 SpringMVC 来处理。
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
- ② 效果:
- 图片可以直接访问,因为请求的扩展名不是html,不会受到 SpringMVC 的影响。
- 其它的请求:需要做到下面的操作才可以正常访问
- 需要在超链接地址后面附加
.html
扩展名。 - 在
@RequestMapping
注解指定的 URL 地址中也附加.html
扩展名
- 需要在超链接地址后面附加
注意:
- 在请求扩展名就是 html 的情况下,访问 HTML 静态页面。仍然需要开启
<mvc:default-servlet-handler/>
配置。- 原因是:扩展名为 html 会让 SpringMVC 觉得这个请求归它管,它一看没有
@RequestMapping
就会返回 404。而打开了<mvc:default-servlet-handler/>
配置就会转发到 HTML 页面。
3.3 <mvc:default-servlet-handler/>
底层(了解)
- 所在类:org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler
- 关键方法:handleRequest() 方法。
- 原理:SpringMVC 首先查找是否存在和当前请求对应的 @RequestMapping;如果没有,则调用 handleRequest() 方法转发到目标资源。
package org.springframework.web.servlet.resource;
public class DefaultServletHttpRequestHandler implements HttpRequestHandler, ServletContextAware {
...
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Assert.state(this.servletContext != null, "No ServletContext set");
RequestDispatcher rd = this.servletContext.getNamedDispatcher(this.defaultServletName);
if (rd == null) {
throw new IllegalStateException("A RequestDispatcher could not be located for the default servlet '" +
this.defaultServletName + "'");
}
rd.forward(request, response);
}
}
第四章:<mvc:view-controller/>
(⭐)
4.1 需求场景
- 在一个 handler 方法中,仅仅只是完成
@RequestMapping
映射,将请求转发到目标视图,除此之外没有任何其他代码,此时可以使用 SpringMVC 配置文件中的配置代替这样的 handler 方法。
4.2 具体操作
4.1.2 配置
- 在 SpringMVC 配置文件中使用
<mvc:view-controller/>
配置:
<mvc:view-controller path="/test" view-name="target"/>
- 同时,在 handler 类中去掉被代替的方法。
4.1.3 新的问题
- 加入
<mvc:view-controller/>
配置后,其它正常的@RequestMapping
将失效,此时还需要加入<mvc:annotation-driven/>
来解决。
4.3 三个配置影响访问效果探索(了解)
4.3.1 三个配置
<mvc:annotation-driven/>
<mvc:default-servlet-handler/>
<mvc:view-controller path="/test" view-name="target"/>
4.3.2 相关组件 HandlerMapping
- 见名知意,HandlerMapping 封装的数据包含了请求地址和 handler 方法之间的映射关系。所以请求访问是否能生效关键要看 HandlerMapping 在 IOC 容器中加载的情况。为了看到这一点,我们可以在 DispatcherServlet 中找到 doDispatch() 方法设置断点。之所以选择这个方法,是因为每一个由 SpringMVC 处理的请求都会经过这里,便于操作。
4.3.3 三个标签都没有配置
- 此时,我们看到 SpringMVC 加载了三个 HandlerMapping,其中 RequestMappingHandlerMapping 封装了
@RequestMapping
相关请求,有它在@RequestMapping
相关请求就能访问到。
4.3.4 增加一个标签
- 配置了
<mvc:view-controller/>
或<mvc:default-servlet-handler/>
之后。
- 此时,我们看到 SpringMVC 加载了两个 HandlerMapping。
4.3.5 配置三个标签
- 此时,我们看到 SpringMVC 加载了略有不同的三个 HandlerMapping。
4.3.6 总结
- 在配置不同的情况下,SpringMVC 底层加载的组件不同,特定功能需要特定组件的支持。当特定功能所需组件没有加入到 IOC 容器中的时候,对应的功能就无法使用了。
<mvc:annotation-driven/>
是 SpringMVC标配
,必加。