Appearance
Spring MVC
本文介绍Spring MVC中的关键组件与流程,并会尝试自己实现一个简陋的MVC框架。
1. 前置知识
如果我们要开发WEB应用,那么就需要网络连接,即一台设备可以发送数据到另一台设备,这涉及网络通信,涉及的网络模型是TCP/IP 模型。
在Java中,我们可以通过ServerSocket来监听TCP连接,但是,从传输层到应用层,需要进行数据转换,也就是说,从传输层的数据包转换为应用层特定协议的数据,这个工作是需要完成的。
好在对于应用层的HTTP协议,市面上有很多软件完成了这一份工作,即将TCP的数据包转换为HTTP请求,比如Tomcat。
之后呢,Tomcat拿到转换完成后的HTTP请求,该交给谁呢?这时,各种各种的应用程序(业务程序)就出场了,Tomcat会将请求交给对应的应用程序,这些应用程序都有一个特点,那就是实现了Servlet接口。
java
package jakarta.servlet;
import java.io.IOException;
public interface Servlet {
void init(ServletConfig config) throws ServletException;
ServletConfig getServletConfig();
void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
String getServletInfo();
void destroy();
}Servlet中的方法service()就是我们要写业务代码的地方,也是由Servlet容器(Tomcat)调用的方法,其中参数ServletRequest req和ServletResponse res都是由Tomcat传给我们的,并且都是HTTP相关的具体实现类,req表示HTTP请求,res表示HTTP响应。
Spring MVC就是实现了Servlet,并在其中根据路径匹配等规则,找到我们写的Controller中的方法,反射调用,并为我们封装返回值。
2. 请求到响应总体流程
根据上面的分析,请求和响应是由Servlet为我们封装好的,我们只需要操作请求和响应就可以了。
从请求到响应,主要流程如下:
- 根据请求方法和请求路径,找到匹配的处理器方法;
- 从请求中解析出参数,解析参数涉及类型转换;
- 反射调用处理器方法,传入第二步解析出来的参数;
- 获取第三步的返回值,处理返回值;
- 由于WEB技术涉及视图渲染,所以可以通过返回值渲染视图(也就是前端界面);
3. 模拟实现
本小节将按照以上流程,模拟实现MVC框架。
3.1 模拟实现Tomcat
com.sun.net.httpserver.HttpServer是Java提供的轻量级HTTP服务器,我们可以用它来模拟Tomcat容器,从而帮助我们获取HTTP请求,并返回响应。
基础用法如下:
java
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
public class SimpleHttpServer {
public static void main(String[] args) throws IOException {
// 创建一个 HTTP 服务器,监听在本地的 8080 端口
HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
// 设置上下文路径 "/hello",当访问 http://localhost:8080/hello 时会触发 MyHandler
server.createContext("/hello", new MyHandler());
// 设置默认的执行器,或者使用默认的线程池
server.setExecutor(null); // 使用默认的线程池
// 启动服务器
server.start();
System.out.println("服务器已启动,监听端口 8080");
System.out.println("请在浏览器中访问 http://localhost:8080/hello");
}
// 处理 HTTP 请求的处理器
static class MyHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) throws IOException {
String response = "Hello from Simple Java HTTP Server!";
// 设置响应头,告知客户端响应类型和内容长度
exchange.sendResponseHeaders(200, response.length());
// 获取响应输出流,写入响应内容
OutputStream os = exchange.getResponseBody();
os.write(response.getBytes());
os.close(); // 关闭输出流
}
}
}关于HttpServer.create()第二个参数的说明
第二个参数
0代表的是 backlog,它指的是操作系统为 TCP 服务器 维护的一个队列的长度。这个队列用来存放那些已经完成了 TCP 三次握手,但服务器应用程序还没有来得及调用accept()方法接受的客户端连接。简单来说:
- 当一个客户端尝试连接到服务器时,它会首先与服务器进行 TCP 三次握手,建立一个 TCP 连接。
- 握手完成后,这个连接就处于一个“已完成但未被接受”的状态。
- 这些已完成的连接会被放到一个队列里等待服务器应用程序处理。这个队列的长度就是 backlog。
如果将
backlog设置为 0,这通常意味着使用系统默认的 backlog 值。具体的默认值取决于操作系统和配置,但它通常是一个合理的小整数。如果将
backlog设置为一个正整数,比如50,那么这个队列最多可以容纳 50 个已完成但未被接受的连接。
有了HttpServer,那么我们就可以将所有请求分发到一个类中(参考Spring MVC,我们也叫DispatcherServlet,但是并没有实现Servlet接口,因为我们的容器没有实现Servlet那一套接口),由该类进行后续的工作,并且该类通用处理方法接受HttpExchange作为参数。
3.2 模拟实现DispatcherServlet
DispatcherServlet类是主要的类,主要完成以下工作:根据请求方法和路径找到处理器方法,从请求中解析出参数,通过反射调用处理器方法,最后处理返回值,如果有必要,进行视图渲染。
3.2.1 缓存处理器方法
首先,我们先定义处理器方法:
java
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class InvocableHandlerMethod {
// 处理器对象
private Object handler;
// 处理器方法
private Method method;
public InvocableHandlerMethod(Object handler, Method method) {
this.handler = handler;
this.method = method;
}
public Object invoke(Object... args){
try {
return method.invoke(handler, args);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
public Method getMethod(){
return this.method;
}
}很简单,就是一个Method反射方法和一个对象bean,然后有一个invoke(Object... args)方法反射调用处理器方法。
然后,为了标注哪些方法是处理器方法,我们需要在方法上添加注解@RequestMapping,其中定义了该方法可以处理哪个请求:
java
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
// 请求方法
HttpMethod method() default HttpMethod.GET;
// 请求路径
String value();
}java
// 简单起见,这里只定义了两种HTTP请求方式
public enum HttpMethod {
GET("get"),
POST("post");
private String value;
public String getValue() {
return value;
}
private HttpMethod(String value){
this.value = value;
}
}最后,我们在DispatcherServlet的构造方法中,传入处理器类(Controller),并从这些处理器类中找到处理器方法并缓存:
java
public class DispatcherServlet {
private HashMap<String, InvocableHandlerMethod> handlerMethodHashMap = new HashMap<>();
/**
* 构造方法,其中主要工作有缓存处理器方法
* @param controllers 处理器类
*/
public DispatcherServlet(Class... controllers) {
// 解析controller中的处理方法
initHandlerMethod(controllers);
}
private void initHandlerMethod(Class... controllers){
if(controllers != null){
for (Class controller : controllers) {
Method[] methods = controller.getMethods();
if(methods == null || methods.length == 0){
continue;
}
Object bean = null;
for (Method method : methods) {
// 判断方法上是不是有 @RequestMapping 注解
RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
if(requestMapping != null){
if(bean == null){
try {
bean = controller.getDeclaredConstructor().newInstance();
}catch (Exception e){
break;
}
}
// 缓存处理器方法,以HTTP请求方式+路径作为key,以InvocableHandlerMethod作为value
String key = String.format("%s %s",
requestMapping.method().getValue(),
requestMapping.value());
InvocableHandlerMethod invocableHandlerMethod = new InvocableHandlerMethod(bean, method);
handlerMethodHashMap.put(key, invocableHandlerMethod);
}
}
}
}
}
}3.2.2 doDispatch()方法
我们统一将请求分发到DispatcherServlet.doDispatch(HttpExchange exchange)中进行处理:
java
public static void main( String[] args ) throws IOException {
HttpServer httpServer = HttpServer.create(new InetSocketAddress(8888), 0);
DispatcherServlet dispatcherServlet = new DispatcherServlet(UserController.class);
// "/" 表示所有路径都会进入到doDispatch()方法
httpServer.createContext("/", exchange -> dispatcherServlet.doDispatch(exchange));
httpServer.start();
System.out.println("启动成功");
}在DispatcherServlet.doDispatch(HttpExchange exchange)中,主要完成以下内容:
java
public void doDispatch(HttpExchange exchange) throws IOException {
try{
// 找到处理器方法
// 解析参数
// 反射调用处理器方法
// 处理返回值
// 如有必要,渲染视图
}catch (Exception e){
// 异常处理
FrameworkException frameworkException = null;
if(!(e instanceof FrameworkException)){
frameworkException = new FrameworkException(500, e.getMessage());
}else{
frameworkException = (FrameworkException) e;
}
handleException(exchange, frameworkException);
}
}由于在上述过程可能会发生错误,所以先定义一个框架异常:
java
public class FrameworkException extends Exception{
private int code;
private String message;
public FrameworkException(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
@Override
public String getMessage() {
return message;
}
}然后统一处理异常方法:
java
private void handleException(HttpExchange exchange, FrameworkException exception){
try {
byte[] bytes = exception.getMessage().getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(exception.getCode(), bytes.length);
exchange.getResponseBody().write(bytes);
exchange.getResponseBody().flush();
exchange.getResponseBody().close();
} catch (IOException e) {
exchange.close();
}
}3.2.3 查找处理器方法
由于之前已经缓存了处理器方法,所以只需要根据缓存规则,从Map中查找即可:
java
private InvocableHandlerMethod getHandlerMethod(HttpExchange exchange){
String key = String.format("%s %s",
exchange.getRequestMethod().toLowerCase(),
exchange.getRequestURI().getRawPath()
);
InvocableHandlerMethod invocableHandlerMethod = handlerMethodHashMap.get(key);
return invocableHandlerMethod;
}3.2.4 解析参数
为了简单期间,假设要解析的参数上加了@RequestParam注解,并且只从请求参数中获取:
java
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER})
public @interface RequestParam {
String value();
}解析方法如下:
java
// 获取处理器方法的参数
private Object[] resolveArgs(HttpExchange exchange, InvocableHandlerMethod handlerMethod){
Parameter[] parameters = handlerMethod.getMethod().getParameters();
if(parameters == null || parameters.length == 0){
return null;
}
Object[] result = new Object[parameters.length];
Map<String, String> paramsFromRequest = resolveParamsFromRequest(exchange);
for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
RequestParam requestParam = parameter.getAnnotation(RequestParam.class);
if(requestParam != null){
String paramName = requestParam.value();
String v = paramsFromRequest.get(paramName);
result[i] = v;
}
}
return result;
}
// 从请求中获取请求参数
private Map<String, String> resolveParamsFromRequest(HttpExchange exchange){
Map<String, String> result = new HashMap<>();
String query = exchange.getRequestURI().getQuery();
if(query == null || query.isEmpty()){
return result;
}
String[] split = query.split("&");
for (String s : split) {
String[] kv = s.split("=");
if(kv.length == 2){
result.put(kv[0], kv[1]);
}
}
return result;
}3.2.5 反射调用
找到处理器方法,并且从请求中解析出实际的参数后,就可以反射调用处理器方法了:
java
// 找到处理器方法
InvocableHandlerMethod invocableHandlerMethod = getHandlerMethod(exchange);
if(invocableHandlerMethod == null){
throw new FrameworkException(404, "no mapping handler");
}
// 解析参数
Object[] args = resolveArgs(exchange, invocableHandlerMethod);
// 反射调用处理器方法
Object returnValue = invocableHandlerMethod.invoke(args);3.2.6 处理返回值
当拿到处理器方法返回值后,如何将返回值返回给前端,分为两阶段:
- 在前后端不分离的项目中,一般是返回视图,也就是HTML页面,之后浏览器就可以直接渲染了,那么就需要后端处理,将返回值放入HTML页面,涉及的技术有JSP、freemarker等;
- 在前后端分离项目中,一般是直接返回数据,需要进行序列化。在实际开发中,我们经常将Java对象转换为Json字符串,当然,也可以转换为XML或其他格式。
我们使用freemarker和jackson库,分别作为视图渲染和json序列化支持:
xml
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.32</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.19.1</version>
</dependency>这里我们将返回值统一处理为ModelAndView:
java
public class ModelAndView {
private String viewName;
private Map<String, String> model;
public String getViewName() {
return viewName;
}
public void setViewName(String viewName) {
this.viewName = viewName;
}
public Map<String, String> getModel() {
return model;
}
public void setModel(Map<String, String> model) {
this.model = model;
}
}如果返回的ModelAndView不为空,则表示需要进行渲染,返回HTML格式,如果返回的ModelAndView为空,则进行序列话,返回Json格式数据。
首先定义两个注解:
java
// 表示返回格式为 Json
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ResponseBody {
}java
// 表示返回格式为
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface View {
// 视图名
String name();
}当处理器方法上标注了@Requestbody注解时,表示返回Json;当处理器方法上标注了@View注解时,表示返回HTML;当什么注解都没标注时,默认返回HTML。
返回值处理方法如下:
java
private ModelAndView handleReturnValue(Object returnValue,
InvocableHandlerMethod handlerMethod,
HttpExchange exchange) throws IOException {
ResponseBody responseBody = handlerMethod.getMethod().getAnnotation(ResponseBody.class);
View view = handlerMethod.getMethod().getAnnotation(View.class);
if(responseBody == null || view != null){
// 需要进行视图渲染
ModelAndView modelAndView = new ModelAndView();
// view不为空,则视图名从view中获取,否则默认取方法名
modelAndView.setViewName(view != null ? view.name() : handlerMethod.getMethod().getName());
modelAndView.setModel(getObjectPropertyValues(returnValue));
return modelAndView;
}else{
// 不需要进行视图渲染,直接写出响应
ObjectMapper objectMapper = new ObjectMapper();
String data = objectMapper.writeValueAsString(returnValue);
byte[] bytes = data.getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(200, bytes.length);
exchange.getResponseBody().write(bytes);
return null;
}
}
// 反射获取返回值属性值,并封装进map中
// 简单起见,这里假设object就是简单对象
private Map<String, String> getObjectPropertyValues(Object object){
Map<String, String> result = new HashMap<>();
Field[] declaredFields = object.getClass().getDeclaredFields();
for (Field field : declaredFields) {
field.setAccessible(true);
try {
String fieldName = field.getName();
Object value = field.get(object);
result.put(fieldName, value == null ? "" : value.toString());
} catch (IllegalAccessException e) {
}
}
return result;
}3.2.7 视图渲染
当ModelAndView对象不为空,需要进行视图渲染:
java
private void render(ModelAndView modelAndView, HttpExchange exchange) throws IOException, TemplateException {
// 创建 FreeMarker 配置实例
Configuration cfg = new Configuration(Configuration.VERSION_2_3_32);
// 设置模板文件所在的目录
URL resource = this.getClass().getClassLoader().getResource("templates");
cfg.setDirectoryForTemplateLoading(new File(resource.getPath()));
// 设置默认编码
cfg.setDefaultEncoding("UTF-8");
// 获取模板
Template template = cfg.getTemplate(modelAndView.getViewName() + ".ftl");
// 输出结果
Writer out = new StringWriter();
template.process(modelAndView.getModel(), out);
out.close();
// 将结果写入响应流
String data = out.toString();
byte[] bytes = data.getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(200, bytes.length);
exchange.getResponseHeaders().add("Content-Type", "text/html;charset=utf-8");
exchange.getResponseBody().write(bytes);
}3.2.8 总体
最后再看一下doDispatch()方法的总体逻辑:
java
public void doDispatch(HttpExchange exchange) throws IOException {
try{
// 找到处理器方法
InvocableHandlerMethod invocableHandlerMethod = getHandlerMethod(exchange);
if(invocableHandlerMethod == null){
throw new FrameworkException(404, "no mapping handler");
}
// 解析参数
Object[] args = resolveArgs(exchange, invocableHandlerMethod);
// 反射调用处理器方法
Object returnValue = invocableHandlerMethod.invoke(args);
// 处理返回值
ModelAndView modelAndView = handleReturnValue(returnValue, invocableHandlerMethod, exchange);
// 如有必要,渲染视图
if(modelAndView != null){
render(modelAndView, exchange);
}
}catch (Exception e){
// 异常处理
FrameworkException frameworkException = null;
if(!(e instanceof FrameworkException)){
frameworkException = new FrameworkException(500, e.getMessage());
}else{
frameworkException = (FrameworkException) e;
}
handleException(exchange, frameworkException);
}finally {
exchange.getResponseBody().flush();
exchange.getResponseBody().close();
}
}3.3 编写Controller类与测试
按照业务,编写Controller类:
java
public class UserController {
@RequestMapping(value = "/getUser1", method = HttpMethod.GET)
@View(name = "user")
public User getUser1(@RequestParam("name") String name){
return new User(1L, name, "广州");
}
@RequestMapping(value = "/getUser2", method = HttpMethod.GET)
@ResponseBody
public User getUser2(){
return new User(2L, "get user 2", "上海");
}
}然后将编写的Controller类传入DispatcherServlet:
java
public static void main( String[] args ) throws IOException {
HttpServer httpServer = HttpServer.create(new InetSocketAddress(8888), 0);
DispatcherServlet dispatcherServlet = new DispatcherServlet(UserController.class);
httpServer.createContext("/", exchange -> dispatcherServlet.doDispatch(exchange));
httpServer.start();
System.out.println("启动成功");
}启动项目,然后运行测试:


