Java案例怎么编写控制器?

wen java案例 24

Java案例实战:控制器编写全流程指南(从零到精通)

📖 目录导读

  1. 控制器的核心职责与Spring MVC架构
  2. 控制器编写前的环境搭建与依赖配置
  3. 基础控制器编写步骤:注解、映射与数据返回
  4. 实战案例:用户管理系统的控制器实现
  5. 控制器中的参数绑定与数据校验
  6. 高频问题解答(Q&A)

控制器的核心职责与Spring MVC架构

1 什么是控制器?

控制器(Controller)是Spring MVC框架中的核心组件,负责处理客户端(如浏览器、移动App)发送的HTTP请求,并返回响应数据或视图,控制器是用户交互与业务逻辑之间的桥梁

Java案例怎么编写控制器?

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数据。

控制器编写黄金法则

  1. 职责单一:控制器只负责接收请求、调用Service、返回响应,不写SQL或复杂逻辑
  2. 统一异常:使用全局异常处理器,保持错误返回格式一致
  3. 参数校验:使用@Valid注解+自定义校验,避免冗余if-else
  4. Restful风格:使用标准的HTTP方法(GET/POST/PUT/DELETE)和状态码(200/201/404)
  5. 清晰注释:为每个接口添加Swagger注解或Javadoc,便于团队协作

抱歉,评论功能暂时关闭!