Java案例实战:控制器编写全流程指南(从零到精通)
📖 目录导读
- 控制器的核心职责与Spring MVC架构
- 控制器编写前的环境搭建与依赖配置
- 基础控制器编写步骤:注解、映射与数据返回
- 实战案例:用户管理系统的控制器实现
- 控制器中的参数绑定与数据校验
- 高频问题解答(Q&A)
控制器的核心职责与Spring MVC架构
1 什么是控制器?
控制器(Controller)是Spring MVC框架中的核心组件,负责处理客户端(如浏览器、移动App)发送的HTTP请求,并返回响应数据或视图,控制器是用户交互与业务逻辑之间的桥梁。

2 控制器的三层职责
- 接收请求:通过
@RequestMapping等注解将URL映射到具体方法 - 调用业务:通过依赖注入调用Service层处理业务逻辑
- 返回响应:返回JSON数据、视图页面或错误信息
3 Spring MVC请求处理流程
请求 → DispatcherServlet → 处理器映射器(定位控制器) → 处理器适配器(执行方法) → 返回ModelAndView → 视图解析器 → 响应
控制器是开发者直接接触最多的层,也是Java Web开发必会技能。
控制器编写前的环境搭建与依赖配置
1 核心依赖(Maven/Gradle)
<!-- Spring Boot父依赖(简化配置) -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
</parent>
<!-- 关键依赖:Web场景启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2 项目结构分层建议
src/main/java/com/example/demo
├── controller/ # 控制器层
├── service/ # 业务逻辑层
├── repository/ # 数据访问层
└── model/ # 实体类
❓ 问答:控制器必须放在controller包下吗?
不一定,但Spring默认会扫描主启动类所在包及其子包,建议遵循约定,将控制器放在controller包中,便于管理和扫描。
基础控制器编写步骤:注解、映射与数据返回
1 关键注解
| 注解 | 作用 | 常用场景 |
|---|---|---|
@RestController |
标记为控制器且返回JSON | 前后端分离项目 |
@Controller |
标记为控制器,配合视图解析 | 传统MVC项目 |
@RequestMapping |
类/方法级别的请求映射 | 定义URL路径 |
@GetMapping |
处理GET请求 | 查询接口 |
@PostMapping |
处理POST请求 | 新增接口 |
@PutMapping |
处理PUT请求 | 更新接口 |
@DeleteMapping |
处理DELETE请求 | 删除接口 |
2 第一个控制器:Hello World
@RestController
@RequestMapping("/api")
public class HelloController {
@GetMapping("/hello")
public String sayHello() {
return "Hello, Spring MVC!";
}
@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
// 模拟返回用户对象
return new User(id, "张三", 25);
}
}
测试结果:访问http://localhost:8080/api/hello返回字符串,访问/api/user/1返回JSON对象。
3 返回JSON的两种方式
- 方式一:方法上直接返回对象(推荐),Spring自动序列化为JSON
- 方式二:返回
ResponseEntity<T>,可自定义HTTP状态码和响应头
实战案例:用户管理系统的控制器实现
1 需求分析
实现一个用户管理RESTful API,包含:
- 查询所有用户(GET /api/users)
- 根据ID查询单个用户(GET /api/users/{id})
- 新增用户(POST /api/users)
- 更新用户(PUT /api/users/{id})
- 删除用户(DELETE /api/users/{id})
2 控制器代码实现
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService; // 依赖注入业务层
@GetMapping
public List<User> getAllUsers() {
return userService.findAll();
}
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
User user = userService.findById(id);
if (user == null) {
return ResponseEntity.notFound().build(); // 404处理
}
return ResponseEntity.ok(user);
}
@PostMapping
public ResponseEntity<User> createUser(@RequestBody @Valid User user) {
User savedUser = userService.save(user);
return ResponseEntity.status(HttpStatus.CREATED).body(savedUser);
}
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(@PathVariable Long id,
@RequestBody @Valid User user) {
user.setId(id);
User updatedUser = userService.update(user);
return ResponseEntity.ok(updatedUser);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.deleteById(id);
return ResponseEntity.noContent().build();
}
}
3 关键设计点
- 使用
ResponseEntity:可精确控制HTTP状态码(200/201/404/204) - 参数校验:通过
@Valid触发校验(需要配合实体类上的@NotBlank等注解) - 路径变量:
@PathVariable获取URL中的{id}参数
❓ 问答:为什么推荐使用ResponseEntity而不是直接返回对象?
直接返回对象时,Spring默认返回200状态码,但在实际开发中,需要区分新增成功(201)、资源不存在(404)、参数错误(400)等不同情况。ResponseEntity提供了更细致的控制能力。
控制器中的参数绑定与数据校验
1 常见参数绑定方式
// 1. 查询参数
@GetMapping("/search")
public List<User> search(@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size) {}
// 2. 路径变量
@GetMapping("/{id}/detail")
public User detail(@PathVariable Long id) {}
// 3. 请求体(POST/PUT)
@PostMapping
public User create(@RequestBody @Valid UserCreateRequest request) {}
// 4. 请求头
@GetMapping("/header-demo")
public String getHeader(@RequestHeader("Authorization") String auth) {}
2 数据校验实战
在实体类中添加校验注解:
public class UserCreateRequest {
@NotBlank(message = "用户名不能为空")
@Size(min = 2, max = 20, message = "用户名长度2-20字符")
private String name;
@Email(message = "邮箱格式不正确")
private String email;
@Min(value = 18, message = "年龄必须大于等于18")
@Max(value = 100, message = "年龄必须小于等于100")
private Integer age;
}
3 全局异常处理(统一错误返回)
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationException(
MethodArgumentNotValidException ex) {
String message = ex.getBindingResult().getFieldErrors()
.stream().map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
return ResponseEntity.badRequest().body(new ErrorResponse(message));
}
}
❓ 问答:控制器如何优雅处理参数校验失败?
使用@Valid+@RestControllerAdvice全局异常拦截是最佳实践,统一返回格式为{ "code": 400, "message": "用户名不能为空" },避免在每个控制器中重复写try-catch。
高频问题解答(Q&A)
Q1:@RestController和@Controller区别?
@RestController=@Controller+@ResponseBody,默认所有方法返回数据(JSON/XML),不进行视图解析。@Controller用于传统MVC模式,返回字符串会被视图解析器处理为页面路径。
Q2:RequestMapping可以放在类上和方法上吗?
可以。类级别用于定义模块前缀(如/api/users),方法级别定义具体路径(如/{id}),结合后完整URL为/api/users/{id}。
Q3:控制器中的Service如何注入?
推荐使用构造器注入(Spring官方推荐):
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
避免使用@Autowired字段注入,不利于单元测试和不可变性。
Q4:控制器如何处理文件上传?
使用MultipartFile:
@PostMapping("/upload")
public String uploadFile(@RequestParam("file") MultipartFile file) {
String fileName = file.getOriginalFilename();
// 保存文件到磁盘...
return "上传成功: " + fileName;
}
Q5:为什么有的控制器方法返回String,有的返回对象?
- 返回String:
@Controller+视图解析器时,返回视图名称(如"user/list")。 - 返回对象:
@RestController或@ResponseBody时,返回JSON数据。
控制器编写黄金法则
- 职责单一:控制器只负责接收请求、调用Service、返回响应,不写SQL或复杂逻辑
- 统一异常:使用全局异常处理器,保持错误返回格式一致
- 参数校验:使用
@Valid注解+自定义校验,避免冗余if-else - Restful风格:使用标准的HTTP方法(GET/POST/PUT/DELETE)和状态码(200/201/404)
- 清晰注释:为每个接口添加Swagger注解或Javadoc,便于团队协作