深入理解 Spring MVC:DispatcherServlet 与视图解析机制

java 复制代码
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Locale;
import java.util.Map;

// 继承自 FrameworkServlet 的 DispatcherServlet 类,作为前端控制器
public class DispatcherServlet extends FrameworkServlet {

    // 前端控制器的核心方法,处理请求,返回视图,渲染视图,都是在这个方法中完成的。
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 假设这里有对请求进行预处理
        HttpServletRequest processedRequest = request;

        // 模拟处理器适配器和映射处理器
        HandlerAdapter ha = new SimpleHandlerAdapter();
        HandlerExecutionChain mappedHandler = new HandlerExecutionChain(new SimpleController());

        // 根据请求路径调用映射的处理器方法,处理器方法执行结束之后,返回逻辑视图名称
        // 返回逻辑视图名称之后,DispatcherServlet会将 逻辑视图名称ViewName + Model,将其封装为ModelAndView对象。
        ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

        // 这行代码的作用是处理视图
        processDispatchResult(processedRequest, response, mappedHandler, mv, null);
    }

    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
                                       HandlerExecutionChain mappedHandler, ModelAndView mv,
                                       Exception dispatchException) throws Exception {
        // 渲染页面(将模板字符串转换成html代码响应到浏览器)
        render(mv, request, response);
    }

    protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
        String viewName = mv.getViewName();
        // 这个方法的作用是将 逻辑视图名称 转换成 物理视图名称 ,并且最终返回视图对象View
        View view = resolveViewName(viewName, mv.getModelInternal(), Locale.getDefault(), request);

        // 真正的将模板字符串转换成HTML代码,并且将HTML代码响应给浏览器。(真正的渲染。)
        view.render(mv.getModelInternal(), request, response);
    }

    protected View resolveViewName(String viewName, Map<String, Object> model,
                                   Locale locale, HttpServletRequest request) throws Exception {
        // 其实这一行代码才是真正起作用的:将 逻辑视图名称 转换成 物理视图名称 ,并且最终返回视图对象View
        ViewResolver viewResolver = new SimpleViewResolver();
        // 如果使用的是Thymeleaf,那么返回的视图对象:ThymeleafView对象。
        View view = viewResolver.resolveViewName(viewName, locale);

        return view;
    }
}

// 处理器适配器接口
interface HandlerAdapter {
    ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
}

// 简单处理器适配器实现
class SimpleHandlerAdapter implements HandlerAdapter {
    @Override
    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof SimpleController) {
            SimpleController controller = (SimpleController) handler;
            return controller.handleRequest(request, response);
        }
        return null;
    }
}

// 处理器执行链类
class HandlerExecutionChain {
    private final Object handler;

    public HandlerExecutionChain(Object handler) {
        this.handler = handler;
    }

    public Object getHandler() {
        return handler;
    }
}

// 简单控制器类
class SimpleController {
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 模拟返回逻辑视图名称和模型
        return new ModelAndView("testView", null);
    }
}

// 模型和视图类
class ModelAndView {
    private final String viewName;
    private final Map<String, Object> model;

    public ModelAndView(String viewName, Map<String, Object> model) {
        this.viewName = viewName;
        this.model = model;
    }

    public String getViewName() {
        return viewName;
    }

    public Map<String, Object> getModelInternal() {
        return model;
    }
}

// 这是一个接口(负责视图解析的)
interface ViewResolver {
    // 如果使用Thymeleaf,那么该接口的实现类就是:ThymeleafViewResolver
    // 这个方法就是将:逻辑视图名称 转换成 物理视图名称 ,并且最终返回视图对象View
    View resolveViewName(String viewName, Locale locale) throws Exception;
}

// 简单视图解析器实现
class SimpleViewResolver implements ViewResolver {
    @Override
    public View resolveViewName(String viewName, Locale locale) throws Exception {
        return new SimpleView();
    }
}

// 这是一个接口(负责将 模板字符串 转换成HTML代码,响应给浏览器)
interface View {
    void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
            throws Exception;
}

// 简单视图实现
class SimpleView implements View {
    @Override
    public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
        response.setContentType("text/html;charset=UTF-8");
        response.getWriter().write("<html><body><h1>Simple View Rendered</h1></body></html>");
    }
}

// 模拟的 FrameworkServlet 类
abstract class FrameworkServlet {
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 空实现,由子类实现具体逻辑
    }
}

代码功能概述