4. Spring MVC 组件分析
4.1 初始化方法
在DispatcherServlet的onRefresh()方法中,有一系列初始化方法:
java
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}从初始化方法中,主要的组件有HandlerMapping、HandlerAdapter和HandlerExceptionResolver。
4.2 HandlerMapping
在 Spring MVC 中,HandlerMapping 是 Spring 框架的核心组件之一,负责将客户端的 HTTP 请求映射到相应的处理程序(Handler,通常是控制器方法)。它在 DispatcherServlet 的请求处理流程中扮演重要角色,决定了请求应该由哪个控制器方法来处理。
就像我们自己实现的中的:
java
private HashMap<String, InvocableHandlerMethod> handlerMethodHashMap = new HashMap<>();常用的实现是RequestMappingHandlerMapping,处理 @RequestMapping 注解定义的控制器方法,也就是说根据 @RequestMapping 注解找到处理器方法并缓存,当请求到来时,根据@RequestMapping的 value(URL 路径)、method(HTTP 方法)、params、 headers 等进行请求匹配,找到处理器方法。
所以,在RequestMappingHandlerMapping中使用的关键类有两个:
RequestMappingInfo:封装@RequestMapping注解的元数据,用于匹配请求。内容有:
- Patterns:URL 路径模式(如 /user/{id})。
- Methods:HTTP 方法(如 GET、POST)。
- Params:请求参数条件(如 paramName=value)。
- Headers:请求头条件。
- Consumes:请求的 Content-Type(如 application/json)。
- Produces:响应的 Content-Type(如 application/json)。
HandlerMethod:表示控制器中的具体方法,包含方法本身、所属 Bean 及参数信息。方法对象(java.lang.reflect.Method)。
控制器实例(@Controller 或 @RestController 的 Bean)。
方法参数(如 @PathVariable、@RequestParam 等)。
这时一个接口,主要实现为
ServletInvocableHandlerMethod。在Spring MVC中,还实现了拦截器
HandlerInterceptor,用于在处理器方法前后进行执行。
所以,当一个请求到来时,不仅会匹配到处理器方法,还可能匹配到多个拦截器,一个处理器和多个拦截器组成HandlerExecutionChain:
java
public class HandlerExecutionChain {
// 处理器方法
private final Object handler;
// 多个拦截器
private final List<HandlerInterceptor> interceptorList = new ArrayList<>();
// 当前执行的拦截器
private int interceptorIndex = -1;
}在RequestMappingHandlerMapping中的结构(不准确)大致如下:
java
class RequestMappingHandlerMapping{
Map<RequestMappingInfo, HandlerExecutionChain> handlerMapping ;
}测试HandlerMapping代码如下:
java
public static void main(String[] args) throws Exception {
// 创建容器
AnnotationConfigServletWebServerApplicationContext applicationContext =
new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
// 创建RequestMappingHandlerMapping
RequestMappingHandlerMapping requestMappingHandlerMapping = new RequestMappingHandlerMapping();
requestMappingHandlerMapping.setApplicationContext(applicationContext);
TestController controller = new TestController();
// 添加第一个匹配项
RequestMappingInfo requestMappingInfo1 = RequestMappingInfo
.paths("/test1")
.methods(RequestMethod.GET)
.build();
requestMappingHandlerMapping.registerMapping(
requestMappingInfo1,
controller,
TestController.class.getMethod("test1")
);
// 添加第二个匹配项
RequestMappingInfo requestMappingInfo2 = RequestMappingInfo
.paths("/test2")
.methods(RequestMethod.POST)
.build();
requestMappingHandlerMapping.registerMapping(
requestMappingInfo2,
controller,
TestController.class.getMethod("test2")
);
// MockHttpServletRequest 是 spring-test中的类,模拟HTTP请求
MockHttpServletRequest httpServletRequest =
new MockHttpServletRequest("GET", "/test1");
// 通过请求获取匹配的处理器方法和拦截器
HandlerExecutionChain handler = requestMappingHandlerMapping.getHandler(httpServletRequest);
System.out.println(handler);
}java
@Configuration
public class WebConfig {
@Bean
public ServletWebServerFactory webServerFactory(){
return new TomcatServletWebServerFactory();
}
@Bean
public DispatcherServlet dispatcherServlet(){
return new DispatcherServlet();
}
@Bean
public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet){
return new DispatcherServletRegistrationBean(dispatcherServlet,"/");
}
}java
class TestController{
public String test1(){
return "test1";
}
public String test2(){
return "test2";
}
}结果如下:
java
HandlerExecutionChain with [com.lee.springmvc.TestController#test1()] and 0 interceptors可以看到,根据HTTP请求匹配到了处理器方法。
4.3 HandlerAdapter
在 Spring MVC 中,HandlerAdapter 是 Spring 框架的核心组件之一,负责执行由 HandlerMapping 匹配到的处理程序(Handler),并将执行结果转换为 ModelAndView 对象。
为什么要专门引入HandlerAdapter?我认为有两点原因:
- 执行处理程序逻辑复杂,包括解析参数、类型转换、处理返回值等,为了解耦;
- 在Spring MVC中,处理程序不仅仅只是由
@RequestMapping标注的方法,还可能实现特定接口,所以执行处理程序的逻辑不同;
由于执行处理程序包括解析参数和处理返回值,所以HandlerAdapter依赖两组组件:
HandlerMethodArgumentResolverComposite:其中聚合了多个HandlerMethodArgumentResolver,用于解析参数;HandlerMethodReturnValueHandlerComposite:其中聚合了HandlerMethodReturnValueHandler,用于处理返回值;
用于调用@RequestMapping标注的处理器方法的是RequestMappingHandlerAdapter,使用案例如下:
java
public static void main(String[] args) throws Exception {
// 创建容器
AnnotationConfigServletWebServerApplicationContext applicationContext =
new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
// 创建RequestMappingHandlerMapping
RequestMappingHandlerMapping requestMappingHandlerMapping = new RequestMappingHandlerMapping();
requestMappingHandlerMapping.setApplicationContext(applicationContext);
TestController controller = new TestController();
// 添加第一个匹配项
RequestMappingInfo requestMappingInfo1 = RequestMappingInfo
.paths("/test1")
.methods(RequestMethod.GET)
.params("name")
.build();
requestMappingHandlerMapping.registerMapping(
requestMappingInfo1,
controller,
TestController.class.getMethod("test1", String.class)
);
// 添加第二个匹配项
RequestMappingInfo requestMappingInfo2 = RequestMappingInfo
.paths("/test2")
.methods(RequestMethod.POST)
.build();
requestMappingHandlerMapping.registerMapping(
requestMappingInfo2,
controller,
TestController.class.getMethod("test2")
);
// MockHttpServletRequest 是 spring-test中的类,模拟HTTP请求
MockHttpServletRequest httpServletRequest =
new MockHttpServletRequest("GET", "/test1");
httpServletRequest.addParameter("name","zs");
MockHttpServletResponse httpServletResponse = new MockHttpServletResponse();
// 通过请求获取匹配的处理器方法和拦截器
HandlerExecutionChain handler = requestMappingHandlerMapping.getHandler(httpServletRequest);
// 创建RequestMappingHandlerAdapter,执行处理器方法
RequestMappingHandlerAdapter requestMappingHandlerAdapter = new RequestMappingHandlerAdapter();
requestMappingHandlerAdapter.setApplicationContext(applicationContext);
requestMappingHandlerAdapter.afterPropertiesSet();
ModelAndView modelAndView = requestMappingHandlerAdapter.handle(httpServletRequest, httpServletResponse, handler.getHandler());
System.out.println(httpServletResponse.getContentAsString());
System.out.println(modelAndView);
}java
class TestController{
@ResponseBody
public String test1(String name){
return "test1 " + name;
}
public String test2(){
return "test2";
}
}结果如下:
txt
test1 zs
null如果在处理器方法上去除@ResponseBody,结果如下:
txt
ModelAndView [view="test1 zs"; model={}]4.4 HandlerExceptionResolver
在Spring MVC中,通过HandlerAdapter执行处理方法,最终调用DispatcherServlet中的processDispatchResult()方法,该方法用于处理异常或渲染视图:
TIP
对于没有异常和不需要渲染视图的情况,由返回值处理器已经直接写入了HttpServletResponse,所以在这个方法中不用管。
java
private void processDispatchResult(
HttpServletRequest request, // 请求
HttpServletResponse response, // 响应
@Nullable HandlerExecutionChain mappedHandler, // 执行链
@Nullable ModelAndView mv, // 处理结果,有可能为null
@Nullable Exception exception // 异常
) throws Exception {
boolean errorView = false;
// 异常不为空,需要处理异常
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException mavDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = mavDefiningException.getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// ModelAndView不为空,需要渲染视图
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
return;
}
if (mappedHandler != null) {
// 渲染视图完成后,调用拦截器的AfterCompletion方法
mappedHandler.triggerAfterCompletion(request, response, null);
}
}在processHandlerException()中,主要逻辑如下,调用异常处理器逐一处理异常,并返回异常视图:
java
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
exMv = resolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}4.5 渲染
在DispatcherServlet中,调用render(mv, request, response);进行视图渲染,主要逻辑分为两部分:
- 如果
ModelAndView中只有视图名称,则通过ViewResolvers解析视图,返回View对象;如果ModelAndView中直接包含View对象,则直接获取; - 调用
View.render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)方法进行渲染;
在Spring MVC中,视图解析器默认为InternalResourceViewResolver,会将逻辑视图名称解析为实际的 JSP 文件路径(如 /WEB-INF/views/home.jsp),返回为InternalResourceView对象。
Spring MVC 的InternalResourceView 使用 RequestDispatcher.forward()将请求转发到 JSP 文件的路径。
转发的请求到达Servlet容器(如Tomcat)时,由 Web 容器的 JSP 引擎(如 Tomcat 的 Jasper)在服务器端完成渲染。
所以整个流程如下:
txt
控制器返回视图名称 → InternalResourceViewResolver 解析 → InternalResourceView 转发 → JSP 引擎渲染 → 返回 HTML由于我们不是在Tomcat环境下,所以我们这里使用freemarker实验渲染功能。
首先引入freemarker依赖库:
java
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>然后在配置文件中加入freemarker视图解析器和相关配置类:
java
@Configuration
public class WebConfig {
@Bean
public ViewResolver viewResolver(){
// 设置模板前缀和后缀
return new FreeMarkerViewResolver("/WEB-INF/views/",".ftl");
}
@Bean
public FreeMarkerConfigurer freeMarkerConfigurer(){
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
URL resource = this.getClass().getClassLoader().getResource("");
// 设置模板加载器,ClassTemplateLoader会从classpath路径下加载模板
configurer.setPreTemplateLoaders(new ClassTemplateLoader());
// 设置模板加载路径
configurer.setTemplateLoaderPath(resource.getPath());
// 设置模板编码
configurer.setDefaultEncoding("UTF-8");
return configurer;
}
}然后,在resources/WEB-INF/views/目录下编写模板:
java
<html>
<body>
<h1>hello </h1>
<#if user?? && user.name??>
<h3>用户名: ${user.name}</h3>
<#else>
用户不存在或姓名为空
</#if>
<#if user?? && user.address??>
<h3>家乡: ${user.address}</h3>
<#else>
家乡为空
</#if>
</body>
</html>最后,使用模板解析器渲染ModelAndView对象:
java
public class App {
public static void main(String[] args) throws Exception {
// 创建容器
AnnotationConfigServletWebServerApplicationContext applicationContext =
new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
// 创建RequestMappingHandlerMapping
RequestMappingHandlerMapping requestMappingHandlerMapping = new RequestMappingHandlerMapping();
requestMappingHandlerMapping.setApplicationContext(applicationContext);
TestController controller = new TestController();
// 添加第一个匹配项
RequestMappingInfo requestMappingInfo1 = RequestMappingInfo
.paths("/test1")
.methods(RequestMethod.GET)
.params("name")
.build();
requestMappingHandlerMapping.registerMapping(
requestMappingInfo1,
controller,
TestController.class.getMethod("test1", String.class)
);
// 添加第二个匹配项
RequestMappingInfo requestMappingInfo2 = RequestMappingInfo
.paths("/test2")
.methods(RequestMethod.POST)
.build();
requestMappingHandlerMapping.registerMapping(
requestMappingInfo2,
controller,
TestController.class.getMethod("test2")
);
// MockHttpServletRequest 是 spring-test中的类,模拟HTTP请求
MockHttpServletRequest httpServletRequest =
new MockHttpServletRequest("GET", "/test1");
httpServletRequest.addParameter("name","zs");
MockHttpServletResponse httpServletResponse = new MockHttpServletResponse();
// 通过请求获取匹配的处理器方法和拦截器
HandlerExecutionChain handler = requestMappingHandlerMapping.getHandler(httpServletRequest);
RequestMappingHandlerAdapter requestMappingHandlerAdapter = new RequestMappingHandlerAdapter();
requestMappingHandlerAdapter.setApplicationContext(applicationContext);
requestMappingHandlerAdapter.afterPropertiesSet();
ModelAndView modelAndView = requestMappingHandlerAdapter.handle(httpServletRequest, httpServletResponse, handler.getHandler());
// 从容器中获取视图解析器
ViewResolver viewResolver = applicationContext.getBean(ViewResolver.class);
// 获取视图
View view = viewResolver.resolveViewName(modelAndView.getViewName(), Locale.CHINA);
// 解析视图
view.render(modelAndView.getModel(), httpServletRequest, httpServletResponse);
// 输出结果
System.out.println(httpServletResponse.getContentAsString());
}
}
class TestController{
// 注意返回 ModelAndView 对象
public ModelAndView test1(String name){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("hello");
modelAndView.addAllObjects(Map.of("user", new User(name, "gz")));
return modelAndView;
}
public String test2(){
return "test2";
}
}结果如下:
java
<html>
<body>
<h1>hello </h1>
<h3>用户名: zs</h3>
<h3>家乡: gz</h3>
</body>
</html>解析成功。
4.6 doDispatch()方法
我们再来看看DispatcherServlet中的doDispatch()方法:
java
// 参数就是请求和响应
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 获取处理器方法
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 获取执行器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 应用拦截器链的preHandle()方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 调用处理器方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 处理默认视图名
applyDefaultViewName(processedRequest, mv);
// 应用拦截器链的postHandle()方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
// 上述执行过程中发生异常,保存在dispatchException中
dispatchException = ex;
}
catch (Throwable err) {
// 上述执行过程中发生异常,保存在dispatchException中
dispatchException = new ServletException("Handler dispatch failed: " + err, err);
}
// 最后处理,包括视图解析、异常处理、执行拦截器链的afterCompletion()方法
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new ServletException("Handler processing failed: " + err, err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
asyncManager.setMultipartRequestParsed(multipartRequestParsed);
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed || asyncManager.isMultipartRequestParsed()) {
cleanupMultipart(processedRequest);
}
}
}
}5. HandlerMapping 和 HandlerAdapter
本小节介绍其他的HanlerMapping 和 HandlerAdapter。
5.1 BeanNameUrlHandlerMapping
BeanNameUrlHandlerMapping用于将请求路径映射到名称为路径的Bean,也就是说,假设请求路径为/c1,容器中有名称为/c1的Bean,那么就由该Bean处理该请求。
注意,这种方式要求Bean的名称以/开始。
java
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ServletException {
// 创建容器
AnnotationConfigServletWebServerApplicationContext applicationContext =
new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
DispatcherServlet dispatcherServlet = applicationContext.getBean(DispatcherServlet.class);
// 为了触发 DispatcherServlet.onRefresh() 方法,加载 BeanNameUrlHandlerMapping
dispatcherServlet.onApplicationEvent(new ContextRefreshedEvent(applicationContext));
// MockHttpServletRequest 是 spring-test中的类,模拟HTTP请求
MockHttpServletRequest httpServletRequest =
new MockHttpServletRequest("GET", "/c1");
// 反射调用获取 getHandler方法
Method getHandler = DispatcherServlet.class.getDeclaredMethod("getHandler", HttpServletRequest.class);
getHandler.setAccessible(true);
// 获取匹配的处理器和拦截器
HandlerExecutionChain handlerExecutionChain = (HandlerExecutionChain) getHandler.invoke(dispatcherServlet, httpServletRequest);
System.out.println(handlerExecutionChain);
}java
@Configuration
public class WebConfig {
@Bean("/c1")
public Object object(){
return new Object();
}
}结果为:
java
HandlerExecutionChain with [java.lang.Object@55caeb35] and 1 interceptors可以发现,匹配到的处理器就是Object,就是我们放入容器中的名称为/c1的Bean。
5.2 HttpRequestHandlerAdapter
从上一小节我们已经找到了处理器,但是,仔细想想,这个处理器可以处理请求吗?我们应该调用哪个方法来处理请求呢?
显然,我们找到的处理器并不能处理请求。那要如何才能处理请求呢?可以让我们的处理器类实现HttpRequestHandler接口:
java
@FunctionalInterface
public interface HttpRequestHandler {
void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException;
}处理器实现HttpRequestHandler接口:
java
@Bean("/c1")
public Object object(){
return new HttpRequestHandler() {
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().write("hello world");
}
};
}之后,我们找到的处理器,就可以判断其是否属于HttpRequesthandler类,如果是,就可以调用handleRequest()处理请求了:
java
// 获取匹配的处理器和拦截器
HandlerExecutionChain handlerExecutionChain = (HandlerExecutionChain) getHandler.invoke(dispatcherServlet, httpServletRequest);
Object handler = handlerExecutionChain.getHandler();
if(handler instanceof HttpRequestHandler httpRequestHandler){
MockHttpServletResponse response = new MockHttpServletResponse();
httpRequestHandler.handleRequest(httpServletRequest, response);
System.out.println(response.getContentAsString());
}Spring MVC将上述判断和调用handleRequest()的流程封装在了HttpRequestHandlerAdapter中:
java
public class HttpRequestHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return (handler instanceof HttpRequestHandler);
}
@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
((HttpRequestHandler) handler).handleRequest(request, response);
return null;
}
@Override
@SuppressWarnings("deprecation")
public long getLastModified(HttpServletRequest request, Object handler) {
if (handler instanceof LastModified lastModified) {
return lastModified.getLastModified(request);
}
return -1L;
}
}所以,只需要我们的处理器实现了HttpRequestHandler接口,那么Spring MVC就能帮我们调用方法处理请求。
5.3 SimpleControllerHandlerAdapter
除了实现HttpRequestHandler接口,我们的处理器也可以实现Controller接口来处理请求:
java
@FunctionalInterface
public interface Controller {
@Nullable
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
Controller和HttpRequestHandler最大的不同是返回值
而对于实现了Controller的处理器的支持,Spring MVC 提供了SimpleControllerHandlerAdapter:
java
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return (handler instanceof Controller);
}
@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return ((Controller) handler).handleRequest(request, response);
}
@Override
@SuppressWarnings("deprecation")
public long getLastModified(HttpServletRequest request, Object handler) {
if (handler instanceof LastModified lastModified) {
return lastModified.getLastModified(request);
}
return -1L;
}
}5.4 RouterFunctionMapping 和 HandlerFunctionAdapter
我们也可以同时声明请求的路径和与之匹配的处理方法,使用RouterFunction:
java
@Bean
public RouterFunction routerFunction(){
return RouterFunctions.route(
RequestPredicates.GET("/r1"), // 匹配 GET /r1 请求
new HandlerFunction<ServerResponse>() {
// 处理方法
@Override
public ServerResponse handle(ServerRequest request) throws Exception {
ServerResponse serverResponse = ServerResponse.ok().body("hello router function");
return serverResponse;
}
}
);
}支持这一功能的是RouterFunctionMapping 和 HandlerFunctionAdapter:
RouterFunctionMapping:用来存储匹配关系;HandlerFunctionAdapter:用来处理请求;
例如:
java
// MockHttpServletRequest 是 spring-test中的类,模拟HTTP请求
MockHttpServletRequest httpServletRequest =
new MockHttpServletRequest("GET", "/r1");
// 反射调用获取 getHandler方法
Method getHandler = DispatcherServlet.class.getDeclaredMethod("getHandler", HttpServletRequest.class);
getHandler.setAccessible(true);
// 获取匹配的处理器和拦截器
HandlerExecutionChain handlerExecutionChain = (HandlerExecutionChain) getHandler.invoke(dispatcherServlet, httpServletRequest);
Object handler = handlerExecutionChain.getHandler();
HandlerFunctionAdapter handlerFunctionAdapter = new HandlerFunctionAdapter();
if(handlerFunctionAdapter.supports(handler)){
MockHttpServletResponse response = new MockHttpServletResponse();
handlerFunctionAdapter.handle(httpServletRequest, response, handler);
System.out.println(response.getContentAsString());
}结果:
java
hello router function5.5 ResourceHttpRequestHandler
ResourceHttpRequestHandler其本质就是一个请求处理器,其实现了HttpRequestHandler接口,专用于处理静态资源的,我们只需要将其注入到容器中,配合BeanNameUrlHandlerMapping,就能发挥作用。
java
@Bean("/img/**")
public ResourceHttpRequestHandler resourceHttpRequestHandler(){
ResourceHttpRequestHandler resourceHttpRequestHandler = new ResourceHttpRequestHandler();
// 设置静态资源路径
resourceHttpRequestHandler.setLocations(List.of(new ClassPathResource("/WEB-INF/img/")));
return resourceHttpRequestHandler;
}由于有BeanNameUrlHandlerMapping,所以当请求到来时,如果是/img/的请求,则会找到ResourceHttpRequestHandler处理器进行处理,该处理器就会去设定的资源路径下找到相应资源,返回给前端。

