详细的项目结构
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 响应)。
- 使用 Postman:设置请求方法为 GET,URL 为
- 通俗解释:就像你在餐厅点了一份外卖,期望收到菜的配方(JSON 数据)而不是直接上菜(HTML 页面)。
2. DispatcherServlet 接收请求(2. 寻找控制器)
- 描述:
DispatcherServlet
作为 Spring MVC 的核心控制器,接收 HTTP 请求,并分发到合适的处理器。 - 配置细节:我们使用
AbstractAnnotationConfigDispatcherServletInitializer
替代传统web.xml
,确保DispatcherServlet
加载SpringConfig
配置。 - 代码(
SpringMvcInitializer.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
找到UserController
的getUserInfo
方法。 - 配置细节:
@EnableWebMvc
自动启用RequestMappingHandlerMapping
,无需额外配置。 - 代码(
UserController.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.java
和User.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
):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,返回给客户端。最终响应为:{ "name": "User1", "age": 21 }
- 细节:
- Spring 使用
MappingJackson2HttpMessageConverter
进行序列化。 - 返回的 HTTP 状态码默认是 200 OK。
- 如果需要自定义 JSON 格式(如日期、字段过滤),可以配置
ObjectMapper
:@Bean public ObjectMapper objectMapper() { ObjectMapper mapper = new ObjectMapper(); mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd")); return mapper; }
- Spring 使用
- 代码:已在
UserController.java
中实现。 - 通俗解释:服务员把菜的配方(JSON)直接交给送餐员(客户端),不需要装盘(HTML)。
8. 返回 HTTP 响应(8. http响应)
- 描述:客户端(浏览器、Postman)收到 JSON 数据,可以解析或显示。
- 细节:
- 响应头包括
Content-Type: application/json; charset=UTF-8
。 - 如果客户端是 Postman,JSON 数据直接显示;如果是浏览器,可能需要插件(如 JSONView)格式化显示。
- 响应头包括
- 测试验证:
- 打开 Postman,设置请求方法为 GET,URL 为
http://localhost:8080/user/info?id=1
。 - 点击发送,查看响应:
{ "name": "User1", "age": 21 }
- 确保服务正常运行,依赖配置正确。
- 打开 Postman,设置请求方法为 GET,URL 为
- 通俗解释:送餐员(客户端)收到菜的配方(JSON),可以直接查看或用在其他地方。
完整依赖配置(Maven 示例)
确保 pom.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。
可能扩展和注意事项
-
参数验证:
- 可以使用
@Valid
和@NotNull
验证id
参数:
需要添加@GetMapping("/user/info") public User getUserInfo(@RequestParam @NotNull int id) { return userService.getUserById(id); }
hibernate-validator
依赖:<dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>6.2.5.Final</version> </dependency>
- 可以使用
-
异常处理:
- 可以使用
@ExceptionHandler
或@ControllerAdvice
处理异常:@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; } }
- 可以使用
-
跨域支持(CORS):
- 如果前端在不同域名,需配置 CORS:
@Configuration public class SpringConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("http://localhost:3000") // 允许的源 .allowedMethods("GET", "POST", "PUT", "DELETE") // 允许的方法 .allowedHeaders("*"); // 允许的头 } }
- 如果前端在不同域名,需配置 CORS:
-
性能优化:
- 可以使用
@Cacheable
缓存查询结果:
需要添加@Service public class UserService { @Cacheable("users") public User getUserById(int id) { return new User("User" + id, 20 + id); } }
spring-boot-starter-cache
或spring-context-support
依赖。
- 可以使用
-
静态资源处理:
- 如果需要静态资源(如 CSS、JS),在
SpringConfig
中配置:@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/resources/**") .addResourceLocations("classpath:/static/") .setCachePeriod(3600); // 缓存 1 小时 }
- 如果需要静态资源(如 CSS、JS),在
对应图中的流程变化(更详细)
- 原图中的
ViewResolver
和View(HTML/FTL)
被移除,因为我们直接返回 JSON。 - 新流程简化为:
- HTTP 请求 →
DispatcherServlet
DispatcherServlet
找HandlerMapping
→UserController
UserController
调用UserService
→ 返回User
对象DispatcherServlet
转换User
为 JSON → 返回 HTTP 响应
- HTTP 请求 →
运行和测试
-
运行环境:
- 使用 JDK 11 或更高版本。
- 部署到 Tomcat 8+ 或运行 Spring Boot 项目。
- 确保 Maven 依赖下载完整。
-
测试:
- 使用 Postman 测试
GET http://localhost:8080/user/info?id=1
。 - 预期响应:
{ "name": "User1", "age": 21 }
- 使用 Postman 测试
-
调试:
- 检查日志,确保
DispatcherServlet
、HandlerMapping
和HttpMessageConverter
正常工作。 - 如果 JSON 格式错误,检查
User
类是否有无参构造、getter/setter。
- 检查日志,确保
总结
通过上述详细修改,我们将 Spring MVC 从返回 JSP 页面改为返回 JSON 数据,适合 RESTful API 开发。我补充了技术细节(如 Jackson 配置、参数验证、异常处理等)和代码注释,确保每个步骤清晰易懂。