这段 Java 代码实现了一个简单的前端控制器 DispatcherServlet,它是整个 Web 请求处理流程的核心。其主要功能是接收客户端请求,根据请求路径调用对应的处理器方法,获取逻辑视图名称,将逻辑视图名称转换为物理视图名称,最终将模板字符串渲染成 HTML 代码并响应给客户端。同时,代码中定义了两个核心接口 ViewResolverView,分别负责视图解析和视图渲染。

代码解释

  1. DispatcherServlet :作为前端控制器,继承自 FrameworkServletdoDispatch 方法是核心方法,负责接收请求、调用处理器方法、获取逻辑视图名称并封装为 ModelAndView 对象,最后调用 processDispatchResult 方法处理视图。processDispatchResult 方法调用 render 方法进行视图渲染。render 方法通过 resolveViewName 方法将逻辑视图名称转换为物理视图名称并获取视图对象,最后调用视图对象的 render 方法进行实际渲染。
  2. ViewResolver 接口 :定义了 resolveViewName 方法,用于将逻辑视图名称转换为物理视图名称并返回视图对象。
  3. View 接口 :定义了 render 方法,用于将模板字符串转换为 HTML 代码并响应给浏览器。
  4. 其他辅助类 :为了使代码能够完整运行,添加了 HandlerAdapterHandlerExecutionChainSimpleControllerModelAndViewSimpleViewResolverSimpleView 等类,分别模拟处理器适配器、处理器执行链、控制器、模型和视图、视图解析器和视图的实现。

结论

如果你想实现自己的视图,至少需要编写两个类:一个类实现 ViewResolver 接口,实现其中的 resolveViewName 方法;另一个类实现 View 接口,实现其中的 render 方法。这样就可以自定义视图的解析和渲染逻辑。



深入理解 Spring MVC:DispatcherServlet 与视图解析机制​



在 Spring MVC 框架中,DispatcherServlet作为前端控制器,是整个请求处理流程的核心枢纽。它负责协调各个组件完成请求接收、处理器调用、视图解析和页面渲染等关键步骤。本文将通过源码级解析,带你理解 DispatcherServlet 的核心逻辑,并揭秘视图解析的两大关键接口ViewResolver与View的工作原理。​

一、DispatcherServlet 核心流程:请求处理的 "总指挥"​

DispatcherServlet继承自FrameworkServlet,其核心逻辑集中在doDispatch方法中,该方法串联了从请求接收到视图渲染的完整流程:​

1. 处理器调用与 ModelAndView 生成

java 复制代码
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // 1. 预处理请求(示例代码简化处理)
    HttpServletRequest processedRequest = request;
    
    // 2. 模拟处理器适配与映射(实际由Spring容器管理)
    HandlerAdapter ha = new SimpleHandlerAdapter();
    HandlerExecutionChain mappedHandler = new HandlerExecutionChain(new SimpleController());
    
    // 3. 调用处理器方法,获取逻辑视图名+模型数据
    ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
    // 4. 处理视图渲染逻辑
    processDispatchResult(processedRequest, response, mappedHandler, mv, null);
}
  • 处理器适配:通过HandlerAdapter适配不同类型的处理器(如 @Controller 标注的控制器)
  • 关键产出:ModelAndView对象封装了逻辑视图名(如 "user/list")和模型数据(如 Map 类型的参数)

2. 视图处理的关键步骤

java 复制代码
private void processDispatchResult(...) {
    // 核心:调用render方法进行视图渲染
    render(mv, request, response); 
}

protected void render(ModelAndView mv, ...) throws Exception {
    // 1. 解析逻辑视图名 -> 物理视图对象
    View view = resolveViewName(mv.getViewName(), ...); 
    // 2. 执行实际的视图渲染
    view.render(mv.getModelInternal(), request, response); 
}

这里体现了 "两步走" 的视图处理逻辑:**先解析视图类型,再执行渲染。**接下来我们深入这两个核心环节。​

二、两大核心接口:视图解析的 "左右护法"​

1. ViewResolver:逻辑视图到物理视图的桥梁

java 复制代码
public interface ViewResolver {
    // 将逻辑视图名(如"home")转换为具体的View实现类
    View resolveViewName(String viewName, Locale locale) throws Exception;
}
  • 典型实现:Thymeleaf 对应ThymeleafViewResolver,FreeMarker 对应FreeMarkerViewResolver
  • 核心逻辑:根据视图名拼接物理路径(如添加前缀 "/WEB-INF/views/" 和后缀 ".html"),并创建对应的 View 实例

2. View:模板渲染的实际执行者

