获取原生Servlet API对象
1、原生 Servlet API
- HttpServletRequest
- HttpServletResponse
- HttpSession
- ServletContext
原生:最原始的、本真的,没有经过任何的加工、包装和处理。
API:直接翻译过来是应用程序接口的意思。对我们来说,提到 API 这个词的时候,通常指的是在某个特定的领域,已经封装好可以直接使用的一套技术体系。很多时候,特定领域的技术规范都是对外暴露一组接口作为这个领域的技术标准,然后又在这个标准下有具体实现。
有需要使用的 Servlet API 直接在形参位置声明即可。
ServletContext对象没法通过形参声明的方式直接获取,如果非要在形参位置声明ServletContext类型的变量,那么会抛出下面的异常:
java.lang.IllegalStateException: No primary or single public constructor found for interface javax.servlet.ServletContext - and no default constructor found either
3、获取ServletContext
①方法一:通过HttpSession获取
@RequestMapping("/original/servlet/context/first/way")
public String originalServletContextFirstWay(HttpSession session) {
// 获取ServletContext对象的方法一:通过HttpSession对象获取
ServletContext servletContext = session.getServletContext();
logger.debug(servletContext.toString());
return "target";
}
②方法二:通过 IOC 容器注入
// 获取ServletContext对象的方法二:从 IOC 容器中直接注入
@Autowired
private ServletContext servletContext;
@RequestMapping("/original/servlet/context/second/way")
public String originalServletContextSecondWay() {
logger.debug(this.servletContext.toString());
return "target";
}
4、原生对象和 IOC 容器关系
第二节 属性域
2、请求域操作方式
①使用 Model 类型的形参
@RequestMapping("/attr/request/model")
public String testAttrRequestModel(
// 在形参位置声明Model类型变量,用于存储模型数据
Model model) {
// 我们将数据存入模型,SpringMVC 会帮我们把模型数据存入请求域
// 存入请求域这个动作也被称为暴露到请求域
model.addAttribute("requestScopeMessageModel","i am very happy[model]");
return "target";
}
注意:
我们将数据存入模型,SpringMVC 会帮我们把模型数据存入请求域
②使用 ModelMap 类型的形参
@RequestMapping("/attr/request/model/map")
public String testAttrRequestModelMap(
// 在形参位置声明ModelMap类型变量,用于存储模型数据
ModelMap modelMap) {
// 我们将数据存入模型,SpringMVC 会帮我们把模型数据存入请求域
// 存入请求域这个动作也被称为暴露到请求域
modelMap.addAttribute("requestScopeMessageModelMap","i am very happy[model map]");
return "target";
}
注意:
我们将数据存入模型,SpringMVC 会帮我们把模型数据存入请求域
③使用 Map 类型的形参
@RequestMapping("/attr/request/map")
public String testAttrRequestMap(
// 在形参位置声明Map类型变量,用于存储模型数据
Map<String, Object> map) {
// 我们将数据存入模型,SpringMVC 会帮我们把模型数据存入请求域
// 存入请求域这个动作也被称为暴露到请求域
map.put("requestScopeMessageMap", "i am very happy[map]");
return "target";
}
④使用原生 request 对象
@RequestMapping("/attr/request/original")
public String testAttrOriginalRequest(
// 拿到原生对象,就可以调用原生方法执行各种操作
HttpServletRequest request) {
request.setAttribute("requestScopeMessageOriginal", "i am very happy[original]");
return "target";
}
⑤使用 ModelAndView 对象
@RequestMapping("/attr/request/mav")
public ModelAndView testAttrByModelAndView() {
// 1.创建ModelAndView对象
ModelAndView modelAndView = new ModelAndView();
// 2.存入模型数据
modelAndView.addObject("requestScopeMessageMAV", "i am very happy[mav]");
// 3.设置视图名称
modelAndView.setViewName("target");
return modelAndView;
}
3、模型的本质
①BindingAwareModelMap
SpringMVC 传入的 Model、ModelMap、Map类型的参数其实本质上都是 BindingAwareModelMap 类型的。
②它们之间的关系
4、框架底层将模型存入请求域
①最终找到的源码位置
所在类:org.thymeleaf.context.WebEngineContext
所在方法:setVariable()
②过程中值得关注的点
5、会话域
使用会话域最简单直接的办法就是使用原生的 HttpSession 对象
@RequestMapping("/attr/session")
public String attrSession(
// 使用会话域最简单直接的办法就是使用原生的 HttpSession 对象
HttpSession session) {
session.setAttribute("sessionScopeMessage", "i am haha ...");
return "target";
}
6、应用域
应用域同样是使用原生对象来操作:
@Autowired
private ServletContext servletContext;
@RequestMapping("/attr/application")
public String attrApplication() {
servletContext.setAttribute("appScopeMsg", "i am hungry...");
return "target";
}
第三节 静态资源访问
1、静态资源的概念
资源本身已经是可以直接拿到浏览器上使用的程度了,不需要在服务器端做任何运算、处理。典型的静态资源包括:
- 纯HTML文件
- 图片
- CSS文件
- JavaScript文件
- ……
2、SpringMVC 环境下静态资源问题
①情况一:斜杠情况
[1]情景描述
DispatcherServlet 的 url-pattern 标签配置的是“/”。意味着整个 Web 应用范围内所有请求都由 SpringMVC 来处理。
[2]情景重现
在 Web 应用中加入图片资源:
部署目录下不会自动加入,需要我们手动重新构建才行:
重新构建应用参考下面的操作步骤:
[3]访问静态资源
[5]解决办法
- 在 SpringMVC 配置文件中增加配置:
<!-- 加入这个配置,SpringMVC 就会在遇到没有 @RequestMapping 的请求时放它过去 --> <!-- 所谓放它过去就是让这个请求去找它原本要访问的资源 --> <mvc:default-servlet-handler/>
- 再次测试访问图片:
- 新的问题:其他原本正常的请求访问不了了
- 进一步解决问题:再增加一个配置
-
<!-- 开启 SpringMVC 的注解驱动功能。这个配置也被称为 SpringMVC 的标配。 --> <!-- 标配:因为 SpringMVC 环境下非常多的功能都要求必须打开注解驱动才能正常工作。 --> <mvc:annotation-driven/>
[6]default-servlet-handler底层[了解]
所在类:org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler
关键方法:handleRequest()方法
大体机制:SpringMVC 首先查找是否存在和当前请求对应的 @RequestMapping;如果没有,则调用handleRequest()方法转发到目标资源。
@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);
}
[7]前面两个配置对请求访问的影响[了解]
我们在前面的操作中发现,使用了 mvc:default-servlet-handler 后必须使用 mvc:annotation-driven。那么这是为什么呢?关键原因是他们加载使用的 HandlerMapping 不同。
- 没有 default-servlet-handler 和 annotation-driven 时,SpringMVC 加载的 HandlerMapping 是:
org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
DefaultAnnotationHandlerMapping 负责把所有 handler 类中的 handler 方法收集起来。
- 加入 default-servlet-handler 时,SpringMVC 加载的 HandlerMapping 是:
org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping org.springframework.web.servlet.handler.SimpleUrlHandlerMapping
很明显,DefaultAnnotationHandlerMapping 没了,而 SimpleUrlHandlerMapping 只能映射静态资源。所以我们通过 @RequestMapping 映射的 handler 方法无效了。
- 再加入 annotation-driven 时,SpringMVC 加载的 HandlerMapping 是:
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping org.springframework.web.servlet.handler.SimpleUrlHandlerMapping
加入了 mvc:annotation-driven 后最关键的是增加了 RequestMappingHandlerMapping,从而可以映射我们的handler 方法。
结论:在配置不同的情况下,SpringMVC 底层加载的组件不同,特定功能需要特定组件的支持。当特定功能所需组件没有加入到 IOC 容器中的时候,对应的功能就无法使用了。
②情况二:扩展名情况
[1]修改 url-pattern
<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:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<!--<url-pattern>/</url-pattern>-->
<!-- 以扩展名方式匹配 SpringMVC 要处理的请求 -->
<!-- 此时要求请求扩展名必须是 html,SpringMVC 才会处理这个请求 -->
<url-pattern>*.html</url-pattern>
</servlet-mapping>
[2]效果
-
图片直接就可以访问了。因为请求扩展名不是 html,不会受到 SpringMVC 影响。
-
其他请求:做下面两个操作才可以正常访问
- 需要在超链接地址后面附加 html 扩展名
- 在 @RequestMapping 注解指定的 URL 地址中也附加 html 扩展名
第五节 表单标签
主要的目的是在页面上实现表单回显。最典型的情况是在修改数据时,把之前旧的数据重新显示出来供用户参考。
1、回显简单标签
一个标签回显一个值的情况。
①创建用于测试的实体类
public class Tiger {
private Integer tigerId;
private String tigerName;
private Double tigerSalary;
……
②创建 handler 方法
@RequestMapping("/form/redisplay/simple")
public String simpleTagRedisplay(Model model) {
// 1.准备好用来回显表单的实体类对象
// 在实际功能中,这里的对象应该是从数据库查询得到
Tiger tiger = new Tiger();
tiger.setTigerId(5);
tiger.setTigerName("tomCat");
tiger.setTigerSalary(666.66);
// 2.将实体类数据存入模型
model.addAttribute("tiger", tiger);
return "form-simple";
}
③页面表单回显
<h3>回显Tiger数据</h3>
<form th:action="@{/save/tiger}" method="post">
<!-- th:value 和 th:field 属性都可以 -->
老虎的id:<input type="text" name="tigerId" th:value="${tiger.tigerId}" /><br/>
老虎的名字:<input type="text" name="tigerName" th:field="${tiger.tigerName}" /><br/>
老虎的工资:<input type="text" name="tigerSalary" th:field="${tiger.tigerSalary}" /><br/>
<button type="submit">保证</button>
</form>
2、回显带选择功能的标签
①总体思路
- 显示标签本身,需要用到一个集合对象来存储标签本身所需要的数据
- 对标签执行回显操作,需要用到另外的一个实体类
②创建实体类
[1]用来显示标签的实体类
public class Season {
// 提交给服务器的值
private String submitValue;
// 给用户看的值
private String showForUserValue;
……
[2]用来回显数据的实体类
public class Paige {
private Integer paigeId;
private String paigeName;
private Season season;
……
③handler 方法
@RequestMapping("/form/redisplay/choose")
public String chooseTagRedisplay(Model model) {
// 1.准备用来显示标签的数据
List<Season> seasonList = new ArrayList<>();
seasonList.add(new Season("spring", "春天"));
seasonList.add(new Season("summer", "夏天"));
seasonList.add(new Season("autumn", "秋天"));
seasonList.add(new Season("winter", "冬天"));
model.addAttribute("seasonList", seasonList);
// 2.准备用来回显表单的实体类数据
Paige paige = new Paige();
paige.setPaigeId(6);
paige.setPaigeName("pig");
paige.setSeason(new Season("summer", "夏天"));
model.addAttribute("paige", paige);
return "form-choose";
}
③页面表单回显
[1]单选按钮
<!-- th:each属性:指定用来生成这一组标签的集合数据 -->
<!-- th:value属性:获取数据用来设置HTML标签的value属性,成为将来提交给服务器的值 -->
<!-- th:text属性:获取数据用来设置HTML标签旁边给用户看的名字 -->
<!-- th:checked属性:判断是否回显(把适合的标签设置为默认被选中) -->
<input type="radio" name="season.submitValue"
th:each="season : ${seasonList}"
th:value="${season.submitValue}"
th:text="${season.showForUserValue}"
th:checked="${season.submitValue == paige.season.submitValue}"
/>
[2]下拉列表
<select name="season.submitValue">
<option th:each="season : ${seasonList}"
th:value="${season.submitValue}"
th:text="${season.showForUserValue}"
th:selected="${season.submitValue == paige.season.submitValue}"/>
</select>
[3]多选框
- 另外封装一个实体类
-
public class John { private List<Season> seasonList; public List<Season> getSeasonList() { return seasonList; } public void setSeasonList(List<Season> seasonList) { this.seasonList = seasonList; } }
handler方法
-
@RequestMapping("/form/redisplay/choose/multi") public String chooseMulti(Model model) { // 1.准备用来显示标签的数据 List<Season> seasonList = new ArrayList<>(); seasonList.add(new Season("spring", "春天")); seasonList.add(new Season("summer", "夏天")); seasonList.add(new Season("autumn", "秋天")); seasonList.add(new Season("winter", "冬天")); model.addAttribute("seasonList", seasonList); // 2.准备用来回显表单的实体类数据 John john = new John(); List<Season> seasonListForRedisplay = new ArrayList<>(); seasonListForRedisplay.add(new Season("summer", "夏天")); seasonListForRedisplay.add(new Season("winter", "冬天")); model.addAttribute("seasonListForRedisplay", seasonListForRedisplay); return "form-multi"; }
页面标签
-
<!-- seasonListForRedisplay.contains(season) 用包含回显数据的集合调用contains()方法判断是否应该被选中; 传入contains()方法的是生成具体每一个标签时遍历得到的对象 --> <input type="checkbox" name="xxx" th:each="season : ${seasonList}" th:value="${season.submitValue}" th:text="${season.showForUserValue}" th:checked="${seasonListForRedisplay.contains(season)}" />