Skip to content

控制器与请求映射

在Spring MVC中,控制器(Controller)处理用户请求并返回适当的响应。请求映射则定义了URL与控制器方法之间的映射关系。本文详细介绍Spring MVC中的控制器和请求映射机制。

控制器基础

什么是控制器?

控制器是Spring MVC的核心组件,负责:

  • 接收客户端请求
  • 调用业务逻辑处理
  • 将处理结果封装成模型数据
  • 选择合适的视图进行渲染

创建控制器的方式

在Spring MVC中创建控制器主要有两种方式:

  1. 基于注解的控制器(推荐)
java
@Controller
public class UserController {
    // 控制器方法
}
  1. 实现接口的控制器(传统方式,较少使用)
java
public class UserController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {
        // 处理请求
        return new ModelAndView("viewName");
    }
}

@Controller与@RestController

Spring MVC提供了两种主要的控制器注解:

  1. @Controller

    • 传统的Spring MVC控制器
    • 方法返回的字符串通常被解释为视图名
    • 需要结合@ResponseBody注解才能返回数据而非视图
  2. @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模式匹配:

  1. 精确匹配/users
  2. 路径变量/users/{id}
  3. 通配符匹配
    • * 匹配一个路径段:/users/*/profile
    • ** 匹配多个路径段:/users/**
  4. 正则表达式/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));
}

请求映射的工作原理

  1. 初始化阶段

    • Spring扫描带有@Controller和@RequestMapping注解的类
    • 解析所有映射信息,构建HandlerMethod对象
    • 注册到HandlerMapping中
  2. 请求处理阶段

    • DispatcherServlet接收请求
    • 调用HandlerMapping.getHandler(request)查找匹配的HandlerMethod
    • 使用HandlerAdapter调用找到的处理器方法

常见问题与最佳实践

URL命名约定

  • 使用小写字母
  • 使用连字符(-)分隔单词,而非下划线
  • 使用复数表示资源集合(如/users而非/user)
  • 保持URL结构的一致性
  • 避免URL中包含动词(REST风格)

请求映射的优先级

当多个映射可能匹配同一个请求时,Spring使用以下规则确定优先级:

  1. 精确路径匹配优先于通配符匹配
  2. 更长的通配符模式优先于较短的模式
  3. /** 通配符的优先级最低
  4. 带有路径扩展名的映射优先级较高

控制器组织技巧

  1. 按功能或资源类型组织控制器
  2. 使用嵌套类细分大型控制器
  3. 利用基础控制器抽象共享行为
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";
    }
}