spring MVC执行流程

详细的项目结构

复制代码
src
├── main
│   ├── java
│   │   ├── com.example
│   │   │   ├── config
│   │   │   │   └── SpringMvcInitializer.java      // 配置 DispatcherServlet
│   │   │   │   └── SpringConfig.java             // Spring MVC 配置
│   │   │   ├── controller
│   │   │   │   └── UserController.java           // 控制器,返回 JSON
│   │   │   ├── service
│   │   │   │   └── UserService.java              // 服务层,处理业务逻辑
│   │   │   └── model
│   │   │   │   └── User.java                     // 数据模型
│   └── webapp
│       └── WEB-INF
│           └── web.xml(可选,可省略)
  • 注意 :我们不再需要 views 文件夹和 userInfo.jsp,因为不再渲染 HTML 页面。

详细代码实现(每个步骤对应之前的流程)

1. HTTP 请求(1. http请求)
  • 描述 :用户通过浏览器、Postman 或其他客户端发送 GET /user/info?id=1 到服务器,请求用户信息。
  • 细节 :请求可以是 HTTP GET 方法,URL 包含查询参数 id,期望返回 JSON 格式的数据。
  • 测试方式
    • 使用 Postman:设置请求方法为 GET,URL 为 http://localhost:8080/user/info?id=1,Headers 中可添加 Accept: application/json
    • 浏览器直接访问(需要确保服务器支持 JSON 响应)。
  • 通俗解释:就像你在餐厅点了一份外卖,期望收到菜的配方(JSON 数据)而不是直接上菜(HTML 页面)。

2. DispatcherServlet 接收请求(2. 寻找控制器)
  • 描述DispatcherServlet 作为 Spring MVC 的核心控制器,接收 HTTP 请求,并分发到合适的处理器。

  • 配置细节 :我们使用 AbstractAnnotationConfigDispatcherServletInitializer 替代传统 web.xml,确保 DispatcherServlet 加载 SpringConfig 配置。

  • 代码SpringMvcInitializer.java):

    java 复制代码
    package com.example.config;
    
    import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
    
    public class SpringMvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
        @Override
        protected Class<?>[] getRootConfigClasses() {
            // 根应用上下文配置(这里可以用于其他非 MVC 的配置,如数据源)
            return null;
        }
    
        @Override
        protected Class<?>[] getServletConfigClasses() {
            // 指定 Spring MVC 配置类
            return new Class<?>[] { SpringConfig.class };
        }
    
        @Override
        protected String[] getServletMappings() {
            // 所有请求(/)都由 DispatcherServlet 处理
            return new String[] { "/" };
        }
    }
  • 注意事项

    • getServletMappings 中的 "/" 表示拦截所有请求(包括静态资源),如果需要处理静态资源(如 CSS、JS),需要额外配置(见后文)。
    • 确保项目部署到支持 Servlet 3.0+ 的容器(如 Tomcat 8+)。
  • 通俗解释 :服务员(DispatcherServlet)在餐厅入口接单,准备分发到后厨(Controller)。


3. HandlerMapping 匹配处理器(3. 调用控制器)
  • 描述DispatcherServlet 调用 HandlerMapping(通常是 RequestMappingHandlerMapping),根据 URL /user/info 找到 UserControllergetUserInfo 方法。

  • 配置细节@EnableWebMvc 自动启用 RequestMappingHandlerMapping,无需额外配置。

  • 代码UserController.java):

    java 复制代码
    package com.example.controller;
    
    import com.example.model.User;
    import com.example.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController // 标记为 REST 控制器,自动返回 JSON,无需视图
    public class UserController {
        @Autowired
        private UserService userService;
    
        @GetMapping("/user/info") // 处理 GET 请求,路径为 /user/info
        public User getUserInfo(@RequestParam("id") int id) {
            // 调用服务层查询用户信息
            User user = userService.getUserById(id);
            return user; // 直接返回 User 对象,Spring 自动转换为 JSON
        }
    }
  • 注意事项

    • @RestController 结合 @GetMapping 简洁地定义了 REST API,@RequestParam 绑定 URL 参数 id
    • Spring 依赖 Jackson 库将 User 对象序列化为 JSON,确保项目有 jackson-databind 依赖。
  • 通俗解释 :服务员(DispatcherServlet)问前台(HandlerMapping):"这单谁做?"前台说:"交给这个厨师(UserController)处理,返回菜的配方(JSON)。"