为了更高效使用ResourceHttpRequestHandler,可以做如下优化:
java
@Bean("/img/**")
public ResourceHttpRequestHandler resourceHttpRequestHandler(){
ResourceHttpRequestHandler resourceHttpRequestHandler = new ResourceHttpRequestHandler();
// 设置静态资源路径
resourceHttpRequestHandler.setLocations(List.of(new ClassPathResource("/WEB-INF/img/")));
resourceHttpRequestHandler.setResourceResolvers(List.of(
// 从缓存中获取资源
new CachingResourceResolver(new ConcurrentMapCache("cache")),
// 压缩资源支持
new EncodedResourceResolver(),
// 根据路径查找资源
new PathResourceResolver()
));
return resourceHttpRequestHandler;
}6.流程总结
Spring MVC 提供了DispatcherServlet,它使用的是标准Servlet技术
路径:默认映射路径为
/,即会匹配到所有请求URL,可以作为请求的统一入口,也被称为前控制器。例外:JSP不会匹配到DispatcherServlet,因为Tomcat拦截处理了;
创建:在Spring Boot中,由
DispatcherServletAutoConfiguration这个自动配置类提供DispatcherServlet这个组件;初始化:DispatcherServlet初始化时会优先到容器中寻找各种组件,作为它的成员变量:
- HandlerMapping:初始化时记录映射关系,即请求由哪个处理器处理;
- HandlerAdapter:初始化时准备参数解析器、返回值处理器、消息转换器;
- HandlerExceptionResolver:初始化时准备参数解析器、返回值处理器、消息转换器;
- ViewResolver:视图解析器
如果在容器中没有找到,那么会使用默认配置,在
org/springframework/web/servlet/DispatcherServlet.properties中定义
当请求来时,DispatcherServlet会利用HandlerMapping进行匹配,找到对应的处理器:
- 处理器对象会和拦截器一起返回给DispatcherServlet,结合在一起称为 HandlerExecutionChain 对象;
DispatherServlet接下来开始处理请求:
- 首先调用拦截器的 preHandle()方法
- 然后查找可以调用处理器方法的 HandlerAdapter,由HandlerAdapter调用处理器方法:
- 使用HandlerMethodArgumentResolver获取参数;
- 调用处理器方法HandlerMethod;
- 使用HandlerMethodReturnValueHandler处理返回值
- 如果处理后的返回值ModelAndView为null,则不走视图解析及渲染流程;
- 如果处理后的返回值ModelAndView为null,则表示直接将数据写入到响应Response中了;
- 注意:在执行处理器方法时,会涉及 @ControllerAdvice 增强
- 然后调用拦截器的 postHandle()方法
- 如有必要,进行视图解析与渲染
- 如果前面发生了异常,则进入异常处理流程;
- 如果没有发生异常,并且需要进行渲染,则进入渲染流程;
- 最后,执行拦截器的 afterCompletion()方法