第8章 文件上传与下载
8.1 文件上传
使用SpringMVC6版本,不需要添加以下依赖,Spring5以及之前版本需要:
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.5</version>
</dependency>
前端页面:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>文件上传</title>
</head>
<body>
<!--文件上传表单-->
<form th:action="@{/file/up}" method="post" enctype="multipart/form-data">
文件:<input type="file" name="fileName"/><br>
<input type="submit" value="上传">
</form>
</body>
</html>
重点是:form表单采用post请求,enctype是multipart/form-data,并且上传组件是:type=“file”
web.xml文件:
<!--前端控制器-->
<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>
<multipart-config>
<!--设置单个支持最大文件的大小-->
<max-file-size>102400</max-file-size>
<!--设置整个表单所有文件上传的最大值-->
<max-request-size>102400</max-request-size>
<!--设置最小上传文件大小-->
<file-size-threshold>0</file-size-threshold>
</multipart-config>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
重点:在DispatcherServlet配置时,添加 multipart-config 配置信息。(这是Spring6,如果是Spring5,则不是这样配置,而是在springmvc.xml文件中配置:CommonsMultipartResolver)
SpringMVC6中把这个类已经删除了。废弃了。
Controller中的代码:
package com.powernode.springmvc.controller;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.util.UUID;
@Controller
public class FileController {
@RequestMapping(value = "/file/up", method = RequestMethod.POST)
public String fileUp(@RequestParam("fileName") MultipartFile multipartFile, HttpServletRequest request) throws IOException {
// 获取请求参数的名字
String name = multipartFile.getName();
System.out.println(name); //fileName
// 获取的是文件真实的名字
String originalFilename = multipartFile.getOriginalFilename();
System.out.println(originalFilename); //touxiang.jpeg
// 一边读,一边写。
// 读客户端传过来的文件,写到服务器上。
// 获取输入流
InputStream in = multipartFile.getInputStream(); // 输入流,负责读客户端的文件
BufferedInputStream bis = new BufferedInputStream(in); // 封装成带有缓冲区的输入流
// 获取上传之后的存放目录
ServletContext application = request.getServletContext();
String realPath = application.getRealPath("/upload");
File file = new File(realPath);
// 如果服务器目录不存在则新建
if(!file.exists()){
file.mkdirs();
}
// 开始写
//BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file.getAbsolutePath() + "/" + originalFilename));
// 可以采用UUID来生成文件名,防止服务器上传文件时产生覆盖
File destFile = new File(file.getAbsolutePath() + "/" + UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf(".")));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile));
// 一边读一边写
byte[] bytes = new byte[1024 * 100];
int readCount = 0;
while((readCount = bis.read(bytes)) != -1){
bos.write(bytes,0,readCount);
}
// 刷新缓冲流
bos.flush();
// 关闭流
bis.close();
bos.close();
return "ok";
}
}
最终测试结果:
建议:上传文件时,文件起名采用UUID。以防文件覆盖。
8.2 文件下载
<!--文件下载-->
<a th:href="@{/download}">文件下载</a>
文件下载核心程序,使用ResponseEntity:
@GetMapping("/download")
public ResponseEntity<byte[]> downloadFile(HttpServletResponse response, HttpServletRequest request) throws IOException {
File file = new File(request.getServletContext().getRealPath("/upload") + "/1.jpeg");
// 创建响应头对象
HttpHeaders headers = new HttpHeaders();
// 设置响应内容类型
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
// 设置下载文件的名称
headers.setContentDispositionFormData("attachment", file.getName());
// 下载文件
ResponseEntity<byte[]> entity = new ResponseEntity<byte[]>(Files.readAllBytes(file.toPath()), headers, HttpStatus.OK);
return entity;
}
效果:
8.3 课堂笔记
34. 文件上传
文件上传必须是post请求。
文件上传的form标签中必须使用 enctype="multipart/form-data"
enctype是用来设置请求头的内容类型的。默认值是:enctype="application/x-www-form-urlencoded"
文件上传的组件是:<input type="file" name="fileName">
注意:如果你用的是spring6,那么需要在web.xml文件的DispatcherServlet中进入如下的配置:
<multipart-config>
<!--设置单个支持最大文件的大小-->
<max-file-size>102400</max-file-size>
<!--设置整个表单所有文件上传的最大值-->
<max-request-size>102400</max-request-size>
<!--设置最小上传文件大小-->
<file-size-threshold>0</file-size-threshold>
</multipart-config>
文件上传:浏览器端向服务器端发送文件,最终服务器将文件保存到服务器上。(本质上还是IO流,读文件和写文件。)
SpringMVC专门为文件上传准备了一个类:MultipartFile multipartFile.
这个类怎么理解?这个类就代表你从客户端传过来的那个文件。
multipartFile.getName(); 获取请求参数的name
multipartFile.getOriginalFilename(); 获取文件的真实名字
35. 文件下载,代码非常固定
@GetMapping("/download")
public ResponseEntity<byte[]> downloadFile(HttpServletRequest request) throws IOException {
File file = new File(request.getServletContext().getRealPath("/upload") + "/touxiang.jpeg");
// 创建响应头对象
HttpHeaders headers = new HttpHeaders();
// 设置响应内容类型
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
// 设置下载文件的名称
headers.setContentDispositionFormData("attachment", file.getName());
// 下载文件
return new ResponseEntity<byte[]>(Files.readAllBytes(file.toPath()), headers, HttpStatus.OK);
}
第9章 异常处理器
9.1 什么是异常处理器
Spring MVC在处理器方法
执行过程中出现了异常,可以采用异常处理器
进行应对。
一句话概括异常处理器作用:处理器方法执行过程中出现了异常,跳转到对应的视图,在视图上展示友好信息。
SpringMVC为异常处理提供了一个接口:HandlerExceptionResolver
核心方法是:resolveException。
该方法用来编写具体的异常处理方案。返回值ModelAndView,表示异常处理完之后跳转到哪个视图。
HandlerExceptionResolver 接口有两个常用的默认实现:
- DefaultHandlerExceptionResolver
- SimpleMappingExceptionResolver
9.2 默认的异常处理器
DefaultHandlerExceptionResolver 是默认的异常处理器。
核心方法:
当请求方式和处理方式不同时,DefaultHandlerExceptionResolver的默认处理态度是:
9.3 自定义的异常处理器
自定义异常处理器需要使用:SimpleMappingExceptionResolver
自定义异常处理机制有两种语法:
- 通过XML配置文件
- 通过注解
9.3.1 配置文件方式
<!--配置属于自己的异常处理器-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!--用来指定出现异常后,跳转的视图-->
<!--这里可以配置很多键值对,key是异常,要提供具体的异常类型,包括包名。-->
<!--以下的配置表示,只要发生异常,都跳转到tip视图-->
<prop key="java.lang.Exception">tip</prop>
</props>
</property>
<!-- 以下配置的含义是:将当前发生的异常对象存储到request域当中,value属性用来指定存储时的key。-->
<!--底层会执行这样的代码: request.setAttribute("yiChang", 异常对象)-->
<property name="exceptionAttribute" value="yiChang"/>
</bean>
在视图页面上展示异常信息:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>出错了</title>
</head>
<body>
<h1>出错了,请联系管理员!</h1>
<div th:text="${yiChang}"></div>
</body>
</html>
9.3.2 注解方式
@ControllerAdvice
public class ExceptionController {
@ExceptionHandler
public String tip(Exception e, Model model){
model.addAttribute("e", e);
return "tip";
}
}
第10章 拦截器
10.1 拦截器概述
拦截器(Interceptor)类似于过滤器(Filter)
Spring MVC的拦截器作用是在请求到达控制器之前或之后进行拦截,可以对请求和响应进行一些特定的处理。
拦截器可以用于很多场景下:
- 登录验证:对于需要登录才能访问的网址,使用拦截器可以判断用户是否已登录,如果未登录则跳转到登录页面。
- 权限校验:根据用户权限对部分网址进行访问控制,拒绝未经授权的用户访问。
- 请求日志:记录请求信息,例如请求地址、请求参数、请求时间等,用于排查问题和性能优化。
- 更改响应:可以对响应的内容进行修改,例如添加头信息、调整响应内容格式等。
拦截器和过滤器的区别在于它们的作用层面不同。
- 过滤器更注重在请求和响应的流程中进行处理,可以修改请求和响应的内容,例如设置编码和字符集、请求头、状态码等。
- 拦截器则更加侧重于对控制器进行前置或后置处理,在请求到达控制器之前或之后进行特定的操作,例如打印日志、权限验证等。
Filter、Servlet、Interceptor、Controller的执行顺序:
1 拦截器是SpringMVC里的,过滤器是JavaWeb中Servlet规范里的
2 拦截器是Interceptor,过滤器是Filter
3 过滤器过滤的范围比较大,整个请求开始到请求结束,囊括的范围、跨度比较大。在整个Servlet前和Servlet之后去执行过滤;拦截器在控制器前和后执行拦截。
4 在Servlet之前的过滤器,执行的是请求的过滤;目标执行结束后,最后是对响应进行过滤。过滤也是有顺序的:请求的时候先执行filter1,再执行filter2;响应的时候先执行filter2,再执行filter1。
5 拦截器出现在Controller(处理器/控制器)前后,任何一个拦截器中都有三个方法:preHandle(前处理)、postHandle(后处理)、afterCompletion(完成之后)。控制器方法调用之前,拦截器前处理方法preHandle()会执行;控制器方法执行结束后,拦截器后处理方法postHandle()就会执行;当整个页面渲染完毕,拦截器afterCompletion()方法自动调用
10.2 拦截器的创建与基本配置
10.2.1 定义拦截器
实现org.springframework.web.servlet.HandlerInterceptor
接口,共有三个方法可以进行选择性的实现:
- preHandle:处理器方法调用之前执行
- 只有该方法有返回值,返回值是布尔类型,true放行,false拦截。
- postHandle:处理器方法调用之后执行
- afterCompletion:渲染完成后执行
package com.powernode.springmvc.interceptors;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
@Component
public class Interceptor1 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("Interceptor1's preHandle!");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("Interceptor1's postHandle!");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("Interceptor1's afterCompletion!");
}
}
10.2.2 拦截器基本配置
在springmvc.xml文件中进行如下配置:
第一种方式:
<mvc:interceptors>
<bean class="com.powernode.springmvc.interceptors.Interceptor1"/>
</mvc:interceptors>
第二种方式:
<mvc:interceptors>
<ref bean="interceptor1"/>
</mvc:interceptors>
第二种方式的前提:
- 前提1:包扫描
- 前提2:使用 @Component 注解进行标注
注意:对于这种基本配置来说,拦截器是拦截所有请求的。
10.2.3 拦截器部分源码分析
10.2.3.1 方法执行顺序的源码分析
public class DispatcherServlet extends FrameworkServlet {
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 调用所有拦截器的 preHandle 方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 调用处理器方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 调用所有拦截器的 postHandle 方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
// 处理视图
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
// 渲染页面
render(mv, request, response);
// 调用所有拦截器的 afterCompletion 方法
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
10.2.3.2 拦截与放行的源码分析
public class DispatcherServlet extends FrameworkServlet {
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 调用所有拦截器的 preHandle 方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
// 如果 mappedHandler.applyPreHandle(processedRequest, response) 返回false,以下的return语句就会执行
return;
}
}
}
拦截器可以配置多个,所有的拦截器都是自动放到ArrayList集合当中的。
public class HandlerExecutionChain {
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for (int i = 0; i < this.interceptorList.size(); i++) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
// 如果 interceptor.preHandle(request, response, this.handler) 返回 false,以下的 return false;就会执行。
return false;
}
this.interceptorIndex = i;
}
return true;
}
}
10.3 拦截器的高级配置
采用以上基本配置方式,拦截器是拦截所有请求路径的。如果要针对某些路径进行拦截,某些路径不拦截,可以采用高级配置:
<mvc:interceptors>
<!--基本配置:第一种方式-->
<!--注意:基本配置,默认情况下是拦截所有请求的。-->
<!--<bean class="com.powernode.springmvc.interceptors.Interceptor1"/>-->
<!--基本配置:第二种方式-->
<!--<ref bean="interceptor1"/>-->
<!--高级配置,指定一些路径被拦截,一些路径不拦截。-->
<mvc:interceptor>
<!--拦截所有路径-->
<mvc:mapping path="/**"/>
<!--除 /test 路径之外-->
<mvc:exclude-mapping path="/test"/>
<!--设置拦截器-->
<ref bean="interceptor1"/>
</mvc:interceptor>
</mvc:interceptors>
以上的配置表示,除 /test 请求路径之外,剩下的路径全部拦截。
10.4 拦截器的执行顺序
10.4.1 执行顺序
10.4.1.1 如果所有拦截器preHandle都返回true
按照springmvc.xml文件中配置的顺序,自上而下调用 preHandle:
<mvc:interceptors>
<!--配置多个拦截器-->
<ref bean="interceptor1"/>
<ref bean="interceptor2"/>
</mvc:interceptors>
执行顺序:
10.4.1.2 如果其中一个拦截器preHandle返回false
<mvc:interceptors>
<ref bean="interceptor1"/>
<ref bean="interceptor2"/>
</mvc:interceptors>
如果interceptor2
的preHandle返回false,执行顺序:
规则:只要有一个拦截器preHandle
返回false,任何postHandle
都不执行。但返回false的拦截器的前面的拦截器按照逆序执行afterCompletion
。
补充说明:如果注册拦截器的顺序依次为:interceptor1,interceptor2,interceptor3。interceptor2的preHandle
返回false,其余拦截器的preHandle
返回true。则:interceptor1,interceptor2依次执行preHandle
;interceptor1执行afterCompletion
;任何postHandle
都不执行
10.4.2 源码分析
DispatcherServlet和 HandlerExecutionChain的部分源码:
public class DispatcherServlet extends FrameworkServlet {
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 按照顺序执行所有拦截器的preHandle方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 执行处理器方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 按照逆序执行所有拦截器的 postHanle 方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
// 处理视图
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
// 渲染视图
render(mv, request, response);
// 按照逆序执行所有拦截器的 afterCompletion 方法
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
public class HandlerExecutionChain {
// 顺序执行 preHandle
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for (int i = 0; i < this.interceptorList.size(); i++) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
// 如果其中一个拦截器preHandle返回false
// 将该拦截器前面的拦截器按照逆序执行所有的afterCompletion
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
return true;
}
// 逆序执行 postHanle
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
interceptor.postHandle(request, response, this.handler, mv);
}
}
// 逆序执行 afterCompletion
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}
第11章 Spring MVC执行流程
Tomcat服务器负责创建DispatcherServlet对象。并且Tomcat服务器自动调用这个Servlet的init方法。init方法只调用一次。在服务器启动的时候,初始化Servlet的时候只调用一次
11.1 从源码角度看执行流程
以下是核心代码:
public class DispatcherServlet extends FrameworkServlet {
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 根据请求对象request获取
// 这个对象是在每次发送请求时都创建一个,是请求级别的
// 该对象中描述了本次请求应该执行的拦截器是哪些,顺序是怎样的,要执行的处理器是哪个
HandlerExecutionChain mappedHandler = getHandler(processedRequest);
// 根据处理器获取处理器适配器。(底层使用了适配器模式)
// HandlerAdapter在web服务器启动的时候就创建好了。(启动时创建多个HandlerAdapter放在List集合中)
// HandlerAdapter有多种类型:
// RequestMappingHandlerAdapter:用于适配使用注解 @RequestMapping 标记的控制器方法
// SimpleControllerHandlerAdapter:用于适配实现了 Controller 接口的控制器
// 注意:此时还没有进行数据绑定(也就是说,表单提交的数据,此时还没有转换为pojo对象。)
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 执行请求对应的所有拦截器中的 preHandle 方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 通过处理器适配器调用处理器方法
// 在调用处理器方法之前会进行数据绑定,将表单提交的数据绑定到处理器方法上。(底层是通过WebDataBinder完成的)
// 在数据绑定的过程中会使用到消息转换器:HttpMessageConverter
// 结束后返回ModelAndView对象
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 执行请求对应的所有拦截器中的 postHandle 方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
// 处理分发结果(在这个方法中完成了响应)
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
// 根据每一次的请求对象来获取处理器执行链对象
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
// HandlerMapping在服务器启动的时候就创建好了,放到了List集合中。HandlerMapping也有多种类型
// RequestMappingHandlerMapping:将 URL 映射到使用注解 @RequestMapping 标记的控制器方法的处理器。
// SimpleUrlHandlerMapping:将 URL 映射到处理器中指定的 URL 或 URL 模式的处理器。
for (HandlerMapping mapping : this.handlerMappings) {
// 重点:这是一次请求的开始,实际上是通过处理器映射器来获取的处理器执行链对象
// 底层实际上会通过 HandlerMapping 对象获取 HandlerMethod对象,将HandlerMethod 对象传递给 HandlerExecutionChain对象。
// 注意:HandlerMapping对象和HandlerMethod对象都是在服务器启动阶段创建的。
// RequestMappingHandlerMapping对象中有多个HandlerMethod对象。
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
// 渲染
render(mv, request, response);
// 渲染完毕后,调用该请求对应的所有拦截器的 afterCompletion方法。
mappedHandler.triggerAfterCompletion(request, response, null);
}
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 通过视图解析器返回视图对象
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
// 真正的渲染视图
view.render(mv.getModelInternal(), request, response);
}
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
Locale locale, HttpServletRequest request) throws Exception {
// 通过视图解析器返回视图对象
View view = viewResolver.resolveViewName(viewName, locale);
}
}
public interface ViewResolver {
View resolveViewName(String viewName, Locale locale) throws Exception;
}
public interface View {
void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception;
}
伪代码:
0从源码角度分析SpringMVC执行流程
// 前端控制器,SpringMVC最核心的类
public class DispatcherServlet extends FrameworkServlet {
// 前端控制器最核心的方法,这个方法是负责处理请求的,一次请求,调用一次 doDispatch 方法。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 通过请求获取处理器
// 请求:http://localhost:8080/springmvc/hello (有URI)
// 根据请求路径来获取对应的要执行的处理器
// 实际上返回的是一个处理器执行链对象
// 这个执行链(链条)把谁串起来了呢?把这一次请求要执行的所有拦截器和处理器串起来了。
// HandlerExecutionChain是一次请求对应一个对象
HandlerExecutionChain mappedHandler = getHandler(request);
// 根据处理器获取处理器适配器对象
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Handler就是我们写的Controller
// 执行该请求对应的所有拦截器中的 preHandle 方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 调用处理器方法,返回ModelAndView对象
// 在这里进行的数据绑定,实际上调用处理器方法之前要给处理器方法传参
// 需要传参的话,这个参数实际上是要经过一个复杂的数据绑定过程(将前端提交的表单数据转换成POJO对象)
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 执行该请求对应的所有拦截器中的 postHandle 方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
// 处理分发结果(本质上就是响应结果到浏览器)
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
// 渲染
render(mv, request, response);
// 执行该请求所对应的所有拦截器的afterCompletion方法
mappedHandler.triggerAfterCompletion(request, response, null);
}
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 通过视图解析器进行解析,返回视图View对象
View view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
// 调用视图对象的渲染方法(完成响应)
view.render(mv.getModelInternal(), request, response);
}
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
Locale locale, HttpServletRequest request) throws Exception {
// 视图解析器
ViewResolver viewResolver;
// 通过视图解析器解析返回视图对象View
View view = viewResolver.resolveViewName(viewName, locale);
}
}
// 视图解析器接口
public interface ViewResolver {
View resolveViewName(String viewName, Locale locale) throws Exception;
}
// 视图解析器接口实现类也很多:ThymeleafViewResolver、InternalResourceViewResolver
// 视图接口
public interface View{
void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception;
}
// 每一个接口肯定是有接口下的实现类,例如View接口的实现类:ThymeleafView、InternalResourceView....
1关于根据请求获取处理器执行链
SpringMVC执行流程第一步:通过处理器映射器找到请求路径对应的处理器方法
分析这一行代码:
HandlerExecutionChain mappedHandler = getHandler(request);
1. HandlerExecutionChain:处理器执行链对象
2. HandlerExecutionChain中的属性:
public class HandlerExecutionChain{
// 底层对应的是一个HandlerMethod对象
// 处理器方法对象
Object handler = new HandlerMethod(.....);
// 该请求对应的所有的拦截器按照顺序放到了ArrayList集合中
// 所有的拦截器对象也都是在服务器启动的时候都创建好。
List<HandlerInterceptor> interceptorList;
}
3. HandlerMethod 是什么?
HandlerMethod是最核心的要执行的目标,翻译为:处理器方法。
注意:HandlerMethod 是在web服务器启动时初始化spring容器的时候,就创建好了。
这个类当中比较重要的属性包括:beanName和Method
例如,以下代码:
@Controller("userController")
public class UserController{
@RequestMapping("/login")
public String login(User user){
return ....
}
}
那么以上代码对应了一个HandlerMethod对象:
public class HandlerMethod{
private String beanName = "userController";
private Method loginMethod;
}
4. getHandler(request);
这个方法还是在DispatcherServlet类中。
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
// 通过合适的 HandlerMapping才能获取到 HandlerExecutionChain对象。
// 如果你处理器方法使用了 @RequestMapping注解,那么以下代码中的mapping是:RequestMappingHandlerMapping对象。
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
重点:
我们处理请求的第一步代码是:HandlerExecutionChain mappedHandler = getHandler(request);
其本质上是调用了:HandlerExecutionChain handler = mapping.getHandler(request);
mapping变量就是 HandlerMapping。
HandlerMapping是一个接口:
翻译为处理器映射器,专门负责映射的。就是本质上根据请求路径去映射处理器方法的。
HandlerMapping接口下有很多实现类:
例如其中一个比较有名的,常用的:RequestMappingHandlerMapping
这个 RequestMappingHandlerMapping 叫做:@RequestMapping注解专用的处理器映射器对象。
当然,如果你没有使用 @RequestMapping注解,也可以写xml配置文件来进行映射,那个时候对应的就是其他的HandlerMapping接口的实现类了。
HandlerMapping 对象也是在服务器启动阶段创建的,所有的HandlerMapping对象都是在服务器启动阶段创建,并且存放到集合中。
public class DispatcherServlet{
List<HandlerMapping> handlerMappings;
}
5. RequestMappingHandlerMapping中的 getHandler(request);
HandlerExecutionChain handler = mapping.getHandler(request);
mapping.getHandler(request);这个方法底层一定是获取了 HandlerMethod 对象,将其赋值给 HandlerExecutionChain的handler属性
public class RequestMappingHandlerMapping extends AbstractHandlerMethodMapping{
protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
super.registerHandlerMethod(handler, method, mapping);
updateConsumesCondition(mapping, method);
}
}
public class AbstractHandlerMethodMapping{
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
}
public void register(T mapping, Object handler, Method method) {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
}
protected HandlerMethod createHandlerMethod(Object handler, Method method) {
if (handler instanceof String beanName) {
return new HandlerMethod(beanName,
obtainApplicationContext().getAutowireCapableBeanFactory(),
obtainApplicationContext(),
method);
}
return new HandlerMethod(handler, method);
}
}
这一步牵连到的类有哪些:
HandlerExecutionChain
HandlerMethod
HandlerInterceptor
HandlerMapping
RequestMappingHandlerMapping(是HandlerMaping接口的实现)
2关于根据处理器来获取处理器适配器
分析:
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
1. 底层使用了适配器模式。
2. 每一个处理器(我们自己写的Controller),都有自己适合的处理器适配器。
3. 在SpringMVC当中处理器适配器也有很多种,其中一个比较有名的,常用的处理器适配器是:RequestMappingHandlerAdapter
这个处理器适配器是专门处理 “处理器方法”上有 @RequestMapping 注解的。
4. mappedHandler.getHandler() 获取的是 HandlerMethod 对象
5. HandlerAdapter也是一个接口:
其中有一个常用的实现类:RequestMappingHandlerAdapter
6. 在服务器启动阶段,所有的 HandlerAdapter接口的实现类都会创建出来。在服务器启动阶段!!!!!!
List<HandlerAdapter> handlerAdapters;
7. HandlerAdapter接口非常重要,通过这个接口来调用最终的 HandlerMethod。
8. HandlerAdapter是适配器,是对 HandlerMethod 进行的适配。
9. 在DispatcherServlet类中,如下代码:
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
}
3关于执行请求对应的拦截器preHandle
关于执行请求对应的拦截器的preHandle方法
DispatcherServlet:
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
HandlerExecutionChain:
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for (int i = 0; i < this.interceptorList.size(); i++) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
return true;
}
遍历List集合,从List集合中取出每一个 HandlerInterceptor对象,调用 preHandle,i++,可见是顺序调用。
4关于调用处理器方法
关于调用处理器方法:
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
ha 是处理器适配器
mv 是ModelAndView对象
这个方法是最核心的,调用请求路径对应的HandlerMethod。(调用处理器方法。)
ha是HandlerAdapter,如果是 @RequestMapping 注解对应的,那么就是 RequestMappingHandlerAdapter:
RequestMappingHandlerAdapter:
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
// 获取一个数据绑定工厂
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
// 获取一个可调用的处理器方法
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
// 给可调用的方法绑定数据
invocableMethod.setDataBinderFactory(binderFactory);
// 给可调用的方法设置参数
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
// 可调用的方法执行了。
invocableMethod.invokeAndHandle(webRequest, mavContainer);
}
在HandlerAdapter中做的核心事情:
将前端提交的form数据通过 HttpMessageConverter 将其转换成 POJO对象。(数据转换)
并将数据绑定到 HandlerMethod 对象上。
调用HandlerMethod。
返回 ModelAndView
5关于执行请求对应的拦截器的postHandle
DispatcherServlet:
mappedHandler.applyPostHandle(processedRequest, response, mv);
HandlerExecutionChain:
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
throws Exception {
for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
interceptor.postHandle(request, response, this.handler, mv);
}
}
通过源码解决,可以很轻松的看到,从List集合中逆序(i--)逐一取出拦截器对象,并且调用拦截器的 postHandle方法。
6关于处理分发结果
public class DispatcherServlet{
// 处理分发结果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
// 渲染
render(mv, request, response);
}
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 通过视图解析器进行解析,返回视图View对象
View view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
// 调用视图对象的渲染方法(完成响应)
view.render(mv.getModelInternal(), request, response);
}
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
Locale locale, HttpServletRequest request) throws Exception {
// 视图解析器
ViewResolver viewResolver;
// 通过视图解析器解析返回视图对象View
View view = viewResolver.resolveViewName(viewName, locale);
}
}
// 视图解析器接口
public interface ViewResolver {
View resolveViewName(String viewName, Locale locale) throws Exception;
}
// 视图解析器接口实现类也很多:ThymeleafViewResolver、InternalResourceViewResolver
// 视图接口
public interface View{
void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception;
}
// 每一个接口肯定是有接口下的实现类,例如View接口的实现类:ThymeleafView、InternalResourceView....
7关于执行拦截器的afterCompletion方法
DispatcherServlet:
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
// 渲染
render(mv, request, response);
// 执行该请求所对应的所有拦截器的afterCompletion方法
mappedHandler.triggerAfterCompletion(request, response, null);
}
HandlerExecutionChain:
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
通过源码可以看出,也是通过逆序(i--)的方式进行拦截器的调用,调用拦截器的afterCompletion方法。
11.2 从图片角度看执行流程
11.3 WEB服务器启动时都做了什么
先搞明白核心类的继承关系:
DispatcherServlet extends FrameworkServlet extends HttpServletBean extends HttpServlet extends GenericServlet implements Servlet
服务器启动阶段完成了:
- 初始化Spring上下文,也就是创建所有的bean,让IoC容器将其管理起来。
- 初始化SpringMVC相关的对象:处理器映射器,处理器适配器等。。。
第12章 手写Spring MVC
12.1 整个完整系统的参与者
对于一个完整的web项目参与者包括:
- Servlet规范的制定者(已有)
- 实现Servlet规范的Tomcat服务器(已有)
- Spring MVC框架的开发者(手写Spring MVC框架)
- 编写webapp的开发者(用Spring MVC框架的人)
12.2 基本结构搭建
12.2.1 创建Maven模块
12.2.2 引入Servlet依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springmvc</groupId>
<artifactId>myspringmvc</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<!--servlet api-->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
12.2.3配置Tomcat服务器
12.2.4 添加web支持
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
</web-app>
12.2.5 创建基本类和接口
根据Spring MVC执行流程,目前先创建出以下的类和接口,后期如果需要其他的再添加:
SpringMVC流程中重要的接口和类有哪些,分析一下:
-
类 DispatcherServlet extends HttpServlet
所有的Servlet都要实现Servlet接口,或者直接继承HttpServlet
Javaweb规范中的
重写 service(带http的) -
HandlerExecutionChain 类
-
HandlerMapping 处理器映射器接口【根据请求URI找到对应的Controller】
-
HandlerMapping的实现类有很多,其中专门为 @RequestMapping注解服务的处理器映射器:RequestMappingHandlerMapping
-
HandlerMethod(处理器方法)类
-
HandlerInterceptor 拦截器接口
-
HandlerAdapter 处理器适配器接口
这个接口下有很多实现类,其中有一个实现类,是专门给 @RequestMapping 注解使用的。 -
HandlerAdapter接口实现类 RequestMappingHandlerAdapter 【通过它调用的处理器方法】
-
ModelAndView类
-
ViewResolver接口 【SpringMVC的接口】
10和12的实现类有多种,这里使用JSP的模板引擎。JSP模板引擎对应的View和ViewResolver接口的实现类都是内置的,SpringMVC框架内部提供好了
InternalResourceViewResolver
InternalResourceView -
InternalResourceViewResolver类
-
View接口 【SpringMVC的接口】
-
InternalResourceView类
-
@Controller 注解
-
@RequestMapping 注解
-
枚举RequestMethod
-
ModelMap
12.3 部分类和接口的代码完善
12.3.1 @Controller注解
package org.myspringmvc.stereotype;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* ClassName: 【14】Controller
* Description: 用来标注控制器,被标注的控制器纳入IoC容器的管理。
* Datetime: 2024/4/3 9:59
* Author: 老杜@动力节点
* Version: 1.0
*/
@Target(ElementType.TYPE) // 表示该注解只能标注类
@Retention(RetentionPolicy.RUNTIME) // 表示该注解可以被反射机制读取
public @interface Controller {
}
12.3.2 RequestMethod枚举(新建)
package org.myspringmvc.web.bind.annotation;
/**
* ClassName: 【16】RequestMethod
* Description: 请求方式枚举
* Datetime: 2024/4/2 10:35
* Author: 老杜@动力节点
* Version: 1.0
*/
public enum RequestMethod {
GET, POST
}
12.3.3 @RequestMapping注解
package org.myspringmvc.web.bind.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* ClassName: 【15】RequestMapping
* Description: 请求映射的注解
* Datetime: 2024/4/3 10:00
* Author: 老杜@动力节点
* Version: 1.0
*/
@Target({ElementType.TYPE, ElementType.METHOD}) // 表示该注解能标注类,也能标注方法
@Retention(RetentionPolicy.RUNTIME) // 表示该注解可以被反射机制读取
public @interface RequestMapping {
/**
* 支持多个请求路径
* @return
*/
String[] value();
/**
* 指定请求方式
* @return
*/
RequestMethod method();
}
12.3.4 HandlerMethod
@Controller
public class UserController{
@RequestMapping(value...method)
public String login(){}
}
处理器方法:以上整体为一个HandlerMethod。处理器方法HandlerMethod里包括:Controller对象和Method对象
package org.myspringmvc.web.method;
import java.lang.reflect.Method;
/**
* ClassName: 【5】HandlerMethod
* Description: 处理器方法
* Datetime: 2024/4/3 9:52
* Author: 老杜@动力节点
* Version: 1.0
*/
public class HandlerMethod {
/**
* 真正的处理器对象
*/
private Object handler;
/**
* 处理器方法
*/
private Method method;
public Object getHandler() {
return handler;
}
public void setHandler(Object handler) {
this.handler = handler;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
public HandlerMethod() {
}
public HandlerMethod(Object handler, Method method) {
this.handler = handler;
this.method = method;
}
}
12.3.5 HandlerMapping接口
package org.myspringmvc.web.servlet;
import jakarta.servlet.http.HttpServletRequest;
/**
* ClassName: 【3】HandlerMapping
* Description: 处理器映射器(根据请求路径映射到HandlerMethod上)
* Datetime: 2024/4/3 9:49
* Author: 老杜@动力节点
* Version: 1.0
*/
public interface HandlerMapping {
/**
* 根据请求返回处理器执行链对象。
* @param request 请求对象
* @return 处理器执行链对象
* @throws Exception
*/
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
12.3.6 RequestMappingHandlerMapping
package org.myspringmvc.web.servlet.mvc.method.annotation;
import jakarta.servlet.http.HttpServletRequest;
import org.myspringmvc.web.servlet.HandlerExecutionChain;
import org.myspringmvc.web.servlet.HandlerMapping;
/**
* ClassName: RequestMappingHandlerMapping
* Description:
* Datetime: 2024/4/2 9:44
* Author: 老杜@动力节点
* Version: 1.0
*/
public class RequestMappingHandlerMapping implements HandlerMapping {
@Override
public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
return null;
}
}
12.3.7 HandlerAdapter接口
package org.myspringmvc.web.servlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* ClassName: 【7】HandlerAdapter
* Description: 处理器适配器接口
* Datetime: 2024/4/3 9:53
* Author: 老杜@动力节点
* Version: 1.0
*/
public interface HandlerAdapter {
/**
* 调用处理器方法(底层会真正的调用处理器方法,执行核心业务。)
* @param request
* @param response
* @param handler
* @return 数据和视图对象。
* @throws Exception
*/
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
}
12.3.8 RequestMappingHandlerAdapter
package org.myspringmvc.web.servlet.mvc.method.annotation;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.myspringmvc.web.servlet.HandlerAdapter;
import org.myspringmvc.web.servlet.ModelAndView;
/**
* ClassName: RequestMappingHandlerAdapter
* Description:
* Datetime: 2024/4/2 9:44
* Author: 老杜@动力节点
* Version: 1.0
*/
public class RequestMappingHandlerAdapter implements HandlerAdapter {
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return null;
}
}
12.3.9 View接口
package org.myspringmvc.web.servlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Map;
/**
* ClassName: 【12】View
* Description:
* Datetime: 2024/4/2 8:58
* Author: 老杜@动力节点
* Version: 1.0
*/
public interface View {
/**
* 获取响应的内容类型
* @return
*/
String getContentType();
/**
* 渲染
* @param model
* @param request
* @param response
* @throws Exception
*/
void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception;
}
12.3.10 InternalResourceView
package org.myspringmvc.web.servlet.view;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.myspringmvc.web.servlet.View;
import java.util.Map;
/**
* ClassName: InternalResourceView
* Description:
* Datetime: 2024/4/2 10:17
* Author: 老杜@动力节点
* Version: 1.0
*/
public class InternalResourceView implements View {
@Override
public String getContentType() {
return null;
}
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
}
}
12.3.11 ViewResolver接口
package org.myspringmvc.web.servlet;
import java.util.Locale;
/**
* ClassName: 【10】ViewResolver
* Description:视图解析器接口
* Datetime: 2024/4/2 8:58
* Author: 老杜@动力节点
* Version: 1.0
*/
public interface ViewResolver {
/**
* 解视图解析,将逻辑视图名转换为物理视图名,并且返回视图对象。
* @param viewName
* @param locale
* @return
* @throws Exception
*/
View resolveViewName(String viewName, Locale locale) throws Exception;
}
12.3.12 InternalResourceViewResolver
package org.myspringmvc.web.servlet.view;
import org.myspringmvc.web.servlet.View;
import org.myspringmvc.web.servlet.ViewResolver;
import java.util.Locale;
/**
* ClassName: InternalResourceViewResolver
* Description:
* Datetime: 2024/4/2 9:45
* Author: 老杜@动力节点
* Version: 1.0
*/
public class InternalResourceViewResolver implements ViewResolver {
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
return null;
}
}
12.3.13 DispatcherServlet
package org.myspringmvc.web.servlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* ClassName: DispatcherServlet
* Description:
* Datetime: 2024/4/2 8:50
* Author: 老杜@动力节点
* Version: 1.0
*/
public class DispatcherServlet extends HttpServlet {
@Override
public void init() throws ServletException {
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doDispatch(req, resp);
}
/**
* 处理请求的核心方法
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
private void doDispatch(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
12.3.14 HandlerExecutionChain
package org.myspringmvc.web.servlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
/**
* ClassName: 【2】HandlerExecutionChain
* Description: 处理器执行链
* Datetime: 2024/4/3 9:49
* Author: 老杜@动力节点
* Version: 1.0
*/
public class HandlerExecutionChain {
/**
* 处理器方法:实际上底层对象是 HandlerMethod对象。
*/
private Object handler;
/**
* 本次请求需要执行的拦截器
*/
private List<HandlerInterceptor> interceptors;
/**
* 当前拦截器执行到哪个拦截器了,当前拦截器的下标
*/
private int interceptorIndex = -1;
public HandlerExecutionChain(Object handler, List<HandlerInterceptor> interceptors) {
this.handler = handler;
this.interceptors = interceptors;
}
public HandlerExecutionChain() {
}
public Object getHandler() {
return handler;
}
public void setHandler(Object handler) {
this.handler = handler;
}
public List<HandlerInterceptor> getInterceptors() {
return interceptors;
}
public void setInterceptors(List<HandlerInterceptor> interceptors) {
this.interceptors = interceptors;
}
public int getInterceptorIndex() {
return interceptorIndex;
}
public void setInterceptorIndex(int interceptorIndex) {
this.interceptorIndex = interceptorIndex;
}
/**
* 执行所有拦截器的preHandle方法
* @param request
* @param response
* @return
*/
public boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 遍历拦截器(顺序遍历)
for (int i = 0; i < interceptors.size(); i++) {
// 取出每一个拦截器对象
HandlerInterceptor handlerInterceptor = interceptors.get(i);
// 调用preHandle方法
boolean result = handlerInterceptor.preHandle(request, response, handler);
// 根据执行结果,如果为false表示不再继续执行。
if(!result){
// 执行拦截器的afterCompletion方法
triggerAfterCompletion(request, response, null);
return false;
}
interceptorIndex = i;
}
return true;
}
/**
* 按照逆序的方式执行拦截器中的postHandle方法
* @param request
* @param response
* @param mv
*/
public void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
for (int i = interceptors.size() - 1; i >= 0; i--) {
HandlerInterceptor handlerInterceptor = interceptors.get(i);
handlerInterceptor.postHandle(request, response, handler, mv);
}
}
/**
* 按照逆序的方式执行拦截器的afterCompletion方法
* @param request
* @param response
* @param o
*/
public void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
for (int i = interceptorIndex; i >= 0; i--) {
HandlerInterceptor handlerInterceptor = interceptors.get(i);
handlerInterceptor.afterCompletion(request, response, handler, null);
}
}
}
12.3.15 HandlerInterceptor拦截器接口
package org.myspringmvc.web.servlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* ClassName: 【6】HandlerInterceptor
* Description: 拦截器接口
* Datetime: 2024/4/3 9:53
* Author: 老杜@动力节点
* Version: 1.0
*/
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
12.3.16 ModelMap类(新建)
package org.myspringmvc.ui;
import java.util.LinkedHashMap;
/**
* ClassName: 【17】ModelMap
* Description: 将数据存储到域中。
* Datetime: 2024/4/2 11:07
* Author: 老杜@动力节点
* Version: 1.0
*/
public class ModelMap extends LinkedHashMap<String, Object> {
public ModelMap() {
}
/**
* 向域当中绑定数据
* @param name
* @param value
* @return
*/
public ModelMap addAttribute(String name, String value){
this.put(name, value);
return this;
}
}
12.3.17 ModelAndView
package org.myspringmvc.web.servlet;
import org.myspringmvc.ui.ModelMap;
/**
* ClassName: 【9】ModelAndView
* Description:
* Datetime: 2024/4/2 8:57
* Author: 老杜@动力节点
* Version: 1.0
*/
public class ModelAndView {
private Object view;
private ModelMap model;
public ModelAndView() {
}
public ModelAndView(Object view, ModelMap model) {
this.view = view;
this.model = model;
}
public Object getView() {
return view;
}
public void setView(Object view) {
this.view = view;
}
/**
* 该方法待实现
* @param viewName
*/
public void setViewName(String viewName){
// TODO
}
public ModelMap getModel() {
return model;
}
public void setModel(ModelMap model) {
this.model = model;
}
}
12.4 webapp开发者写应用
12.4.1 web.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<!--配置前端控制器-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.myspringmvc.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!--在web服务器启动的时候,就初始化DispatcherServlet,并且调用DispatcherServlet的init() 方法-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
DispatcherServlet的的contextConfigLocation可以编写代码了:
ServletConfig对象不需要我们创建。Tomcat服务器创建好了。并且Tomcat服务器调用init方法的时候,会自动将创建好的ServletConfig对象传递给init方法。
@Override
public void init() throws ServletException {
// 找到springmvc.xml文件
/**
* <init-param>
* <param-name>contextConfigLocation</param-name>
* <param-value>classpath:springmvc.xml</param-value>
* </init-param>
*/
// 根据以上的配置找 springmvc.xml文件
// 获取ServletConfig对象(Servlet配置信息对象,该对象由web容器自动创建,并且将其传递给init方法,我们在这里调用以下方法可以获取该对象)
ServletConfig servletConfig = this.getServletConfig();
String contextConfigLocation = servletConfig.getInitParameter(Constant.CONTEXT_CONFIG_LOCATION);
String springMvcXmlPath = getSpringMvcXmlPath(contextConfigLocation);
System.out.println("Spring MVC配置文件路径解析完成后的绝对路径:" + springMvcXmlPath);
}
private String getSpringMvcXmlPath(String contextConfigLocation) throws UnsupportedEncodingException {
if(contextConfigLocation.trim().startsWith(Constant.CLASSPATH)){
// 条件成立,表示这个配置文件要从类的路径当中查找
// 从类路径当中找springmvc.xml文件
String path = contextConfigLocation.substring(Constant.CLASSPATH.length());
String springMvcXmlPath = Thread.currentThread().getContextClassLoader().getResource(path).getPath();
// 对路径中的特殊字符进行解码操作,防止路径中有 % 等字符。
return URLDecoder.decode(springMvcXmlPath, Charset.defaultCharset());
}
return null;
}
定义系统常量类:Constant
package org.myspringmvc.web.constant;
/**
* ClassName: Constant
* Description:Spring MVC框架的系统常量类,所有的常量全部放到该常量类中。
* Datetime: 2024/4/2 11:28
* Author: 老杜@动力节点
* Version: 1.0
*/
public class Constant {
/**
* web.xml文件中配置DispatcherServlet的初始化参数的 contextConfigLocation 的名字。
*/
public static final String CONTEXT_CONFIG_LOCATION = "contextConfigLocation";
/**
* contextConfigLocation的前缀
*/
public static final String CLASSPATH = "classpath:";
}
12.4.2 编写处理器Controller
package com.powernode.springmvc.controller;
import org.myspringmvc.stereotype.Controller;
import org.myspringmvc.web.bind.annotation.RequestMapping;
import org.myspringmvc.web.bind.annotation.RequestMethod;
/**
* ClassName: UserController
* Description:
* Datetime: 2024/4/2 11:38
* Author: 老杜@动力节点
* Version: 1.0
*/
@Controller
public class UserController {
@RequestMapping(value = "/", method = RequestMethod.GET)
public String index(){
return "index";
}
}
12.4.3 编写拦截器
package com.powernode.springmvc.interceptors;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.myspringmvc.web.servlet.HandlerInterceptor;
import org.myspringmvc.web.servlet.ModelAndView;
/**
* ClassName: Interceptor1
* Description:
* Datetime: 2024/4/2 11:40
* Author: 老杜@动力节点
* Version: 1.0
*/
public class Interceptor1 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("Interceptor1's preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("Interceptor1's postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("Interceptor1's afterCompletion");
}
}
package com.powernode.springmvc.interceptors;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.myspringmvc.web.servlet.HandlerInterceptor;
import org.myspringmvc.web.servlet.ModelAndView;
/**
* ClassName: Interceptor2
* Description:
* Datetime: 2024/4/2 11:41
* Author: 老杜@动力节点
* Version: 1.0
*/
public class Interceptor2 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("Interceptor2's preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("Interceptor2's postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("Interceptor2's afterCompletion");
}
}
12.4.4 编写springmvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<!--组件扫描-->
<component-scan base-package="com.powernode.springmvc.controller"/>
<!--视图解析器-->
<bean class="org.myspringmvc.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!--拦截器-->
<interceptors>
<bean class="com.powernode.springmvc.interceptors.Interceptor1"/>
<bean class="com.powernode.springmvc.interceptors.Interceptor2"/>
</interceptors>
</beans>
InternalResourceViewResolver类中添加属性:suffix和prefix
package org.myspringmvc.web.servlet.view;
import org.myspringmvc.web.servlet.View;
import org.myspringmvc.web.servlet.ViewResolver;
import java.util.Locale;
/**
* ClassName: InternalResourceViewResolver
* Description:
* Datetime: 2024/4/2 9:45
* Author: 老杜@动力节点
* Version: 1.0
*/
public class InternalResourceViewResolver implements ViewResolver {
private String suffix;
private String prefix;
public String getSuffix() {
return suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
return null;
}
}
12.4.5 提供视图
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>index jsp</title>
</head>
<body>
<h1>动力节点:手写Spring MVC框架</h1>
</body>
</html>
12.5 服务器启动阶段的处理
12.5.1 分析服务器启动阶段都需要初始化什么
- 初始化Spring容器
a. 组件扫描包下的类纳入IoC容器的管理。
b. 创建视图解析器对象
c. 创建所有的拦截器对象
d. 扫描这个包下所有的类:org.myspringmvc.web.servlet.mvc.method.annotation,全部实例化,纳入IoC容器管理 - 初始化HandlerMapping
- 初始化HandlerAdapter
- 初始化ViewResolver
12.5.2 初始化Spring容器
Spring容器:ApplicationContext
Spring Web容器:WebApplicationContext
WebApplicationContext继承ApplicationContext,是web项目专属的。
普通的Java项目,使用Spring的话,底层创建的对象就是ApplicationContext;如果是web项目,使用Spring,底层创建的就是WebApplicationContext。两者都属于IoC容器。
服务器启动的时候,把所有该创建的bean对象全部创建出来,存储到ApplicationContext或者WebApplicationContext中。底层以Map集合的方式存储,key是bean的id或者名字,value就是bean对象。整个map集合作为ApplicationContext或WebApplicationContext对象的属性。
ApplicationContext称为父容器,WebApplicationContext称为子容器
两者区别:WebApplicationContext多了一个ServletContext属性
12.5.2.1 组件扫描
添加解析xml文件的依赖
<!--dom4j-->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!--jaxen-->
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
ApplicationContext
package org.myspringmvc.context;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.File;
import java.lang.reflect.Constructor;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;
/**
* ClassName: ApplicationContext
* Description: Spring的IoC容器或者Spring上下文,启动服务器时,初始化。适合于普通的java项目
* Datetime: 2024/4/2 13:52
* Author: 老杜@动力节点
* Version: 1.0
*/
public class ApplicationContext {
private Map<String, Object> beanMap = new HashMap<>();
public ApplicationContext(String xmlPath){
try {
// 解析xml文件
SAXReader reader = new SAXReader();
Document document = reader.read(new File(xmlPath));
// 组件扫描
Element componentScanElement = (Element)document.selectSingleNode("/beans/component-scan");
Map<RequestMappingInfo, HandlerMethod> map = componentScan(componentScanElement);
// 创建视图解析器
Element viewResolverElement = (Element)document.selectSingleNode("/beans/bean");
createViewResolver(viewResolverElement);
// 创建拦截器
Element interceptorsElement = (Element)document.selectSingleNode("/beans/interceptors");
createInterceptors(interceptorsElement);
// 创建org.springmvc.web.servlet.mvc.method.annotation下的所有的HandlerMapping
createHandlerMapping(Const.DEFAULT_PACKAGE, map);
// 创建org.springmvc.web.servlet.mvc.method.annotation下的所有的HandlerAdapter
createHandlerAdapter(Const.DEFAULT_PACKAGE);
System.out.println("Spring IoC容器的当前状态:" + beanMap);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 组件扫描
* @param componentScanElement
*/
private Map<RequestMappingInfo, HandlerMethod> componentScan(Element componentScanElement) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
// 创建处理器映射器大Map
Map<RequestMappingInfo, HandlerMethod> map = new HashMap<>();
// 获取包名
String basePackage = componentScanElement.attributeValue(Const.BASE_PACKAGE);
System.out.println("组件扫描的包:" + basePackage);
// 获取包的路径
String basePath = basePackage.replace(".", "/");
System.out.println("组件包对应的路径:" + basePath);
// 获取绝对路径
String absolutePath = Thread.currentThread().getContextClassLoader().getResource(basePath).getPath();
absolutePath = URLDecoder.decode(absolutePath, Charset.defaultCharset());
System.out.println("组件包对应的绝对路径:" + absolutePath);
// 封装为File对象
File file = new File(absolutePath);
// 获取该目录下所有的子文件
File[] files = file.listFiles();
// 遍历数组
for(File f : files){
String classFileName = f.getName();
System.out.println("class文件的名字:" + classFileName);
if(classFileName.endsWith(Const.SUFFIX_CLASS)){
String simpleClassName = classFileName.substring(0, classFileName.lastIndexOf("."));
System.out.println("简单类名:" + simpleClassName);
String className = basePackage + "." + simpleClassName;
System.out.println("完整类名:" + className);
// 如果类上有 @Controller 注解,则实例化Controller对象, 并且将其存储到IoC容器当中。
Class<?> clazz = Class.forName(className);
if(clazz.isAnnotationPresent(Controller.class)){
// 创建了Controller对象
Object bean = clazz.newInstance();
// 将其存储到IoC容器中(map集合)
beanMap.put(firstCharLowCase(simpleClassName), bean);
// 创建这个bean中所有的HandlerMethod对象,将其放到map集合中。
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods){
if(method.isAnnotationPresent(RequestMapping.class)){
// 获取方法上的注解
RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
// 创建RequestMappingInfo对象(key)
RequestMappingInfo requestMappingInfo = new RequestMappingInfo();
requestMappingInfo.setRequestURI(requestMapping.value()[0]); // 请求路径
requestMappingInfo.setMethod(requestMapping.method().toString()); // 请求方式
// 创建HandlerMethod对象(value)
HandlerMethod handlerMethod = new HandlerMethod();
handlerMethod.setHandler(bean);
handlerMethod.setMethod(method);
// 放到map集合
map.put(requestMappingInfo, handlerMethod);
}
}
}
}
}
return map;
}
/**
* 这个方法的作用是将一个字符串的首字母变成小写
* @param simpleClassName
* @return
*/
private String firstCharLowerCase(String simpleName) {
return simpleName.substring(0, 1).toLowerCase() + simpleName.substring(1);
}
/**
* 通过beanName获取对应的bean
* @param beanName
* @return
*/
public Object getBean(String beanName){
return beanMap.get(beanName);
}
}
WebApplicationContext
package org.myspringmvc.context;
import jakarta.servlet.ServletContext;
/**
* ClassName: WebApplicationContext
* Description: Spring的IoC容器或者Spring上下文,启动服务器时,初始化。适合于web项目
* Datetime: 2024/4/2 14:24
* Author: 老杜@动力节点
* Version: 1.0
*/
public class WebApplicationContext extends ApplicationContext {
private ServletContext servletContext;
private String springMvcConfigPath;
public WebApplicationContext(ServletContext servletContext, String springMvcConfigPath) {
super(springMvcConfigPath);
this.servletContext = servletContext;
}
public ServletContext getServletContext() {
return servletContext;
}
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
public String getSpringMvcConfigPath() {
return springMvcConfigPath;
}
public void setSpringMvcConfigPath(String springMvcConfigPath) {
this.springMvcConfigPath = springMvcConfigPath;
}
}
在DispatcherServlet中添加如下代码:
@Override
public void init() throws ServletException {
// 找到springmvc.xml文件
/**
* <init-param>
* <param-name>contextConfigLocation</param-name>
* <param-value>classpath:springmvc.xml</param-value>
* </init-param>
*/
// 根据以上的配置找 springmvc.xml文件
// 获取ServletConfig对象(Servlet配置信息对象,该对象由web容器自动创建,并且将其传递给init方法,我们在这里调用以下方法可以获取该对象)
ServletConfig servletConfig = this.getServletConfig();
String contextConfigLocation = servletConfig.getInitParameter(Const.CONTEXT_CONFIG_LOCATION);
System.out.println("contextConfigLocation-->" + contextConfigLocation);
String springMvcConfigPath = null;
if(contextConfigLocation.trim().startsWith(Const.PREFIX_CLASSPATH)){
// 条件成立,表示这个配置文件要从类的路径当中查找
// 从类路径当中找springmvc.xml文件
springMvcConfigPath = Thread.currentThread().getContextClassLoader().getResource(contextConfigLocation.substring(Const.PREFIX_CLASSPATH.length())).getPath();
// 对路径中的特殊字符进行解码操作。让其正常显示。
springMvcConfigPath = URLDecoder.decode(springMvcConfigPath, Charset.defaultCharset());//Charset.defaultCharset() 默认为UTF-8
System.out.println("Spring MVC配置文件的绝对路径:" + springMvcConfigPath);
}
// 初始化Spring Web容器
WebApplicationContext webApplicationContext = new WebApplicationContext(this.getServletContext(), springMvcConfigPath);
// webApplicationContext 代表的就是Spring Web容器,我们最好将其存储到 Servlet上下文中。以便后期的使用。
this.getServletContext().setAttribute(Const.WEB_APPLICATION_CONTEXT, webApplicationContext);
// 初始化处理器映射器
this.handlerMapping = (HandlerMapping) webApplicationContext.getBean(Const.HANDLER_MAPPING);
// 初始化处理器适配器
this.handlerAdapter = (HandlerAdapter) webApplicationContext.getBean(Const.HANDLER_ADAPTER);
// 初始化视图解析器
this.viewResolver = (ViewResolver) webApplicationContext.getBean(Const.VIEW_RESOLVER);
}
添加常量值:
启动服务器测试:
12.5.2.2 创建视图解析器对象
InternalResourceViewResolver类代码改动,添加prefix和suffix属性:
package org.myspringmvc.web.servlet.view;
import org.myspringmvc.web.servlet.View;
import org.myspringmvc.web.servlet.ViewResolver;
import java.util.Locale;
/**
* ClassName: InternalResourceViewResolver
* Description:
* Datetime: 2024/4/2 9:45
* Author: 老杜@动力节点
* Version: 1.0
*/
public class InternalResourceViewResolver implements ViewResolver {
private String suffix;
private String prefix;
public String getSuffix() {
return suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
return null;
}
}
/**
* 创建视图解析器
* @param viewResolverElement
*/
private void createViewResolver(Element viewResolverElement) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
String className = viewResolverElement.attributeValue(Const.BEAN_TAG_CLASS_ATTRIBUTE);//"class"
System.out.println("视图解析器名字:" + className);
// 通过反射机制创建对象
Class<?> clazz = Class.forName(className);
// 视图解析器对象(这里使用JSP的模板引擎,其ViewResolver接口的实现类为:InternalResourceViewResolver)
Object bean = clazz.newInstance();
// 获取当前bean节点下子节点property
List<Element> propertyElements = viewResolverElement.elements(Const.PROPERTY_TAG_NAME);//"property"
for(Element propertyElement : propertyElements){
// 属性名
String fieldName = propertyElement.attributeValue(Const.PROPERTY_NAME);//"name"
// 将属性名转换为set方法名
String setMethodName = fieldNameToSetMethodName(fieldName);
// 属性值
String fieldValue = propertyElement.attributeValue(Const.PROPERTY_VALUE);//"value"
System.out.println("属性名:" + fieldName);
System.out.println("set方法名:" + setMethodName);
System.out.println("属性值:" + fieldValue);
// 通过方法名获取方法
Method setMethod = clazz.getDeclaredMethod(setMethodName, String.class);
// 通过反射机制调用方法(setMethod.invoke(哪个对象,传什么参数))
setMethod.invoke(bean, fieldValue);
}
// 添加到IoC容器
//beanMap.put(firstCharLowCase(clazz.getSimpleName()), bean);// "internalResourceViewResolver"
beanMap.put(Const.VIEW_RESOLVER, bean);// "viewResolver"
}
/**
* 将属性名转换为set方法名
* @param fieldName
* @return
*/
private String fieldNameToSetMethodName(String fieldName) {
return "set" + firstCharUpperCase(fieldName);
}
/**
* 将一个字符串的首字母变大写
* @param fieldName
* @return
*/
private String firstCharUpperCase(String fieldName) {
return (fieldName.charAt(0) + "").toUpperCase() + fieldName.substring(1);
}
12.5.2.3 创建所有的拦截器对象
将拦截器bean创建出来存储到List集合中,再把List集合添加到beanMap中
在ApplicationContext构造方法中继续添加如下代码:
/**
* 创建拦截器
* @param interceptorsElement
*/
private void createInterceptors(Element interceptorsElement) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
// 准备一个List集合,存储拦截器对象
List<HandlerInterceptor> interceptors = new ArrayList<>();
// 获取该标签下所有的bean标签
List<Element> beans = interceptorsElement.elements("bean");
// 遍历bean标签
for(Element beanElement : beans){
String className = beanElement.attributeValue(Const.BEAN_TAG_CLASS_ATTRIBUTE);//"class"
// 通过反射机制创建对象
Class<?> clazz = Class.forName(className);
Object interceptor = clazz.newInstance();
interceptors.add((HandlerInterceptor) interceptor);
}
// 存储到IoC容器中
beanMap.put(Const.INTERCEPTORS, interceptors);//"interceptors"
}
12.5.2.4 初始化annotation包下所有类的实例
// 将这个包下所有的类实例化:org.myspringmvc.web.servlet.mvc.method.annotation
String dirPath = Thread.currentThread().getContextClassLoader().getResource(Constant.PACKAGE_AUTO_CREATE.replace(".", "/")).getPath();
File file = new File(URLDecoder.decode(dirPath));
if(file.isDirectory()){
File[] files = file.listFiles();
for (File classFile : files){
if(classFile.getName().endsWith(".class")){
String className = Constant.PACKAGE_AUTO_CREATE + "." + classFile.getName().substring(0, classFile.getName().lastIndexOf("."));
Class<?> clazz = Class.forName(className);
Constructor<?> defaultCon = clazz.getDeclaredConstructor();
Object bean = defaultCon.newInstance();
if(bean instanceof HandlerMapping){
beanMap.put(Constant.HANDLER_MAPPING, bean);
}
if(bean instanceof HandlerAdapter){
beanMap.put(Constant.HANDLER_ADAPTER, bean);
}
}
}
}
System.out.println("Spring Web容器当下状态:" + beanMap);
/**
* 创建HandlerAdapter
* @param defaultPackage
*/
private void createHandlerAdapter(String defaultPackage) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
System.out.println("defaultPackage = " + defaultPackage);
String defaultPath = defaultPackage.replace(".", "/");
System.out.println("defaultPath = " + defaultPath);
String absolutePath = Thread.currentThread().getContextClassLoader().getResource(defaultPath).getPath();
absolutePath = URLDecoder.decode(absolutePath, Charset.defaultCharset());
System.out.println("absolutePath = " + absolutePath);
File file = new File(absolutePath);
File[] files = file.listFiles();
for(File f : files){
String classFileName = f.getName();
System.out.println("classFileName = " + classFileName);
String simpleClassName = classFileName.substring(0, classFileName.lastIndexOf("."));
System.out.println("simpleClassName = " + simpleClassName);
String className = defaultPackage + "." + simpleClassName;
System.out.println("className = " + className);
// 获取Class
Class<?> clazz = Class.forName(className);
// 只有实现了HandlerAdapter接口的,再创建对象
if(HandlerAdapter.class.isAssignableFrom(clazz)){
Object bean = clazz.newInstance();
beanMap.put(Const.HANDLER_ADAPTER, bean);
return;
}
}
}
/**
* 创建HandlerMapping
* @param defaultPackage
*/
private void createHandlerMapping(String defaultPackage, Map<RequestMappingInfo, HandlerMethod> map) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
System.out.println("defaultPackage = " + defaultPackage);
String defaultPath = defaultPackage.replace(".", "/");
System.out.println("defaultPath = " + defaultPath);
String absolutePath = Thread.currentThread().getContextClassLoader().getResource(defaultPath).getPath();
absolutePath = URLDecoder.decode(absolutePath, Charset.defaultCharset());
System.out.println("absolutePath = " + absolutePath);
File file = new File(absolutePath);
File[] files = file.listFiles();
for(File f : files){
String classFileName = f.getName();
System.out.println("classFileName = " + classFileName);
String simpleClassName = classFileName.substring(0, classFileName.lastIndexOf("."));
System.out.println("simpleClassName = " + simpleClassName);
String className = defaultPackage + "." + simpleClassName;
System.out.println("className = " + className);
// 获取Class
Class<?> clazz = Class.forName(className);
// 只有实现了HandlerMapping接口的,再创建对象
if(HandlerMapping.class.isAssignableFrom(clazz)){
// 第一次写的时候调用了无参数构造方法创建对象。
//Object bean = clazz.newInstance();
// 后期修改了一下,调用有参数的构造方法来创建处理器映射器对象
Constructor<?> con = clazz.getDeclaredConstructor(Map.class);
Object bean = con.newInstance(map);
beanMap.put(Const.HANDLER_MAPPING, bean);
return;
}
}
}
12.5.3 初始化HandlerMapping
12.5.4 初始化HandlerAdapter
12.5.5 初始化ViewResolver
12.6 根据请求流程补充代码
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doDispatch(req, resp);
}
/**
* DispatcherServlet前端控制器最核心的方法。
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
private void doDispatch(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
// 1.根据请求对象获取对应的处理器执行链对象
// 通过前端提交的“请求”(包括请求路径和请求方式),来映射底层要执行的 HandlerMethod。
HandlerExecutionChain mappedHandler = handlerMapping.getHandler(request);
// 2.根据“处理器方法”获取对应的处理器适配器对象
HandlerAdapter ha = this.handlerAdapter;
// 3.执行拦截器中的preHandle方法
if (!mappedHandler.applyPreHandle(request, response)) {
return;
}
// 4.执行处理器方法,并返回ModelAndView
ModelAndView mv = ha.handle(request, response, mappedHandler.getHandler());
// 5.执行拦截器中的postHandle方法
mappedHandler.applyPostHandle(request, response, mv);
// 6.响应
// 通过视图解析器进行解析,返回View对象
View view = viewResolver.resolveViewName(mv.getView().toString(), Locale.CHINA);
// 渲染
view.render(mv.getModel(), request, response);
// 7.执行拦截器中的afterCompletion方法
mappedHandler.triggerAfterCompletion(request, response, null);
} catch (Exception e) {
e.printStackTrace();
}
}
12.6.1 根据请求获取处理器执行链
private void doDispatch(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
// 根据请求获取处理器执行链
HandlerExecutionChain mappedHandler = handlerMapping.getHandler(request);
System.out.println(mappedHandler);
} catch (Exception e) {
e.printStackTrace();
}
}
package org.myspringmvc.web.servlet.mvc.method.annotation;
import jakarta.servlet.http.HttpServletRequest;
import org.myspringmvc.context.WebApplicationContext;
import org.myspringmvc.web.constant.Constant;
import org.myspringmvc.web.method.HandlerMethod;
import org.myspringmvc.web.servlet.HandlerExecutionChain;
import org.myspringmvc.web.servlet.HandlerInterceptor;
import org.myspringmvc.web.servlet.HandlerMapping;
import org.myspringmvc.web.servlet.mvc.RequestMappingInfo;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* ClassName: 【4】RequestMappingHandlerMapping
* Description:
* 处理器映射器,专门为 @RequestMapping 注解服务器处理器映射器
* 通过前端提交的“请求”,来映射底层要执行的 HandlerMethod。
*
* 前端提交的“请求”包括:请求路径,请求方式。
*
* Datetime: 2024/4/3 9:51
* Author: 老杜@动力节点
* Version: 1.0
*/
public class RequestMappingHandlerMapping implements HandlerMapping {
/**
* 处理器映射器,主要就是通过以下的map集合进行映射。
* key是:请求信息
* value是:该请求对应要执行的处理器方法
*/
private Map<RequestMappingInfo, HandlerMethod> map;
/**
* 在创建 HandlerMapping对象的时候,给 map 集合赋值。
* @param map
*/
public RequestMappingHandlerMapping(Map<RequestMappingInfo, HandlerMethod> map) {
this.map = map;
}
@Override
public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 通过request对象,获取请求路径,获取请求方式,将其封装成RequestMappingInfo对象。
RequestMappingInfo requestMappingInfo = new RequestMappingInfo(request.getServletPath(), request.getMethod());
// 创建处理器执行链对象
HandlerExecutionChain handlerExecutionChain = new HandlerExecutionChain();
// 给执行链设置HandlerMethod
handlerExecutionChain.setHandler(map.get(requestMappingInfo));
// 获取所有拦截器
WebApplicationContext webApplicationContext = (WebApplicationContext) request.getServletContext().getAttribute(Const.WEB_APPLICATION_CONTEXT);
// 给执行链设置拦截器
List<HandlerInterceptor> interceptors = (List<HandlerInterceptor>)webApplicationContext.getBean(Const.INTERCEPTORS);
handlerExecutionChain.setInterceptors(interceptors);
return handlerExecutionChain;
}
}
HandlerMethod中的属性:
ApplicationContext:
/**
* 组件扫描
* @param componentScanElement
*/
private Map<RequestMappingInfo, HandlerMethod> componentScan(Element componentScanElement) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
// 创建处理器映射器大Map
Map<RequestMappingInfo, HandlerMethod> map = new HashMap<>();
// 获取包名
String basePackage = componentScanElement.attributeValue(Const.BASE_PACKAGE);
System.out.println("组件扫描的包:" + basePackage);
// 获取包的路径
String basePath = basePackage.replace(".", "/");
System.out.println("组件包对应的路径:" + basePath);
// 获取绝对路径
String absolutePath = Thread.currentThread().getContextClassLoader().getResource(basePath).getPath();
absolutePath = URLDecoder.decode(absolutePath, Charset.defaultCharset());
System.out.println("组件包对应的绝对路径:" + absolutePath);
// 封装为File对象
File file = new File(absolutePath);
// 获取该目录下所有的子文件
File[] files = file.listFiles();
// 遍历数组
for(File f : files){
String classFileName = f.getName();
System.out.println("class文件的名字:" + classFileName);
if(classFileName.endsWith(Const.SUFFIX_CLASS)){
String simpleClassName = classFileName.substring(0, classFileName.lastIndexOf("."));
System.out.println("简单类名:" + simpleClassName);
String className = basePackage + "." + simpleClassName;
System.out.println("完整类名:" + className);
// 如果类上有 @Controller 注解,则实例化Controller对象, 并且将其存储到IoC容器当中。
Class<?> clazz = Class.forName(className);
if(clazz.isAnnotationPresent(Controller.class)){
// 创建了Controller对象
Object bean = clazz.newInstance();
// 将其存储到IoC容器中(map集合)
beanMap.put(firstCharLowCase(simpleClassName), bean);
// 创建这个bean中所有的HandlerMethod对象,将其放到map集合中。
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods){
if(method.isAnnotationPresent(RequestMapping.class)){
// 获取方法上的注解
RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
// 创建RequestMappingInfo对象(key)
RequestMappingInfo requestMappingInfo = new RequestMappingInfo();
requestMappingInfo.setRequestURI(requestMapping.value()[0]); // 请求路径
requestMappingInfo.setMethod(requestMapping.method().toString()); // 请求方式
// 创建HandlerMethod对象(value)
HandlerMethod handlerMethod = new HandlerMethod();
handlerMethod.setHandler(bean);
handlerMethod.setMethod(method);
// 放到map集合
map.put(requestMappingInfo, handlerMethod);
}
}
}
}
}
return map;
}
ApplicationContext代码还有以下改造:
添加一个新的类:RequestMappingInfo
package org.myspringmvc.web.servlet.mvc;
import java.util.Objects;
/**
* ClassName: RequestMappingInfo
* Description: 请求映射信息,包含请求路径,还有请求方式.....
* Datetime: 2024/4/3 15:16
* Author: 老杜@动力节点
* Version: 1.0
*/
public class RequestMappingInfo {
/**
* 请求路径
*/
private String requestURI;
/**
* 请求方式
*/
private String method;
public String getRequestURI() {
return requestURI;
}
public void setRequestURI(String requestURI) {
this.requestURI = requestURI;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public RequestMappingInfo() {
}
public RequestMappingInfo(String requestURI, String method) {
this.requestURI = requestURI;
this.method = method;
}
/**
* 重点:思考:为什么这个类的hashCode和equals必须重写。
* RequestMappingInfo a = new RequestMappingInfo("/test", "GET");
* RequestMappingInfo b = new RequestMappingInfo("/test", "GET");
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RequestMappingInfo that = (RequestMappingInfo) o;
return Objects.equals(requestURI, that.requestURI) && Objects.equals(method, that.method);
}
@Override
public int hashCode() {
return Objects.hash(requestURI, method);
}
}
12.6.2 执行拦截器的preHandle
添加以下代码:
HandlerExecutionChain添加以下代码:
/**
* 执行所有拦截器的preHandle方法
* @param request
* @param response
* @return
*/
public boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 遍历拦截器(顺序遍历)
for (int i = 0; i < interceptors.size(); i++) {
// 取出每一个拦截器对象
HandlerInterceptor handlerInterceptor = interceptors.get(i);
// 调用preHandle方法
boolean result = handlerInterceptor.preHandle(request, response, handler);
// 根据执行结果,如果为false表示不再继续执行。
if(!result){
// 执行拦截器的afterCompletion方法
triggerAfterCompletion(request, response, null);
return false;
}
interceptorIndex = i;
}
return true;
}
12.6.3 执行处理器方法
DispatcherServlet中的doDispatch方法:
先让handle方法返回一个固定的ModelAndView,后期在详细编写 handle 方法:
12.6.4 执行拦截器的postHandle
DispatcherServlet的doDispatch方法中:
HandlerExecutionChain的方法:
/**
* 按照逆序的方式执行拦截器中的postHandle方法
* @param request
* @param response
* @param mv
*/
public void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
for (int i = interceptors.size() - 1; i >= 0; i--) {
HandlerInterceptor handlerInterceptor = interceptors.get(i);
handlerInterceptor.postHandle(request, response, handler, mv);
}
}
12.6.5 处理响应
在DispatcherServlet的 doDispatch方法中:
// 6.响应
// 通过视图解析器进行解析,返回View对象
View view = viewResolver.resolveViewName(mv.getView().toString(), Locale.CHINA);
// 渲染
view.render(mv.getModel(), request, response);
package org.myspringmvc.web.servlet.view;
import org.myspringmvc.web.servlet.View;
import org.myspringmvc.web.servlet.ViewResolver;
import java.util.Locale;
/**
* ClassName: InternalResourceViewResolver
* Description:
* Datetime: 2024/4/2 9:45
* Author: 老杜@动力节点
* Version: 1.0
*/
public class InternalResourceViewResolver implements ViewResolver {
private String suffix;
private String prefix;
public String getSuffix() {
return suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
// 视图解析器,将逻辑视图名称转换为物理视图名称。
return new InternalResourceView("text/html;charset=UTF-8", prefix + viewName + suffix);
}
}
package org.myspringmvc.web.servlet.view;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.myspringmvc.web.servlet.View;
import java.util.Map;
/**
* ClassName: InternalResourceView
* Description:
* Datetime: 2024/4/2 10:17
* Author: 老杜@动力节点
* Version: 1.0
*/
public class InternalResourceView implements View {
private String contentType;
private String path;
public InternalResourceView(String contentType, String path) {
this.contentType = contentType;
this.path = path;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
@Override
public String getContentType() {
return contentType;
}
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 设置响应内容类型
response.setContentType(getContentType());
// 将model数据存储到request域当中(默认情况下,数据是存储在request域当中的。)
if(model != null){
model.forEach(request::setAttribute);
}
// 转发(默认情况下,跳转到视图是以转发的方式)
request.getRequestDispatcher(path).forward(request, response);
}
}
12.6.6 执行拦截器的afterCompletion
在DispatcherServlet类的doDispatch方法中:
在HandlerExecutionChain中:
/**
* 按照逆序的方式执行拦截器的afterCompletion方法
* @param request
* @param response
* @param o
*/
public void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
for (int i = interceptorIndex; i >= 0; i--) {
HandlerInterceptor handlerInterceptor = interceptors.get(i);
handlerInterceptor.afterCompletion(request, response, handler, null);
}
}
12.6.7 初步测试
启动服务器,浏览器地址栏:http://localhost:8080/myspringmvc
后台效果:
如果让第二个拦截器返回false尝试一下:
初步测试通过!!!
12.7 调用处理器方法
package org.myspringmvc.web.servlet.mvc.method.annotation;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.myspringmvc.ui.ModelMap;
import org.myspringmvc.web.method.HandlerMethod;
import org.myspringmvc.web.servlet.HandlerAdapter;
import org.myspringmvc.web.servlet.ModelAndView;
import java.lang.reflect.Method;
/**
* ClassName: 【8】RequestMappingHandlerAdapter
* Description: 处理器适配器,专门为 @RequestMapping 注解准备的处理器适配器。
* Datetime: 2024/4/3 9:54
* Author: 老杜@动力节点
* Version: 1.0
*/
public class RequestMappingHandlerAdapter implements HandlerAdapter {
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 需要调用处理器方法的
HandlerMethod handlerMethod = (HandlerMethod)handler;
// 获取Controller对象
Object controller = handlerMethod.getHandler();
// 获取要调用的方法
Method method = handlerMethod.getMethod();
// 通过反射机制调用方法
// 我们自己写的springmvc框架,有一个特殊的要求,要求Controller类中方法必须有ModelMap参数
// 我们自己写的springmvc框架,还有一个特殊的要求,要求Controller类中方法必须返回String逻辑视图名字
ModelMap modelMap = new ModelMap();
String viewName = (String)method.invoke(controller, modelMap);
// 封装ModelAndView对象
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName(viewName);
modelAndView.setModel(modelMap);
return modelAndView;
}
}
第13章 全注解开发
13.1 web.xml文件的替代
13.1.1 Servlet3.0新特性
Servlet3.0新特性:web.xml文件可以不写了。
在Servlet3.0的时候,规范中提供了一个接口:
服务器在启动的时候会自动从容器中找 ServletContainerInitializer
接口的实现类,自动调用它的onStartup
方法来完成Servlet上下文的初始化。
在Spring3.1版本的时候,提供了这样一个类,实现以上的接口:
它的核心方法如下:
可以看到在服务器启动的时候,它会去加载所有实现WebApplicationInitializer
接口的类:
这个接口下有一个子类是我们需要的:AbstractAnnotationConfigDispatcherServletInitializer
当我们编写类继承AbstractAnnotationConfigDispatcherServletInitializer
之后,web服务器在启动的时候会根据它来初始化Servlet上下文。
13.1.2 编写WebAppInitializer
以下这个类就是用来代替web.xml文件的:
package com.powernode.springmvc.config;
import jakarta.servlet.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
/**
* ClassName: WebAppInitializer
* Description:
* Datetime: 2024/3/29 19:03
* Author: 老杜@动力节点
* Version: 1.0
*/
// 在这个配置类当中编写的其实就是web.xml文件中的配置。
// 用来标注这个类当做配置文件。
@Configuration
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* Spring的配置
* @return
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}
/**
* SpringMVC的配置
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
/**
* 用来配置DispatcherServlet的 <url-pattern>
* @return
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
/**
* 配置过滤器
* @return
*/
@Override
protected Filter[] getServletFilters() {
// 配置字符编码过滤器
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceResponseEncoding(true);
characterEncodingFilter.setForceRequestEncoding(true);
// 配置HiddenHttpMethodFilter
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
return new Filter[]{characterEncodingFilter, hiddenHttpMethodFilter};
}
}
Spring配置如下:
package com.powernode.springmvc.config;
import org.springframework.context.annotation.Configuration;
/**
* ClassName: SpringConfig
* Description:
* Datetime: 2024/3/29 17:03
* Author: 老杜@动力节点
* Version: 1.0
*/
@Configuration // 使用该注解指定这是一个配置类
public class SpringConfig {
}
SpringMVC配置如下:
package com.powernode.springmvc.config;
import org.springframework.context.annotation.Configuration;
/**
* ClassName: SpringMVCConfig
* Description:
* Datetime: 2024/3/29 17:03
* Author: 老杜@动力节点
* Version: 1.0
*/
@Configuration
public class SpringMVCConfig {
}
13.2 Spring MVC的配置
13.2.1 组件扫描
// 指定该类是一个配置类,可以当配置文件使用
@Configuration
// 开启组件扫描
@ComponentScan("com.powernode.springmvc.controller")
public class SpringMVCConfig {
}
13.2.2 开启注解驱动
// 指定该类是一个配置类,可以当配置文件使用
@Configuration
// 开启组件扫描
@ComponentScan("com.powernode.springmvc.controller")
// 开启注解驱动 <mvc:annotation-driven/>
@EnableWebMvc
public class SpringMVCConfig {
}
13.2.3 视图解析器
// 指定该类是一个配置类,可以当配置文件使用
@Configuration
// 开启组件扫描
@ComponentScan("com.powernode.springmvc.controller")
// 开启注解驱动
@EnableWebMvc
public class SpringMVCConfig {
// 以下三个方法合并起来就是开启视图解析器
@Bean
public ThymeleafViewResolver getViewResolver(SpringTemplateEngine springTemplateEngine) {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(springTemplateEngine);
resolver.setCharacterEncoding("UTF-8");
resolver.setOrder(1);
return resolver;
}
@Bean
public SpringTemplateEngine templateEngine(ITemplateResolver iTemplateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(iTemplateResolver);
return templateEngine;
}
@Bean
public ITemplateResolver templateResolver(ApplicationContext applicationContext) {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setApplicationContext(applicationContext);
resolver.setPrefix("/WEB-INF/thymeleaf/");
resolver.setSuffix(".html");
resolver.setTemplateMode(TemplateMode.HTML);
resolver.setCharacterEncoding("UTF-8");
resolver.setCacheable(false);//开发时关闭缓存,改动即可生效
return resolver;
}
}
13.2.4 开启默认Servlet处理
让SpringMVCConfig类实现这个接口:WebMvcConfigurer
并且重写以下的方法:
// 开启静态资源处理,开启默认的Servlet处理
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
13.2.5 view-controller
重写以下方法:
// 视图控制器
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/test").setViewName("test");
}
13.2.6 异常处理器
重写以下方法:
/*
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.Exception">tip</prop>
</props>
</property>
<property name="exceptionAttribute" value="yiChang"/>
</bean>
*/
// 配置异常处理器
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
// 可以配置多个异常处理器,这是其中一个。
SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
// 设置其中的 exceptionMappings 属性
Properties prop = new Properties();
prop.setProperty("java.lang.Exception", "tip");
resolver.setExceptionMappings(prop);
// 设置其中的 exceptionAttribute 属性
resolver.setExceptionAttribute("e");
// 将异常处理器添加到List集合中。
resolvers.add(resolver);
}
13.2.7 拦截器
重写以下方法:
// 配置拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
MyInterceptor myInterceptor = new MyInterceptor();
registry.addInterceptor(myInterceptor).addPathPatterns("/**").excludePathPatterns("/test");
}
第14章 SSM整合
14.1 引入相关依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.powernode</groupId>
<artifactId>ssmtest</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<!--springmvc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>6.1.4</version>
</dependency>
<!--spring jdbc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>6.1.4</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.15</version>
</dependency>
<!--mybatis spring-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>3.0.3</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>
<!--德鲁伊连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.22</version>
</dependency>
<!--jackson-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>
<!--servlet api-->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
<!--logback-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.3</version>
</dependency>
<!--thymeleaf和spring6的整合依赖-->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring6</artifactId>
<version>3.1.2.RELEASE</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
14.2 SSM整合
14.2.1 创建包结构
14.2.2 创建webapp目录
14.2.3 Spring整合MyBatis
14.2.3.1 编写jdbc.properties
在类根路径下创建属性配置文件,配置连接数据库的信息:jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/powernode?useUnicode=true&serverTimezone=Asia/Shanghai&useSSL=true&characterEncoding=utf-8
jdbc.username=root
jdbc.password=1234
14.2.3.2 编写DataSourceConfig
package com.powernode.ssm.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;
/**
* ClassName: DataSourceConfig
* Description:
* Datetime: 2024/4/1 14:25
* Author: 老杜@动力节点
* Version: 1.0
*/
public class DataSourceConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
14.2.3.3 编写MyBatisConfig
package com.powernode.ssm.config;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;
/**
* ClassName: MyBatisConfig
* Description:
* Datetime: 2024/4/1 14:25
* Author: 老杜@动力节点
* Version: 1.0
*/
public class MyBatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setTypeAliasesPackage("com.powernode.ssm.bean");
return sqlSessionFactoryBean;
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.powernode.ssm.dao");
return msc;
}
}
14.2.3.4 编写SpringConfig
package com.powernode.ssm.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
/**
* ClassName: SpringConfig
* Description:
* Datetime: 2024/4/1 16:48
* Author: 老杜@动力节点
* Version: 1.0
*/
// 标注该类是一个配置文件类
@Configuration
// 组件扫描
@ComponentScan({"com.powernode.ssm.service"})
// 属性配置文件位置
@PropertySource("classpath:jdbc.properties")
// 导入其他配置到Spring配置
@Import({MyBatisConfig.class, DataSourceConfig.class})
// 开启事务管理机制
@EnableTransactionManagement
public class SpringConfig {
}
14.2.4 Spring整合Spring MVC
14.2.4.1 编写WebAppInitializer(web.xml)
package com.powernode.ssm.config;
import jakarta.servlet.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
/**
* ClassName: WebAppInitializer
* Description:
* Datetime: 2024/4/1 14:59
* Author: 老杜@动力节点
* Version: 1.0
*/
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* Spring的配置
* @return
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
/**
* SpringMVC的配置
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
/**
* 用来配置DispatcherServlet的 <url-pattern>
* DispatcherServlet的映射路径
* @return
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
/**
* 配置过滤器
* @return
*/
@Override
protected Filter[] getServletFilters() {
// 配置字符编码过滤器
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceResponseEncoding(true);
characterEncodingFilter.setForceRequestEncoding(true);
// 配置HiddenHttpMethodFilter
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
return new Filter[]{characterEncodingFilter, hiddenHttpMethodFilter};
}
}
14.2.4.2 编写SpringMvcConfig
package com.powernode.ssm.config;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.*;
import org.thymeleaf.spring6.SpringTemplateEngine;
import org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring6.view.ThymeleafViewResolver;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ITemplateResolver;
import java.util.List;
/**
* ClassName: SpringMvcConfig
* Description:
* Datetime: 2024/4/1 15:02
* Author: 老杜@动力节点
* Version: 1.0
*/
@Configuration
@ComponentScan("com.powernode.ssm.handler")
@EnableWebMvc
public class SpringMvcConfig implements WebMvcConfigurer {
// 以下三个方法合并起来就是开启视图解析器
@Bean
public ThymeleafViewResolver getViewResolver(SpringTemplateEngine springTemplateEngine) {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(springTemplateEngine);
resolver.setCharacterEncoding("UTF-8");
resolver.setOrder(1);
return resolver;
}
@Bean
public SpringTemplateEngine templateEngine(ITemplateResolver iTemplateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(iTemplateResolver);
return templateEngine;
}
@Bean
public ITemplateResolver templateResolver(ApplicationContext applicationContext) {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setApplicationContext(applicationContext);
resolver.setPrefix("/WEB-INF/thymeleaf/");
resolver.setSuffix(".html");
resolver.setTemplateMode(TemplateMode.HTML);
resolver.setCharacterEncoding("UTF-8");
resolver.setCacheable(false);//开发时关闭缓存,改动即可生效
return resolver;
}
// 开启静态资源处理,开启默认的Servlet处理
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
// 视图控制器
@Override
public void addViewControllers(ViewControllerRegistry registry) {}
// 配置异常处理器
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {}
// 配置拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {}
}
14.2.5 添加事务控制
第一步:在SpringConfig中开启事务管理器
@EnableTransactionManagement
public class SpringConfig {
}
第二步:在DataSourceConfig中添加事务管理器对象
@Bean
public PlatformTransactionManager platformTransactionManager(DataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
第三步:在service类上添加如下注解:
@Transactional
public class UserService {}
14.3 实现功能测试ssm整合
14.3.1 数据库表
14.3.2 pojo类编写
package com.powernode.ssm.bean;
/**
* ClassName: User
* Description:
* Datetime: 2024/4/1 15:42
* Author: 老杜@动力节点
* Version: 1.0
*/
public class User {
private Long id;
private String name;
private String password;
private String email;
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", password='" + password + '\'' +
", email='" + email + '\'' +
'}';
}
public User() {
}
public User(Long id, String name, String password, String email) {
this.id = id;
this.name = name;
this.password = password;
this.email = email;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
14.3.3 dao编写
package com.powernode.ssm.dao;
import com.powernode.ssm.bean.User;
import org.apache.ibatis.annotations.Select;
/**
* ClassName: UserDao
* Description:
* Datetime: 2024/4/1 15:43
* Author: 老杜@动力节点
* Version: 1.0
*/
public interface UserDao {
@Select("select * from tbl_user where id = #{id}")
User selectById(Long id);
}
14.3.4 service编写
package com.powernode.ssm.service;
import com.powernode.ssm.bean.User;
/**
* ClassName: UserService
* Description:
* Datetime: 2024/4/1 15:45
* Author: 老杜@动力节点
* Version: 1.0
*/
public interface UserService {
/**
* 根据id获取用户信息
* @param id
* @return
*/
User getById(Long id);
}
package com.powernode.ssm.service.impl;
import com.powernode.ssm.bean.User;
import com.powernode.ssm.dao.UserDao;
import com.powernode.ssm.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* ClassName: UserServiceImpl
* Description:
* Datetime: 2024/4/1 15:45
* Author: 老杜@动力节点
* Version: 1.0
*/
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public User getById(Long id) {
return userDao.selectById(id);
}
}
14.3.5 handler编写
package com.powernode.ssm.handler;
import com.powernode.ssm.bean.User;
import com.powernode.ssm.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* ClassName: UserHandler
* Description:
* Datetime: 2024/4/1 15:46
* Author: 老杜@动力节点
* Version: 1.0
*/
@RestController
@RequestMapping("/users")
public class UserHandler {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public User detail(@PathVariable("id") Long id){
return userService.getById(id);
}
}
14.3.6 前端发送ajax
14.3.6.1 引入js文件
14.3.6.2 开启静态资源处理
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
14.3.6.3 视图控制器
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
}
14.3.6.4 编写ajax
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>ssm整合</title>
<!--引入vue-->
<script th:src="@{/static/js/vue3.4.21.js}"></script>
<!--引入axios-->
<script th:src="@{/static/js/axios.min.js}"></script>
</head>
<body>
<div id="app">
<button @click="getMessage">查看id=1的用户信息</button>
<h1>{{message}}</h1>
</div>
<script th:inline="javascript">
Vue.createApp({
data(){
return {
message : ''
}
},
methods : {
async getMessage(){
let response = await axios.get([[@{/}]] + 'users/1')
this.message = response.data
}
}
}).mount("#app")
</script>
</body>
</html>
测试结果: