手写Spring框架(3):实现MVC

实现WebMVC

对于一个Web应用程序来说,启动时,应用程序本身只是一个war包,并没有main()方法,因此,启动时执行的是Server的main()方法。以Tomcat服务器为例:

  1. 启动服务器,即执行Tomcat的main()方法;
  2. Tomcat根据配置或自动检测到一个xyz.war包后,为这个xyz.war应用程序创建Servlet容器;
  3. Tomcat继续查找xyz.war定义的Servlet、Filter和Listener组件,按顺序实例化每个组件(Listener最先被实例化,然后是Filter,最后是Servlet);
  4. 用户发送HTTP请求,Tomcat收到请求后,转发给Servlet容器,容器根据应用程序定义的映射,把请求发送个若干Filter和一个Servlet处理;
  5. 处理期间产生的事件则由Servlet容器自动调用Listener。

详细流程:Tomcat启动,创建ServletContext,解析web.xml文件,找到ServletContextListener,调用里面的方法

csharp 复制代码
public interface ServletContextListener {
    // Tomcat会在Web应用启动时自动调用
    void contextInitialized(ServletContextEvent sce);
    
    // Tomcat会在Web应用关闭时自动调用
    void contextDestroyed(ServletContextEvent sce);
}

在contextInitialized(ServletContextEvent sce);中向WebMvcConfiguration中注入servletContext,创建ApplicationContext(创建PropertyResolver),创建IOC容器,并注册拦截器和前端控制器(通过WebUtils实现具体逻辑)

ini 复制代码
public class ContextLoaderListener implements ServletContextListener {  
  
    final Logger logger = LoggerFactory.getLogger(getClass());  
  
    @Override  
    public void contextInitialized(ServletContextEvent sce) {  
        logger.info("init {}.", getClass().getName());  
        var servletContext = sce.getServletContext();  
        WebMvcConfiguration.setServletContext(servletContext);  
  
        var propertyResolver = WebUtils.createPropertyResolver();  
        String encoding = propertyResolver.getProperty("${summer.web.character-encoding:UTF-8}");  
        servletContext.setRequestCharacterEncoding(encoding);  
        servletContext.setResponseCharacterEncoding(encoding);  
  
        //扫描配置类,创建ApplicationContext,创建IOC容器  
        var applicationContext = createApplicationContext(servletContext.getInitParameter("configuration"), propertyResolver);  
        // register filters:  
        WebUtils.registerFilters(servletContext);  
        // register DispatcherServlet:  
        WebUtils.registerDispatcherServlet(servletContext, propertyResolver);  
  
        servletContext.setAttribute("applicationContext", applicationContext);  
    }  
  
    //创建 ApplicationContext    ApplicationContext createApplicationContext(String configClassName, PropertyResolver propertyResolver) {  
        logger.info("init ApplicationContext by configuration: {}", configClassName);  
        if (configClassName == null || configClassName.isEmpty()) {  
            throw new NestedRuntimeException("Cannot init ApplicationContext for missing init param name: configuration");  
        }  
        Class<?> configClass;  
        try {  
            configClass = Class.forName(configClassName);  
        } catch (ClassNotFoundException e) {  
            throw new NestedRuntimeException("Could not load class from init param 'configuration': " + configClassName);  
        }  
        return new AnnotationConfigApplicationContext(configClass, propertyResolver);  
    }  
}

前端控制器详细方法调用链

阶段1: 框架初始化 (init() 方法调用链)

text

  1. DispatcherServlet.init() ↓
  2. applicationContext.findBeanDefinitions(Object.class) ↓
  3. 遍历每个Bean定义: ↓
  4. beanClass.getAnnotation(Controller.class) / RestController.class ↓
  5. addController(false/true, beanName, beanInstance) ↓
  6. addMethods(isRest, name, instance, instance.getClass()) ↓
  7. 遍历类的所有方法: ↓
  8. method.getAnnotation(GetMapping.class) / PostMapping.class ↓
  9. checkMethod(method) ↓
  10. new Dispatcher("GET/POST", isRest, instance, method, urlPattern) ↓
  11. Dispatcher构造函数:
    • 设置isRest, isResponseBody, isVoid
    • PathUtils.compile(urlPattern) - 编译URL正则
    • 解析方法参数: new Param[...] ↓
  12. Param构造函数:
    • 解析参数注解: @PathVariable, @RequestParam, @RequestBody
    • 确定ParamType和参数名称