4. Controller 处理请求并返回数据(4. 调用业务逻辑进行处理)
  • 描述UserController 调用 UserService 执行业务逻辑,模拟查询用户信息,返回 User 对象。

  • 代码UserService.javaUser.java):

    java 复制代码
    // User.java(模型类,优化为支持 JSON 序列化)
    package com.example.model;
    
    import com.fasterxml.jackson.annotation.JsonProperty; // 可选,用于自定义 JSON 字段名
    
    public class User {
        private String name;
        private int age;
    
        // 无参构造(Jackson 要求,用于反序列化)
        public User() {}
    
        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        @JsonProperty("name") // 可选,指定 JSON 字段名
        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
    
        @JsonProperty("age")
        public int getAge() { return age; }
        public void setAge(int age) { this.age = age; }
    
        @Override
        public String toString() {
            return "User{name='" + name + "', age=" + age + "}";
        }
    }
    
    // UserService.java(服务层,模拟业务逻辑)
    package com.example.service;
    
    import com.example.model.User;
    import org.springframework.stereotype.Service;
    
    @Service
    public class UserService {
        public User getUserById(int id) {
            // 模拟从数据库查询,实际项目中可能用 JPA、MyBatis 或 JDBC
            return new User("User" + id, 20 + id);
        }
    }
  • 注意事项

    • User 类添加了无参构造和 getter/setter 方法,以支持 Jackson 的 JSON 序列化/反序列化。
    • @JsonProperty 是可选的,用于自定义 JSON 字段名(如将 name 映射为 userName)。
    • @Service 注解使 UserService 成为 Spring Bean,可通过 @Autowired 自动注入。
  • 通俗解释 :厨师(Controller)叫助手(UserService)查菜谱(数据库),炒好一道菜(User 数据),打包成 JSON 配方返回。


5. DispatcherServlet 接收数据(5. 视图处理结果)
  • 描述DispatcherServlet 接收 User 对象,并通过 Spring 的 HttpMessageConverter(默认使用 MappingJackson2HttpMessageConverter)将其转换为 JSON 格式。
  • 细节 :Spring 自动检测返回的 User 对象类型,使用 Jackson 库序列化为 JSON,并设置 HTTP 响应头 Content-Type: application/json
  • 代码 :无额外代码,DispatcherServlet 内部处理。
  • 注意事项
    • 确保 pom.xml 中有 jackson-databind 依赖,否则 Spring 无法序列化 JSON。
    • 如果 JSON 格式需要自定义(如日期格式、忽略某些字段),可配置 ObjectMapper
  • 通俗解释 :服务员(DispatcherServlet)拿到菜的配方(User 对象),用打印机(HttpMessageConverter)打印成 JSON 格式。

6. 移除 ViewResolver(6. 视图模板解析)
  • 描述 :因为我们不再返回 HTML 页面,移除 ViewResolver 和 JSP 相关配置。

  • 代码SpringConfig.java):

    java 复制代码
    package com.example.config;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.EnableWebMvc;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    @Configuration
    @EnableWebMvc
    @ComponentScan(basePackages = "com.example")
    public class SpringConfig implements WebMvcConfigurer {
        // 移除 ViewResolver Bean,因为我们直接返回 JSON
        // 如果需要处理静态资源,可以在这里添加配置
        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
            registry.addResourceHandler("/resources/**")
                    .addResourceLocations("classpath:/static/");
        }
    }
  • 注意事项

    • @EnableWebMvc 启用了 Spring MVC 的所有功能,但默认禁用了 Spring Boot 的自动静态资源处理。如果需要静态资源(如 CSS、JS),需手动配置 addResourceHandlers
    • 移除 ViewResolver 后,Spring 专注于 REST API,不再处理视图模板。
  • 通俗解释:服务员发现不需要盘子(JSP),直接把菜的配方(JSON)打包送出。


7. 返回 JSON 数据(7. 数据直接返回)
  • 描述User 对象被序列化为 JSON,返回给客户端。最终响应为:

    json 复制代码
    {
        "name": "User1",
        "age": 21
    }
  • 细节

    • Spring 使用 MappingJackson2HttpMessageConverter 进行序列化。

    • 返回的 HTTP 状态码默认是 200 OK。

    • 如果需要自定义 JSON 格式(如日期、字段过滤),可以配置 ObjectMapper

      java 复制代码
      @Bean
      public ObjectMapper objectMapper() {
          ObjectMapper mapper = new ObjectMapper();
          mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd"));
          return mapper;
      }
  • 代码 :已在 UserController.java 中实现。

  • 通俗解释:服务员把菜的配方(JSON)直接交给送餐员(客户端),不需要装盘(HTML)。


8. 返回 HTTP 响应(8. http响应)
  • 描述:客户端(浏览器、Postman)收到 JSON 数据,可以解析或显示。
  • 细节
    • 响应头包括 Content-Type: application/json; charset=UTF-8
    • 如果客户端是 Postman,JSON 数据直接显示;如果是浏览器,可能需要插件(如 JSONView)格式化显示。
  • 测试验证
    1. 打开 Postman,设置请求方法为 GET,URL 为 http://localhost:8080/user/info?id=1

    2. 点击发送,查看响应:

      json 复制代码
      {
          "name": "User1",
          "age": 21
      }
    3. 确保服务正常运行,依赖配置正确。

  • 通俗解释:送餐员(客户端)收到菜的配方(JSON),可以直接查看或用在其他地方。

完整依赖配置(Maven 示例)

确保 pom.xml 包含以下依赖:

