Spring MVC核心注解与实践指南

@RequestMapping注解详解

@RequestMapping是Spring MVC中最基础且使用频率最高的注解,用于将HTTP请求映射到控制器方法。该注解可作用于类级别和方法级别,支持多种属性配置:

@Controller
@RequestMapping("/products")
public class ProductController {
    
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public String getProduct(@PathVariable Long id, Model model) {
        Product product = productService.findById(id);
        model.addAttribute("product", product);
        return "productDetail";
    }
    
    @RequestMapping(value = "/create", 
                   method = {RequestMethod.GET, RequestMethod.POST},
                   consumes = MediaType.APPLICATION_JSON_VALUE,
                   produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public ResponseEntity<Product> createProduct(@RequestBody Product product) {
        Product savedProduct = productService.save(product);
        return new ResponseEntity<>(savedProduct, HttpStatus.CREATED);
    }
}

核心属性解析:

  • value/path:定义请求路径映射,支持Ant风格路径模式
  • method:指定支持的HTTP方法类型(GET/POST等)
  • params:要求请求必须包含特定参数
  • headers:限制请求头必须满足特定条件
  • consumes:指定处理请求的媒体类型(如application/json)
  • produces:指定响应输出的媒体类型

开发技巧:

  1. 类级别定义基础路径,方法级别定义具体端点
  2. 使用组合注解(@GetMapping等)简化代码
  3. 路径参数使用{}语法配合@PathVariable
  4. 媒体类型协商优先使用produces/consumes

参数绑定注解簇

@RequestParam参数绑定

处理查询参数和表单数据的标准方式:

@GetMapping("/search")
public String searchProducts(
    @RequestParam(name = "keyword", required = false) String searchTerm,
    @RequestParam(defaultValue = "1") int page,
    @RequestParam(required = false) String sortBy) {
    // 业务逻辑
}

参数选项:

  • required:是否必须参数(默认true)
  • defaultValue:参数默认值
  • name/value:参数别名

@PathVariable路径变量

绑定URI模板变量到方法参数:

@DeleteMapping("/{category}/{id}")
public ResponseEntity deleteItem(
    @PathVariable String category,
    @PathVariable Long id) {
    // 删除操作
}

最佳实践:

  • 保持路径变量名称与方法参数名一致
  • 复杂类型需要自定义Converter
  • 配合正则表达式进行参数验证:
    @GetMapping("/{id:\\d+}")
    

@RequestBody请求体处理

处理非表单类型的请求内容(JSON/XML):

@PostMapping
public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
    User savedUser = userService.create(user);
    return ResponseEntity.created(URI.create("/users/" + savedUser.getId()))
                         .body(savedUser);
}

注意事项:

  • 需要配置消息转换器(如MappingJackson2HttpMessageConverter)
  • 配合@Valid进行参数验证
  • 不支持multipart/form-data类型

模型处理注解

@ModelAttribute的三种用法

  1. 方法参数绑定:
@PostMapping("/order")
public String submitOrder(@ModelAttribute("orderForm") Order order) {
    // 处理订单
}
  1. 方法级别属性添加:
@ModelAttribute("categories")
public List<Category> loadCategories() {
    return categoryService.getAll();
}
  1. 返回值隐式绑定:
@ModelAttribute
public User currentUser() {
    return userService.getCurrentUser();
}

@SessionAttributes会话管理

@Controller
@SessionAttributes("shoppingCart")
public class CartController {
    
    @ModelAttribute("shoppingCart")
    public ShoppingCart initializeCart() {
        return new ShoppingCart();
    }
    
    @PostMapping("/cart/add")
    public String addItem(@ModelAttribute ShoppingCart cart, 
                         @RequestParam Item item) {
        cart.addItem(item);
        return "redirect:/cart";
    }
}

注意点:

  • 需配合@SessionAttribute注解清除属性
  • 会话超时处理策略
  • 分布式会话存储问题

响应处理注解

@ResponseBody与HttpMessageConverter

@GetMapping(value = "/report", produces = MediaType.APPLICATION_PDF_VALUE)
@ResponseBody
public byte[] generateReport() {
    return pdfService.generateReport();
}

转换器配置示例:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
            .indentOutput(true)
            .dateFormat(new SimpleDateFormat("yyyy-MM-dd"));
        converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
    }
}

@RestController复合注解

等效于@Controller + @ResponseBody:

@RestController
@RequestMapping("/api/users")
public class UserApiController {

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

验证注解与错误处理

参数验证流程

@PostMapping("/register")
public String registerUser(@Valid @ModelAttribute UserForm form, 
                          BindingResult result) {
    if (result.hasErrors()) {
        return "registrationForm";
    }
    userService.register(form);
    return "redirect:/welcome";
}

常用验证注解:

  • @NotNull/@NotEmpty
  • @Size(min=2, max=30)
  • @Email
  • @Pattern(regexp="正则表达式")
  • @Future/@Past

自定义验证器示例

public class PhoneValidator implements ConstraintValidator<ValidPhone, String> {
    private Pattern pattern = Pattern.compile("^1[3-9]\\d{9}$");
    
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return value != null && pattern.matcher(value).matches();
    }
}

异常处理机制

@ExceptionHandler控制器级异常处理

@ControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
        ErrorResponse error = new ErrorResponse("NOT_FOUND", ex.getMessage());
        return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
    }
    
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> handleValidationExceptions(
        MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getAllErrors().forEach(error -> {
            String fieldName = ((FieldError) error).getField();
            String message = error.getDefaultMessage();
            errors.put(fieldName, message);
        });
        return ResponseEntity.badRequest().body(errors);
    }
}

RESTful支持注解

@ResponseStatus状态码控制

@ResponseStatus(code = HttpStatus.CREATED, reason = "Resource created")
public class CreatedException extends RuntimeException {}

@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteResource(@PathVariable Long id) {
    resourceService.delete(id);
}

HATEOAS支持示例

@GetMapping("/{id}")
public EntityModel<User> getUser(@PathVariable Long id) {
    User user = userService.findById(id);
    return EntityModel.of(user,
        linkTo(methodOn(UserController.class).getUser(id)).withSelfRel(),
        linkTo(methodOn(UserController.class).getAllUsers()).withRel("users"));
}

跨域处理注解

@CrossOrigin配置示例

@RestController
@CrossOrigin(origins = "https://trusted-domain.com", 
           maxAge = 3600,
           allowedHeaders = {"x-requested-with", "content-type"},
           methods = {RequestMethod.GET, RequestMethod.POST})
@RequestMapping("/api")
public class ApiController {
    // 控制器方法
}

测试验证代码示例

MockMVC测试用例

@SpringBootTest
@AutoConfigureMockMvc
class ProductControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void testGetProduct() throws Exception {
        mockMvc.perform(get("/products/123")
               .header("Accept", "application/json"))
               .andExpect(status().isOk())
               .andExpect(jsonPath("$.name").value("Test Product"));
    }

    @Test
    void testCreateProduct() throws Exception {
        String jsonBody = "{ \"name\":\"New Product\", \"price\":99.99 }";
        
        mockMvc.perform(post("/products")
               .contentType(MediaType.APPLICATION_JSON)
               .content(jsonBody))
               .andExpect(status().isCreated())
               .andExpect(header().exists("Location"));
    }
}

性能优化建议

  1. 合理使用@RequestMapping的窄化配置
  2. 优先使用组合注解提升代码可读性
  3. 批量参数绑定使用@ModelAttribute
  4. 异步处理使用@Async和Callable
  5. 缓存静态资源映射配置
  6. 合理设置消息转换器顺序
正文到此结束
评论插件初始化中...
Loading...