scala 复制代码
@Override  
public void init() throws ServletException {  
    logger.info("init {}.", getClass().getName());  
    // scan @Controller and @RestController:  
    for (var def : ((ConfigurableApplicationContext) this.applicationContext).findBeanDefinitions(Object.class)) {  
        Class<?> beanClass = def.getBeanClass();  
        Object bean = def.getRequiredInstance();  
        Controller controller = beanClass.getAnnotation(Controller.class);  
        RestController restController = beanClass.getAnnotation(RestController.class);  
        if (controller != null && restController != null) {  
            throw new ServletException("Found @Controller and @RestController on class: " + beanClass.getName());  
        }  
        if (controller != null) {  
            addController(false, def.getName(), bean);  
        }  
        if (restController != null) {  
            addController(true, def.getName(), bean);  
        }  
    }  
}

阶段2: 请求处理 (doGet() 方法调用链)

text

  1. DispatcherServlet.doGet(HttpServletRequest, HttpServletResponse) ↓
  2. 获取请求URL: req.getRequestURI() ↓
  3. 判断请求类型:
    • 如果是静态资源: doResource(url, req, resp)
    • 如果是动态请求: doService(req, resp, getDispatchers) ↓
  4. doService(req, resp, dispatchers):
    • 异常处理包装
    • 调用doService(url, req, resp, dispatchers) ↓
  5. doService(url, req, resp, dispatchers): ↓
  6. 遍历dispatchers列表: for (Dispatcher dispatcher : dispatchers) { ↓
  7. dispatcher.process(url, req, resp) ↓
  8. URL正则匹配: urlPattern.matcher(url).matches() ↓
  9. 如果匹配成功: ↓
  10. 构建参数数组 arguments[methodParameters.length] ↓
  11. 遍历每个参数: for (int i = 0; i < arguments.length; i++) { Param param = methodParameters[i]; ↓
复制代码
```arduino
    switch (param.paramType):
        ↓
```
复制代码
```csharp
    case PATH_VARIABLE:
        - matcher.group(param.name) 从URL提取值
        - convertToType() 类型转换
        ↓
```
复制代码
```css
    case REQUEST_PARAM:
        - request.getParameter(param.name) 获取参数
        - getOrDefault() 处理默认值
        - convertToType() 类型转换
        ↓
```
复制代码
```css
    case REQUEST_BODY:
        - request.getReader() 获取输入流
        - JsonUtils.readJson() JSON反序列化
        ↓
```
复制代码
```vbscript
    case SERVLET_VARIABLE:
        - 根据类型返回HttpServletRequest/Response等
}
↓
```
  1. 反射调用: handlerMethod.invoke(controller, arguments) ↓
  2. 控制器方法执行业务逻辑 ↓
  3. 返回Result(true, result) } ↓
  4. 结果处理: if (dispatcher.isRest) { // REST处理逻辑 } else { // MVC处理逻辑 }

具体场景分析

场景1: REST API 请求

css 复制代码
请求: GET /api/users/123

执行流程:
1. DispatcherServlet.doGet()
2. doService() -> 遍历getDispatchers
3. 找到匹配的Dispatcher (URL: /api/users/{id})
4. Dispatcher.process():
   - URL正则匹配: /api/users/123 匹配 /api/users/{id}
   - 构建参数: 
     * @PathVariable("id") Long id → convertToType(Long.class, "123") → 123L
5. 调用: userController.getUser(123L)
6. 返回: User对象
7. 结果处理 (isRest = true):
   - resp.setContentType("application/json")
   - JsonUtils.writeJson(pw, userObject)
8. 响应: {"id":123, "name":"John"}

场景2: MVC 页面请求

css 复制代码
请求: GET /user/profile

执行流程:
1. DispatcherServlet.doGet()
2. doService() -> 遍历getDispatchers  
3. 找到匹配的Dispatcher (URL: /user/profile)
4. Dispatcher.process():
   - 无参数,直接调用userController.profile()
5. 返回: ModelAndView("profile", model)
6. 结果处理 (isRest = false):
   - resp.setContentType("text/html")
   - viewResolver.render("profile", model, req, resp)
7. 响应: 渲染profile.ftl模板生成的HTML

场景3: 表单提交请求

sql 复制代码
请求: POST /user/update

执行流程:
1. DispatcherServlet.doPost()
2. doService() -> 遍历postDispatchers
3. 找到匹配的Dispatcher (URL: /user/update)
4. Dispatcher.process():
   - 构建参数: @RequestBody User user
   - JsonUtils.readJson(reader, User.class) → User对象
5. 调用: userController.update(user)
6. 返回: "redirect:/user/list"
7. 结果处理:
   - resp.sendRedirect("/user/list")

使用框架时:可以自定义过滤器,编写逻辑

相关知识

Record类型

Record 是 Java 14 引入、Java 16 正式化的特性,用于创建不可变的数据载体类

Record会自动生成:

  1. 私有final字段(与组件名称相同)

  2. 规范构造函数(canonical constructor)

  3. 访问器方法name()age(),不是getXxx)

  4. equals()、hashCode()、toString()

Function函数式编程

Function<T, R> 是Java函数式编程的核心接口,表示接收一个参数T,返回结果R的函数。

创建

typescript 复制代码
import java.util.function.Function;

// 1. Lambda表达式(最常用)
Function<String, Integer> stringToInt1 = s -> Integer.parseInt(s);

// 2. 方法引用
Function<String, Integer> stringToInt2 = Integer::parseInt;
Function<String, String> toUpperCase = String::toUpperCase;
Function<String, Integer> getLength = String::length;

// 3. 匿名内部类(传统方式)
Function<String, Integer> stringToInt3 = new Function<String, Integer>() {
    @Override
    public Integer apply(String s) {
        return Integer.parseInt(s);
    }
};

// 4. 已有方法赋值
Function<String, Integer> myMethod = this::parseStringToInt;
private Integer parseStringToInt(String s) {
    return Integer.parseInt(s);
}

使用apply()方法

ini 复制代码
// 创建Function
Function<String, Integer> stringToInt = s -> Integer.parseInt(s);

// 使用apply()调用
Integer number = stringToInt.apply("123");
System.out.println(number);  // 123

// 更多例子
Function<Integer, String> intToString = i -> "Number: " + i;
String result = intToString.apply(456);  // "Number: 456"

Function<String, String> process = s -> s.trim().toUpperCase();
String processed = process.apply("  hello  ");  // "HELLO"
相关推荐
麦兜*17 小时前
Spring Boot 整合 Apache Doris:实现海量数据实时OLAP分析实战
大数据·spring boot·后端·spring·apache
super_lzb17 小时前
mybatis拦截器ResultSetHandler详解
java·spring·mybatis·springboot
while(1){yan}17 小时前
拦截器(详解)
数据库·spring boot·spring·java-ee·拦截器
自在极意功。17 小时前
简单介绍SpringAOP
java·spring·aop思想
梵得儿SHI17 小时前
(第七篇)Spring AI 核心技术攻坚:国内模型深度集成与国产化 AI 应用实战指南
java·人工智能·spring·springai框架·国产化it生态·主流大模型的集成方案·麒麟系统部署调优
qq_124987075318 小时前
基于Spring Boot的电影票网上购票系统的设计与实现(源码+论文+部署+安装)
java·大数据·spring boot·后端·spring·毕业设计·计算机毕业设计
廋到被风吹走18 小时前
【Spring 】Spring Security深度解析:过滤器链、认证授权架构与现代集成方案
java·spring·架构
码农小卡拉18 小时前
Springboot “钩子”:@PostConstruct注解
java·spring boot·后端·spring·spring cloud