xml 复制代码
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.23</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.14.0</version>
    </dependency>
    <dependency>
        <groupId>jakarta.servlet</groupId>
        <artifactId>jakarta.servlet-api</artifactId>
        <version>6.0.0</version>
        <scope>provided</scope>
    </dependency>
</dependencies>
  • 说明jackson-databind 是 JSON 序列化/反序列化的核心库,确保版本兼容 Spring。

可能扩展和注意事项

  1. 参数验证

    • 可以使用 @Valid@NotNull 验证 id 参数:

      java 复制代码
      @GetMapping("/user/info")
      public User getUserInfo(@RequestParam @NotNull int id) {
          return userService.getUserById(id);
      }

      需要添加 hibernate-validator 依赖:

      xml 复制代码
      <dependency>
          <groupId>org.hibernate.validator</groupId>
          <artifactId>hibernate-validator</artifactId>
          <version>6.2.5.Final</version>
      </dependency>
  2. 异常处理

    • 可以使用 @ExceptionHandler@ControllerAdvice 处理异常:

      java 复制代码
      @ControllerAdvice
      public class GlobalExceptionHandler {
          @ExceptionHandler(Exception.class)
          @ResponseBody
          public Map<String, Object> handleException(Exception e) {
              Map<String, Object> result = new HashMap<>();
              result.put("error", "Internal Server Error");
              result.put("message", e.getMessage());
              return result;
          }
      }
  3. 跨域支持(CORS)

    • 如果前端在不同域名,需配置 CORS:

      java 复制代码
      @Configuration
      public class SpringConfig implements WebMvcConfigurer {
          @Override
          public void addCorsMappings(CorsRegistry registry) {
              registry.addMapping("/**")
                      .allowedOrigins("http://localhost:3000") // 允许的源
                      .allowedMethods("GET", "POST", "PUT", "DELETE") // 允许的方法
                      .allowedHeaders("*"); // 允许的头
          }
      }
  4. 性能优化

    • 可以使用 @Cacheable 缓存查询结果:

      java 复制代码
      @Service
      public class UserService {
          @Cacheable("users")
          public User getUserById(int id) {
              return new User("User" + id, 20 + id);
          }
      }

      需要添加 spring-boot-starter-cachespring-context-support 依赖。

  5. 静态资源处理

    • 如果需要静态资源(如 CSS、JS),在 SpringConfig 中配置:

      java 复制代码
      @Override
      public void addResourceHandlers(ResourceHandlerRegistry registry) {
          registry.addResourceHandler("/resources/**")
                  .addResourceLocations("classpath:/static/")
                  .setCachePeriod(3600); // 缓存 1 小时
      }

对应图中的流程变化(更详细)

  • 原图中的 ViewResolverView(HTML/FTL) 被移除,因为我们直接返回 JSON。
  • 新流程简化为:
    1. HTTP 请求 → DispatcherServlet
    2. DispatcherServletHandlerMappingUserController
    3. UserController 调用 UserService → 返回 User 对象
    4. DispatcherServlet 转换 User 为 JSON → 返回 HTTP 响应

运行和测试

  1. 运行环境

    • 使用 JDK 11 或更高版本。
    • 部署到 Tomcat 8+ 或运行 Spring Boot 项目。
    • 确保 Maven 依赖下载完整。
  2. 测试

    • 使用 Postman 测试 GET http://localhost:8080/user/info?id=1

    • 预期响应:

      json 复制代码
      {
          "name": "User1",
          "age": 21
      }
  3. 调试

    • 检查日志,确保 DispatcherServletHandlerMappingHttpMessageConverter 正常工作。
    • 如果 JSON 格式错误,检查 User 类是否有无参构造、getter/setter。

总结

通过上述详细修改,我们将 Spring MVC 从返回 JSP 页面改为返回 JSON 数据,适合 RESTful API 开发。我补充了技术细节(如 Jackson 配置、参数验证、异常处理等)和代码注释,确保每个步骤清晰易懂。

相关推荐
侠客行03178 小时前
Mybatis连接池实现及池化模式
java·mybatis·源码阅读
蛇皮划水怪8 小时前
深入浅出LangChain4J
java·langchain·llm
灰子学技术9 小时前
go response.Body.close()导致连接异常处理
开发语言·后端·golang
老毛肚9 小时前
MyBatis体系结构与工作原理 上篇
java·mybatis
风流倜傥唐伯虎10 小时前
Spring Boot Jar包生产级启停脚本
java·运维·spring boot
二十雨辰10 小时前
[python]-AI大模型
开发语言·人工智能·python
Yvonne爱编码10 小时前
JAVA数据结构 DAY6-栈和队列
java·开发语言·数据结构·python
Re.不晚10 小时前
JAVA进阶之路——无奖问答挑战1
java·开发语言
你这个代码我看不懂10 小时前
@ConditionalOnProperty不直接使用松绑定规则
java·开发语言
pas13610 小时前
41-parse的实现原理&有限状态机
开发语言·前端·javascript