java 复制代码
public interface View {
    // 将模型数据渲染为HTML并响应给浏览器
    void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
}
  • 核心职责:
  1. 加载模板文件(如 Thymeleaf 模板)
  1. 填充模型数据到模板
  1. 生成最终的 HTML 内容并写入响应流

三、完整代码示例:模拟 Spring MVC 核心组件​

为了完整呈现流程,我们补充必要的辅助类(实际 Spring 中由框架提供):​

1. 处理器相关组件

java 复制代码
// 处理器适配器(模拟Spring的HandlerAdapter)
interface HandlerAdapter {
    ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
}

// 简单控制器(返回逻辑视图名"testView")
class SimpleController {
    public ModelAndView handleRequest(...) {
        return new ModelAndView("testView", null); // 无模型数据
    }
}

// 模型与视图封装类
class ModelAndView {
    private final String viewName;
    private final Map<String, Object> model;
    // 构造器、getter方法省略
}

2. 自定义视图解析与渲染(简化实现)

java 复制代码
// 简单视图解析器(返回自定义View实例)
class SimpleViewResolver implements ViewResolver {
    @Override
    public View resolveViewName(String viewName, Locale locale) {
        return new SimpleView(); // 实际应根据视图名创建不同View
    }
}

// 简单视图实现(直接返回固定HTML)
class SimpleView implements View {
    @Override
    public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setContentType("text/html;charset=UTF-8");
        response.getWriter().write("<h1>Custom View Rendered!</h1>");
    }
}

四、如何实现自定义视图?两步走策略​

如果你需要集成新的视图技术(如自定义模板引擎),只需完成以下两步:​

1. 实现 ViewResolver 接口

java 复制代码
public class MyViewResolver implements ViewResolver {
    private String prefix = "/views/";
    private String suffix = ".mytpl";

    @Override
    public View resolveViewName(String viewName, Locale locale) {
        return new MyView(prefix + viewName + suffix); // 拼接物理路径
    }
}

2. 实现 View 接口

java 复制代码
public class MyView implements View {
    private String templatePath;

    public MyView(String templatePath) {
        this.templatePath = templatePath;
    }

    @Override
    public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 1. 读取模板文件
        String template = Files.readString(Paths.get(templatePath));
        // 2. 数据填充(假设模板语法为{{key}})
        String html = template.replaceAll("\\{\\{([^\\}]+)\\}\\}", 
            model.get("$1").toString());
        // 3. 输出响应
        response.getWriter().write(html);
    }
}

五、总结:理解框架设计的核心思想​

通过剖析 DispatcherServlet 的源码逻辑,我们可以提炼出 Spring MVC 的两大设计精髓:​

  1. 责任链模式:DispatcherServlet 作为核心协调者,将请求处理分解为处理器调用、视图解析、视图渲染等独立步骤,各组件通过接口解耦
  1. 策略模式:通过 ViewResolver 和 View 接口的不同实现,支持灵活切换视图技术(Thymeleaf/FreeMarker/ 自定义)

当我们需要扩展框架功能时(如支持新视图),只需遵循既定接口规范,即可无缝集成到现有流程中。这种 "约定大于配置" 的设计思想,正是 Spring 框架保持强大扩展性的关键所在。​

理解这些核心机制后,我们不仅能更熟练地使用 Spring MVC 的现有功能,还能在遇到复杂需求时,基于框架扩展点实现优雅的解决方案。下次当你在配置文件中写下ThymeleafViewResolver时,应该能清晰联想到背后整个视图解析与渲染的流程了吧?

相关推荐
武昌库里写JAVA30 分钟前
Java 设计模式
java·vue.js·spring boot·课程设计·宠物管理
钢铁男儿38 分钟前
Python 函数装饰器和闭包(闭包)
java·网络·python
Clf丶忆笙1 小时前
从零开始搭建第一个Spring Boot应用:从入门到精通
java·spring boot
东坡大表哥1 小时前
【Android】Android签名解析
android·java
杨不易呀1 小时前
Java面试:微服务与大数据场景下的技术挑战
java·大数据·微服务·面试·技术栈
magic 2451 小时前
SpringMVC——第三章:获取请求数据
java·数据库·springmvc
ABCDEEE72 小时前
民宿管理系统5
java
别催小唐敲代码2 小时前
解决跨域的4种方法
java·服务器·前端·json
何似在人间5753 小时前
LangChain4j +DeepSeek大模型应用开发——7 项目实战 创建硅谷小鹿
java·人工智能·ai·大模型开发