Appearance
控制器与请求映射
在Spring MVC中,控制器(Controller)处理用户请求并返回适当的响应。请求映射则定义了URL与控制器方法之间的映射关系。本文详细介绍Spring MVC中的控制器和请求映射机制。
控制器基础
什么是控制器?
控制器是Spring MVC的核心组件,负责:
- 接收客户端请求
- 调用业务逻辑处理
- 将处理结果封装成模型数据
- 选择合适的视图进行渲染
创建控制器的方式
在Spring MVC中创建控制器主要有两种方式:
- 基于注解的控制器(推荐)
java
@Controller
public class UserController {
// 控制器方法
}
- 实现接口的控制器(传统方式,较少使用)
java
public class UserController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {
// 处理请求
return new ModelAndView("viewName");
}
}
@Controller与@RestController
Spring MVC提供了两种主要的控制器注解:
@Controller
- 传统的Spring MVC控制器
- 方法返回的字符串通常被解释为视图名
- 需要结合@ResponseBody注解才能返回数据而非视图
@RestController
- @Controller + @ResponseBody的组合
- 所有方法默认都会将返回值直接写入响应体
- 主要用于开发RESTful API
java
// 传统控制器
@Controller
public class UserViewController {
@GetMapping("/users")
public String listUsers(Model model) {
model.addAttribute("users", userService.findAll());
return "user/list"; // 返回视图名
}
@GetMapping("/api/users")
@ResponseBody // 需要显式标注
public List<User> getUsersJson() {
return userService.findAll(); // 返回JSON数据
}
}
// REST控制器
@RestController
public class UserApiController {
@GetMapping("/api/users")
public List<User> getUsers() {
return userService.findAll(); // 自动作为JSON返回
}
}
请求映射注解
Spring MVC提供了多种请求映射注解,用于将HTTP请求映射到控制器方法:
@RequestMapping
最基本的请求映射注解,可以指定:
- URL路径
- HTTP方法
- 请求参数
- 请求头
- 内容类型
- 等其他条件
java
@Controller
public class ProductController {
// 映射到类上,作为基础路径
@RequestMapping("/products")
public class ProductController {
// GET /products
@RequestMapping(method = RequestMethod.GET)
public String listProducts() {
return "product/list";
}
// GET /products/{id}
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public String getProduct(@PathVariable Long id) {
return "product/detail";
}
// 请求参数匹配
@RequestMapping(params = "category=books")
public String getBookProducts() {
return "product/books";
}
// 请求头匹配
@RequestMapping(headers = "Content-Type=application/json")
public ResponseEntity<String> handleJsonRequest() {
return ResponseEntity.ok("JSON request handled");
}
}
}
HTTP方法特定的注解
Spring提供了针对不同HTTP方法的便捷注解:
- @GetMapping:处理GET请求
- @PostMapping:处理POST请求
- @PutMapping:处理PUT请求
- @DeleteMapping:处理DELETE请求
- @PatchMapping:处理PATCH请求
java
@Controller
@RequestMapping("/api/users")
public class UserApiController {
@GetMapping // GET /api/users
public List<User> listUsers() {
return userService.findAll();
}
@GetMapping("/{id}") // GET /api/users/{id}
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
@PostMapping // POST /api/users
public User createUser(@RequestBody User user) {
return userService.save(user);
}
@PutMapping("/{id}") // PUT /api/users/{id}
public User updateUser(@PathVariable Long id, @RequestBody User user) {
user.setId(id);
return userService.update(user);
}
@DeleteMapping("/{id}") // DELETE /api/users/{id}
public void deleteUser(@PathVariable Long id) {
userService.deleteById(id);
}
}
请求映射的高级特性
URL模式
Spring MVC支持多种URL模式匹配:
- 精确匹配:
/users
- 路径变量:
/users/{id}
- 通配符匹配:
*
匹配一个路径段:/users/*/profile
**
匹配多个路径段:/users/**
- 正则表达式:
/users/{id:[0-9]+}
多URL映射
一个控制器方法可以映射到多个URL:
java
@GetMapping({"/users", "/members"})
public String listUsers() {
return "user/list";
}
消费和生产媒体类型
可以根据请求的Content-Type和Accept头进行映射:
java
@PostMapping(
value = "/users",
consumes = "application/json", // 接收JSON请求
produces = "application/json" // 返回JSON响应
)
public ResponseEntity<User> createUser(@RequestBody User user) {
return ResponseEntity.ok(userService.save(user));
}
@PostMapping(
value = "/users",
consumes = "application/xml", // 接收XML请求
produces = "application/xml" // 返回XML响应
)
public ResponseEntity<User> createUserFromXml(@RequestBody User user) {
return ResponseEntity.ok(userService.save(user));
}
请求映射的工作原理
初始化阶段:
- Spring扫描带有@Controller和@RequestMapping注解的类
- 解析所有映射信息,构建HandlerMethod对象
- 注册到HandlerMapping中
请求处理阶段:
- DispatcherServlet接收请求
- 调用HandlerMapping.getHandler(request)查找匹配的HandlerMethod
- 使用HandlerAdapter调用找到的处理器方法
常见问题与最佳实践
URL命名约定
- 使用小写字母
- 使用连字符(-)分隔单词,而非下划线
- 使用复数表示资源集合(如/users而非/user)
- 保持URL结构的一致性
- 避免URL中包含动词(REST风格)
请求映射的优先级
当多个映射可能匹配同一个请求时,Spring使用以下规则确定优先级:
- 精确路径匹配优先于通配符匹配
- 更长的通配符模式优先于较短的模式
- /** 通配符的优先级最低
- 带有路径扩展名的映射优先级较高
控制器组织技巧
- 按功能或资源类型组织控制器
- 使用嵌套类细分大型控制器
- 利用基础控制器抽象共享行为:
java
@RequestMapping("/api")
public abstract class BaseApiController {
// 共享方法和配置
}
@RestController
@RequestMapping("/users")
public class UserApiController extends BaseApiController {
// 实际路径将是 /api/users
}
实战示例
RESTful资源控制器
java
@RestController
@RequestMapping("/api/products")
public class ProductController {
private final ProductService productService;
@Autowired
public ProductController(ProductService productService) {
this.productService = productService;
}
@GetMapping
public List<Product> getAllProducts(
@RequestParam(required = false) String category,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
return productService.findProducts(category, page, size);
}
@GetMapping("/{id}")
public ResponseEntity<Product> getProduct(@PathVariable Long id) {
return productService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Product createProduct(@Valid @RequestBody Product product) {
return productService.save(product);
}
@PutMapping("/{id}")
public ResponseEntity<Product> updateProduct(
@PathVariable Long id,
@Valid @RequestBody Product product) {
if (!productService.existsById(id)) {
return ResponseEntity.notFound().build();
}
product.setId(id);
return ResponseEntity.ok(productService.save(product));
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
if (!productService.existsById(id)) {
return ResponseEntity.notFound().build();
}
productService.deleteById(id);
return ResponseEntity.noContent().build();
}
@GetMapping("/{id}/reviews")
public List<Review> getProductReviews(@PathVariable Long id) {
return productService.findReviewsByProductId(id);
}
}
传统Web控制器
java
@Controller
@RequestMapping("/products")
public class ProductWebController {
private final ProductService productService;
@Autowired
public ProductWebController(ProductService productService) {
this.productService = productService;
}
@GetMapping
public String listProducts(Model model,
@RequestParam(required = false) String category) {
model.addAttribute("products", productService.findByCategory(category));
model.addAttribute("categories", productService.findAllCategories());
return "product/list";
}
@GetMapping("/{id}")
public String showProduct(@PathVariable Long id, Model model) {
productService.findById(id).ifPresent(product -> {
model.addAttribute("product", product);
model.addAttribute("reviews", product.getReviews());
});
return "product/detail";
}
@GetMapping("/new")
public String showProductForm(Model model) {
model.addAttribute("product", new Product());
model.addAttribute("categories", productService.findAllCategories());
return "product/form";
}
@PostMapping
public String createProduct(@Valid @ModelAttribute("product") Product product,
BindingResult result,
RedirectAttributes redirectAttributes) {
if (result.hasErrors()) {
return "product/form";
}
productService.save(product);
redirectAttributes.addFlashAttribute("message", "Product created successfully!");
return "redirect:/products";
}
@GetMapping("/{id}/edit")
public String showEditForm(@PathVariable Long id, Model model) {
productService.findById(id).ifPresent(product -> {
model.addAttribute("product", product);
model.addAttribute("categories", productService.findAllCategories());
});
return "product/form";
}
@PostMapping("/{id}")
public String updateProduct(@PathVariable Long id,
@Valid @ModelAttribute("product") Product product,
BindingResult result,
RedirectAttributes redirectAttributes) {
if (result.hasErrors()) {
return "product/form";
}
product.setId(id);
productService.save(product);
redirectAttributes.addFlashAttribute("message", "Product updated successfully!");
return "redirect:/products";
}
}