Skip to content

控制器


两种控制器

@RestController(最常用,返回 JSON)

java
@RestController                         // 所有方法自动 JSON 序列化
@RequestMapping("/api/users")
public class UserController {

    private final UserService userService;

    // 构造器注入(推荐方式)
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping
    public List<User> index() {          // 自动转 JSON
        return userService.findAll();
    }

    @GetMapping("/{id}")
    public User show(@PathVariable Long id) {
        return userService.findById(id);
    }
}

@RestController = @Controller + @ResponseBody,表示该类所有方法的返回值都自动序列化为 JSON。绝大多数 API 项目用这个。

@Controller(返回模板页面)

java
@Controller                             // 返回视图模板
public class HomeController {

    @GetMapping("/")
    public String home(Model model) {
        model.addAttribute("title", "Welcome");
        return "home";                   // 渲染 templates/home.html
    }
}

依赖注入进控制器

java
@RestController
public class UserController {

    // 方式一:构造器注入 ✅ 推荐
    private final UserService userService;
    private final Logger log = LoggerFactory.getLogger(getClass());

    public UserController(UserService userService) {
        this.userService = userService;
    }

    // 方式二:字段注入(不推荐,但常见)
    @Autowired
    private UserRepository userRepository;
}

⚠️ 为什么推荐构造器注入?

  1. 构造器注入的对象可以用 final 修饰,一旦赋值不可变
  2. 构造器注入在 Spring 启动时就能发现循环依赖,而不是运行时才报错
  3. 字段注入 (@Autowired) 在单元测试中难以 Mock

方法返回值处理

java
@RestController
public class UserController {

    // 1. 直接返回对象 → 自动 JSON
    @GetMapping("/{id}")
    public User show(@PathVariable Long id) {
        return userService.findById(id);
    }

    // 2. 返回 ResponseEntity → 完全控制状态码和头
    @PostMapping
    public ResponseEntity<User> create(@RequestBody User user) {
        User saved = userService.save(user);
        return ResponseEntity
                .created(URI.create("/api/users/" + saved.getId()))  // 201
                .body(saved);
    }

    // 3. 返回 void → 204 No Content
    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void delete(@PathVariable Long id) {
        userService.delete(id);
    }
}

⚠️ 注意事项

1. 控制器默认是单例(Singleton)

java
@RestController
public class UserController {
    // 所有请求共用这一个实例!不能在这里放请求级别的状态
    private int counter = 0;  // ❌ 并发环境下会出错
}

和 PHP 不同,PHP-FPM 每个请求都是独立进程,控制器在每个请求中重新构造。Java 的控制器默认是单例 Bean,无状态是基本要求。如果需要状态,用 @Scope("request")@SessionAttributes

2. 不需要继承任何基类

java
// 不需要像 PHP 那样 class UserController extends Controller
@RestController  // 只需要这个注解即可
public class UserController { }

Spring 通过注解来判断类的角色,不依赖类继承。这也是为什么你看到的 Spring 代码很少有 extends。

3. 方法参数按需声明

java
@GetMapping("/{id}")
public User show(@PathVariable Long id,   // 从路径取
                 @RequestParam int page,  // 从查询参数取
                 HttpServletRequest req,  // 直接注入 Servlet 原对象
                 Model model) { }        // 注入模型(@Controller 场景用)

Spring 自动匹配方法参数:检查参数类型和注解,从请求中提取对应值。你不需要像 PHP 那样手动 $request->input('id')

4. 没有验证回调

php
// PHP: 控制器中可以写 validate、authorize 等逻辑
public function store(Request $request) {
    $validated = $request->validate([...]);
    $this->authorize('create', Post::class);
}
java
// Java: 验证和授权通过注解处理,控制器中只保留业务逻辑
@PostMapping
public Post create(@Valid @RequestBody Post post) {  // 验证在参数上
    // 业务逻辑,没有 validate() 调用
}

面向 PHP 开发者的 Spring Boot 文档