SpringMVC控制器定义:@Controller注解详解

news2025/3/6 10:06:02

在这里插入图片描述

文章目录

    • 引言
    • 一、@Controller注解基础
    • 二、@RequestMapping与请求映射
    • 三、参数绑定与数据校验
    • 四、@RestController与RESTful API
    • 五、控制器建议与全局处理
    • 六、控制器测试策略
    • 总结

引言

在SpringMVC框架中,控制器(Controller)是整个Web应用的核心组件,负责处理用户请求并返回相应的响应。@Controller注解是Spring框架提供的一种声明式方式,用于定义控制器类,它不仅简化了开发流程,还提供了丰富的功能特性。深入理解@Controller注解的工作原理及其衍生注解,对于开发高质量的Web应用至关重要。本文将全面剖析@Controller注解的用法、特性及其在SpringMVC中的核心地位,通过详细的代码示例展示其实际应用,帮助开发者掌握这一核心注解的精髓,从而更高效地构建Web应用。

一、@Controller注解基础

@Controller注解是SpringMVC框架中最基础的注解之一,用于标识一个类作为控制器。被@Controller标注的类将被Spring容器识别并注册为Bean,同时具备处理HTTP请求的能力。@Controller本质上是@Component注解的特化,继承了@Component的所有功能,但更具有语义价值,明确表示该类在MVC架构中扮演控制器角色。在SpringMVC框架中,@Controller与@RequestMapping等注解配合使用,实现请求URL到处理方法的映射,构成了Web请求处理的核心机制。

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.ui.Model;

/**
 * 基本控制器示例
 * 使用@Controller注解标识该类为控制器
 */
@Controller
@RequestMapping("/users")
public class UserController {
    
    /**
     * 处理GET请求,显示用户列表页面
     * @param model 视图模型
     * @return 视图名称
     */
    @RequestMapping(method = RequestMethod.GET)
    public String listUsers(Model model) {
        // 添加数据到模型
        model.addAttribute("title", "用户列表");
        // 返回视图名称,视图解析器会将其解析为实际的视图
        return "user/list";
    }
    
    /**
     * 处理GET请求,显示用户详情页面
     * @param userId 用户ID
     * @param model 视图模型
     * @return 视图名称
     */
    @RequestMapping(value = "/{userId}", method = RequestMethod.GET)
    public String getUserDetails(@RequestParam("userId") Long userId, Model model) {
        // 获取用户详情并添加到模型
        User user = userService.getUserById(userId);
        model.addAttribute("user", user);
        return "user/details";
    }
}

二、@RequestMapping与请求映射

@RequestMapping注解是@Controller注解的重要搭配,用于将HTTP请求映射到控制器的具体处理方法。@RequestMapping可以标注在类级别和方法级别,前者定义基础URL路径,后者定义具体的URL模式。@RequestMapping支持多种属性配置,包括value/path(指定URL路径)、method(限定HTTP方法类型)、params(限定请求参数)、headers(限定请求头)、consumes(限定请求内容类型)和produces(指定响应内容类型)。从Spring 4.3开始,框架还提供了更具语义化的派生注解,如@GetMapping、@PostMapping等,使代码更加清晰易读。

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.ui.Model;

/**
 * 演示各种请求映射方式的控制器
 */
@Controller
@RequestMapping("/products")
public class ProductController {
    
    /**
     * 使用@GetMapping处理GET请求(Spring 4.3+)
     * 等同于@RequestMapping(method = RequestMethod.GET)
     */
    @GetMapping
    public String listProducts(Model model) {
        return "product/list";
    }
    
    /**
     * 处理带路径变量的GET请求
     * @param productId 产品ID(从URL路径中提取)
     */
    @GetMapping("/{productId}")
    public String getProductDetails(@PathVariable Long productId, Model model) {
        return "product/details";
    }
    
    /**
     * 处理POST请求,限定Content-Type为application/json
     */
    @PostMapping(consumes = "application/json")
    public String createProduct(@RequestBody Product product, Model model) {
        return "redirect:/products";
    }
    
    /**
     * 处理PUT请求,同时指定请求和响应的Content-Type
     */
    @PutMapping(value = "/{productId}", 
               consumes = "application/json",
               produces = "application/json")
    @ResponseBody
    public Product updateProduct(@PathVariable Long productId, 
                                @RequestBody Product product) {
        // 更新产品并返回更新后的对象
        return updatedProduct;
    }
    
    /**
     * 根据请求参数匹配的处理方法
     * 仅当请求包含category参数且不包含type参数时才会匹配
     */
    @GetMapping(params = {"category", "!type"})
    public String getProductsByCategory(@RequestParam String category, Model model) {
        return "product/category";
    }
    
    /**
     * 根据请求头匹配的处理方法
     * 仅当请求包含指定Accept头时才会匹配
     */
    @GetMapping(headers = "Accept=text/html")
    public String getProductsForBrowser() {
        return "product/browser-view";
    }
}

三、参数绑定与数据校验

SpringMVC提供了强大的参数绑定机制,使控制器方法能够接收各种类型的请求数据。参数绑定包括多种形式,如简单类型参数(@RequestParam)、路径变量(@PathVariable)、请求体(@RequestBody)、表单对象(@ModelAttribute)等。除了参数绑定,SpringMVC还集成了Bean Validation规范,支持通过注解进行数据校验。通过@Valid或@Validated注解,可以触发绑定对象的验证,验证结果会存储在BindingResult中。这种声明式的数据验证方式,大大减少了样板代码,提高了开发效率和代码质量。

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.BindingResult;
import org.springframework.ui.Model;

import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Min;
import javax.validation.constraints.Email;
import org.hibernate.validator.constraints.Length;

/**
 * 演示参数绑定和数据校验的控制器
 */
@Controller
@RequestMapping("/customers")
public class CustomerController {
    
    /**
     * 显示客户注册表单
     */
    @GetMapping("/register")
    public String showRegistrationForm(Model model) {
        model.addAttribute("customer", new Customer());
        return "customer/register";
    }
    
    /**
     * 处理客户注册请求
     * 使用@Valid触发数据校验,BindingResult接收校验结果
     */
    @PostMapping("/register")
    public String registerCustomer(
            @Valid @ModelAttribute("customer") Customer customer,
            BindingResult bindingResult,
            Model model) {
        
        // 检查是否有验证错误
        if (bindingResult.hasErrors()) {
            return "customer/register"; // 返回表单页面,显示错误信息
        }
        
        // 处理注册逻辑
        customerService.register(customer);
        
        model.addAttribute("message", "注册成功!");
        return "redirect:/customers/login";
    }
    
    /**
     * 使用@RequestParam接收查询参数
     */
    @GetMapping("/search")
    public String searchCustomers(
            @RequestParam(required = false) String name,
            @RequestParam(defaultValue = "1") int page,
            Model model) {
        
        // 执行搜索
        List<Customer> customers = customerService.searchByName(name, page);
        model.addAttribute("customers", customers);
        
        return "customer/search-results";
    }
    
    /**
     * 使用@PathVariable接收路径变量
     */
    @GetMapping("/{id}")
    public String getCustomerDetails(@PathVariable("id") Long customerId, Model model) {
        Customer customer = customerService.getById(customerId);
        model.addAttribute("customer", customer);
        return "customer/details";
    }
    
    /**
     * 处理RESTful API请求
     * 使用@RequestBody接收JSON请求体
     */
    @PostMapping(value = "/api", consumes = "application/json")
    @ResponseBody
    public CustomerResponse createCustomerApi(@Valid @RequestBody Customer customer) {
        // 处理创建逻辑
        Customer created = customerService.create(customer);
        
        // 返回响应对象
        return new CustomerResponse(created.getId(), "Customer created successfully");
    }
}

/**
 * 包含验证注解的客户实体类
 */
public class Customer {
    
    private Long id;
    
    @NotBlank(message = "姓名不能为空")
    @Length(max = 50, message = "姓名长度不能超过50个字符")
    private String name;
    
    @NotBlank(message = "邮箱不能为空")
    @Email(message = "邮箱格式不正确")
    private String email;
    
    @Min(value = 18, message = "年龄必须大于或等于18岁")
    private int age;
    
    // Getters and Setters
    // ...
}

/**
 * API响应对象
 */
public class CustomerResponse {
    private Long id;
    private String message;
    
    public CustomerResponse(Long id, String message) {
        this.id = id;
        this.message = message;
    }
    
    // Getters and Setters
    // ...
}

四、@RestController与RESTful API

随着RESTful API的普及,Spring框架引入了@RestController注解,它是@Controller和@ResponseBody的组合注解。@RestController标注的控制器中,所有方法的返回值会自动序列化为HTTP响应体,无需再添加@ResponseBody注解。这种方式特别适合构建RESTful Web服务,既简化了代码,又明确了控制器的用途。@RestController与@RequestMapping及其派生注解(@GetMapping, @PostMapping等)配合使用,可以轻松构建符合REST架构风格的API,支持JSON/XML等多种数据格式,满足现代Web应用的需求。

import org.springframework.web.bind.annotation.*;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

import java.util.List;

/**
 * RESTful API控制器示例
 * 使用@RestController注解,所有方法返回值都会自动序列化为响应体
 */
@RestController
@RequestMapping("/api/v1/orders")
public class OrderApiController {
    
    private final OrderService orderService;
    
    /**
     * 构造器注入服务
     */
    public OrderApiController(OrderService orderService) {
        this.orderService = orderService;
    }
    
    /**
     * 获取所有订单
     * @return 订单列表
     */
    @GetMapping
    public List<Order> getAllOrders() {
        return orderService.findAll();
    }
    
    /**
     * 根据ID获取订单
     * @param id 订单ID
     * @return 订单详情
     */
    @GetMapping("/{id}")
    public ResponseEntity<Order> getOrderById(@PathVariable Long id) {
        return orderService.findById(id)
                .map(order -> ResponseEntity.ok(order))
                .orElse(ResponseEntity.notFound().build());
    }
    
    /**
     * 创建新订单
     * @param order 订单数据
     * @return 创建的订单
     */
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Order createOrder(@Valid @RequestBody Order order) {
        return orderService.create(order);
    }
    
    /**
     * 更新订单
     * @param id 订单ID
     * @param order 订单数据
     * @return 更新后的订单
     */
    @PutMapping("/{id}")
    public ResponseEntity<Order> updateOrder(
            @PathVariable Long id, 
            @Valid @RequestBody Order order) {
        
        return orderService.update(id, order)
                .map(updated -> ResponseEntity.ok(updated))
                .orElse(ResponseEntity.notFound().build());
    }
    
    /**
     * 部分更新订单
     * @param id 订单ID
     * @param updates 部分更新数据
     * @return 更新后的订单
     */
    @PatchMapping("/{id}")
    public ResponseEntity<Order> partialUpdateOrder(
            @PathVariable Long id,
            @RequestBody Map<String, Object> updates) {
        
        return orderService.partialUpdate(id, updates)
                .map(updated -> ResponseEntity.ok(updated))
                .orElse(ResponseEntity.notFound().build());
    }
    
    /**
     * 删除订单
     * @param id 订单ID
     * @return 无内容响应
     */
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteOrder(@PathVariable Long id) {
        boolean deleted = orderService.delete(id);
        
        if (deleted) {
            return ResponseEntity.noContent().build();
        } else {
            return ResponseEntity.notFound().build();
        }
    }
    
    /**
     * 处理订单状态变更
     * @param id 订单ID
     * @param status 新状态
     * @return 更新后的订单
     */
    @PutMapping("/{id}/status")
    public ResponseEntity<Order> updateOrderStatus(
            @PathVariable Long id,
            @RequestParam OrderStatus status) {
        
        return orderService.updateStatus(id, status)
                .map(updated -> ResponseEntity.ok(updated))
                .orElse(ResponseEntity.notFound().build());
    }
    
    /**
     * 处理订单异常情况
     */
    @ExceptionHandler(OrderProcessingException.class)
    @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
    public ApiError handleOrderProcessingException(OrderProcessingException ex) {
        return new ApiError(HttpStatus.UNPROCESSABLE_ENTITY.value(), ex.getMessage());
    }
}

/**
 * API错误响应对象
 */
class ApiError {
    private int status;
    private String message;
    private LocalDateTime timestamp = LocalDateTime.now();
    
    public ApiError(int status, String message) {
        this.status = status;
        this.message = message;
    }
    
    // Getters and Setters
    // ...
}

五、控制器建议与全局处理

在实际开发中,往往需要在多个控制器之间共享数据或行为,Spring提供了@ControllerAdvice和@RestControllerAdvice注解来实现这一需求。这两个注解用于定义控制器建议类,可以包含@ExceptionHandler(处理异常)、@InitBinder(初始化数据绑定器)和@ModelAttribute(添加全局数据)方法。控制器建议类提供了一种集中管理跨控制器关注点的机制,如统一异常处理、全局数据预处理等。通过合理使用这些注解,可以使代码更加模块化,减少重复,提高维护性。在大型应用中,良好的控制器建议设计对于保持代码质量和一致性至关重要。

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.ui.Model;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.ModelAndView;

import java.beans.PropertyEditorSupport;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;

/**
 * 控制器建议类
 * 适用于所有控制器
 */
@ControllerAdvice
public class GlobalControllerAdvice {
    
    /**
     * 全局异常处理
     * 处理所有控制器中抛出的ResourceNotFoundException
     */
    @ExceptionHandler(ResourceNotFoundException.class)
    public ModelAndView handleResourceNotFoundException(ResourceNotFoundException ex) {
        ModelAndView modelAndView = new ModelAndView("error/not-found");
        modelAndView.addObject("message", ex.getMessage());
        modelAndView.setStatus(HttpStatus.NOT_FOUND);
        return modelAndView;
    }
    
    /**
     * 全局数据绑定器初始化
     * 为所有控制器添加日期格式化支持
     */
    @InitBinder
    public void initBinder(WebDataBinder binder) {
        // 注册自定义属性编辑器,处理LocalDate类型
        binder.registerCustomEditor(LocalDate.class, new PropertyEditorSupport() {
            private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
            
            @Override
            public void setAsText(String text) throws IllegalArgumentException {
                if (text == null || text.trim().isEmpty()) {
                    setValue(null);
                } else {
                    setValue(LocalDate.parse(text.trim(), formatter));
                }
            }
            
            @Override
            public String getAsText() {
                LocalDate value = (LocalDate) getValue();
                return (value != null ? value.format(formatter) : "");
            }
        });
        
        // 忽略指定字段,防止安全敏感信息被修改
        binder.setDisallowedFields("id", "createdAt", "createdBy");
    }
    
    /**
     * 全局模型属性
     * 为所有控制器的视图添加通用数据
     */
    @ModelAttribute
    public void addCommonAttributes(Model model) {
        model.addAttribute("appName", "My Spring Application");
        model.addAttribute("copyrightYear", LocalDate.now().getYear());
        
        // 添加全局用户信息
        UserDetails currentUser = SecurityUtils.getCurrentUser();
        if (currentUser != null) {
            model.addAttribute("currentUser", currentUser);
        }
    }
}

/**
 * REST API专用的控制器建议
 * 仅适用于@RestController标注的控制器
 */
@RestControllerAdvice
public class GlobalRestControllerAdvice {
    
    /**
     * 处理REST API中的异常
     * 返回JSON格式的错误信息
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ApiError handleException(Exception ex) {
        return new ApiError(
                HttpStatus.INTERNAL_SERVER_ERROR.value(),
                "An unexpected error occurred: " + ex.getMessage()
        );
    }
    
    /**
     * 处理数据校验异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ApiError handleValidationExceptions(MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getFieldErrors().forEach(error -> 
            errors.put(error.getField(), error.getDefaultMessage())
        );
        
        return new ApiError(
                HttpStatus.BAD_REQUEST.value(),
                "Validation failed",
                errors
        );
    }
    
    /**
     * 处理认证和授权异常
     */
    @ExceptionHandler({AccessDeniedException.class, AuthenticationException.class})
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public ApiError handleSecurityExceptions(Exception ex) {
        return new ApiError(
                HttpStatus.FORBIDDEN.value(),
                "Access denied: " + ex.getMessage()
        );
    }
    
    /**
     * 处理业务逻辑异常
     */
    @ExceptionHandler(BusinessException.class)
    @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
    public ApiError handleBusinessExceptions(BusinessException ex) {
        return new ApiError(
                HttpStatus.UNPROCESSABLE_ENTITY.value(),
                ex.getMessage()
        );
    }
}

/**
 * 扩展版API错误响应,支持字段错误
 */
class ApiError {
    private int status;
    private String message;
    private Map<String, String> fieldErrors;
    private LocalDateTime timestamp = LocalDateTime.now();
    
    public ApiError(int status, String message) {
        this.status = status;
        this.message = message;
    }
    
    public ApiError(int status, String message, Map<String, String> fieldErrors) {
        this.status = status;
        this.message = message;
        this.fieldErrors = fieldErrors;
    }
    
    // Getters and Setters
    // ...
}

六、控制器测试策略

测试是确保控制器质量的关键环节。Spring提供了强大的测试框架,支持控制器的单元测试和集成测试。对于单元测试,可以使用MockMvc模拟HTTP请求和响应,验证控制器行为,无需启动完整的应用服务器。对于集成测试,Spring Boot提供了@SpringBootTest注解,支持启动实际的应用上下文,进行端到端测试。良好的测试策略应该覆盖正常流程和异常场景,验证请求映射、参数绑定、业务逻辑处理和响应生成等各个环节。通过自动化测试,可以提前发现潜在问题,保障应用质量,减少生产环境故障。

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import com.fasterxml.jackson.databind.ObjectMapper;

import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

/**
 * 控制器单元测试示例
 * 使用@WebMvcTest只加载Web层组件
 */
@ExtendWith(SpringExtension.class)
@WebMvcTest(ProductController.class)
public class ProductControllerTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @Autowired
    private ObjectMapper objectMapper;
    
    @MockBean
    private ProductService productService;
    
    /**
     * 测试获取产品列表
     */
    @Test
    public void testGetAllProducts() throws Exception {
        // 准备测试数据
        List<Product> products = Arrays.asList(
            new Product(1L, "Product 1", 100.0),
            new Product(2L, "Product 2", 200.0)
        );
        
        // 配置Mock行为
        when(productService.findAll()).thenReturn(products);
        
        // 执行测试并验证结果
        mockMvc.perform(get("/api/products")
                .accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(2)))
                .andExpect(jsonPath("$[0].id").value(1))
                .andExpect(jsonPath("$[0].name").value("Product 1"))
                .andExpect(jsonPath("$[1].id").value(2))
                .andExpect(jsonPath("$[1].name").value("Product 2"));
        
        // 验证服务方法被调用
        verify(productService, times(1)).findAll();
    }
    
    /**
     * 测试创建产品
     */
    @Test
    public void testCreateProduct() throws Exception {
        // 准备测试数据
        Product newProduct = new Product(null, "New Product", 150.0);
        Product savedProduct = new Product(3L, "New Product", 150.0);
        
        // 配置Mock行为
        when(productService.create(any(Product.class))).thenReturn(savedProduct);
        
        // 执行测试并验证结果
        mockMvc.perform(post("/api/products")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(newProduct)))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.id").value(3))
                .andExpect(jsonPath("$.name").value("New Product"))
                .andExpect(jsonPath("$.price").value(150.0));
        
        // 验证服务方法被调用
        verify(productService, times(1)).create(any(Product.class));
    }
    
    /**
     * 测试参数验证失败的情况
     */
    @Test
    public void testCreateProductValidationFailure() throws Exception {
        // 准备无效的测试数据(空名称)
        Product invalidProduct = new Product(null, "", -10.0);
        
        // 执行测试并验证结果
        mockMvc.perform(post("/api/products")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(invalidProduct)))
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.fieldErrors.name").exists())
                .andExpect(jsonPath("$.fieldErrors.price").exists());
        
        // 验证服务方法未被调用
        verify(productService, never()).create(any(Product.class));
    }
    
    /**
     * 测试处理异常的情况
     */
    @Test
    public void testHandleNotFoundException() throws Exception {
        // 配置Mock行为抛出异常
        when(productService.findById(99L)).thenThrow(new ResourceNotFoundException("Product not found"));
        
        // 执行测试并验证结果
        mockMvc.perform(get("/api/products/99")
                .accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isNotFound())
                .andExpect(jsonPath("$.status").value(404))
                .andExpect(jsonPath("$.message").value("Product not found"));
    }
}

/**
 * 使用Spring Boot的集成测试示例
 */
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ProductApiIntegrationTest {
    
    @Autowired
    private TestRestTemplate restTemplate;
    
    @Autowired
    private ProductRepository productRepository;
    
    @BeforeEach
    public void setup() {
        // 清空数据库并插入测试数据
        productRepository.deleteAll();
        productRepository.save(new Product(null, "Test Product", 99.99));
    }
    
    /**
     * 端到端测试产品API
     */
    @Test
    public void testProductApiEndToEnd() {
        // 获取所有产品
        ResponseEntity<Product[]> getResponse = restTemplate.getForEntity(
            "/api/products", Product[].class);
        assertThat(getResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(getResponse.getBody().length).isEqualTo(1);
        
        // 创建新产品
        Product newProduct = new Product(null, "New Product", 123.45);
        ResponseEntity<Product> createResponse = restTemplate.postForEntity(
            "/api/products", newProduct, Product.class);
        assertThat(createResponse.getStatusCode()).isEqualTo(HttpStatus.CREATED);
        assertThat(createResponse.getBody().getId()).isNotNull();
        
        // 获取特定产品
        Long newId = createResponse.getBody().getId();
        ResponseEntity<Product> getOneResponse = restTemplate.getForEntity(
            "/api/products/" + newId, Product.class);
        assertThat(getOneResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(getOneResponse.getBody().getName()).isEqualTo("New Product");
        
        // 删除产品
        restTemplate.delete("/api/products/" + newId);
        
        // 验证删除成功
        ResponseEntity<Product> getDeletedResponse = restTemplate.getForEntity(
            "/api/products/" + newId, Product.class);
        assertThat(getDeletedResponse.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
    }
}

总结

@Controller注解是SpringMVC框架的核心组件之一,它为Java Web开发提供了强大而灵活的控制器定义机制。本文全面剖析了@Controller及其相关注解的使用方法与工作原理,从基础的请求映射到高级的RESTful API开发,从参数绑定与数据校验到全局控制器建议,系统性地展示了SpringMVC控制器的丰富功能。通过合理使用这些注解,开发者可以构建出结构清晰、功能完善的Web应用,有效处理各种复杂的HTTP请求与响应。值得注意的是,良好的控制器设计应遵循单一职责原则,保持代码的简洁性和可测试性,同时结合SpringMVC提供的测试工具,确保控制器的质量和稳定性。随着微服务架构的流行,@RestController在API开发中的重要性日益凸显,掌握这些注解的精髓对于现代Java开发者而言至关重要。通过深入理解SpringMVC控制器的定义方式,开发者能够更加高效地构建企业级Web应用,为用户提供稳定可靠的服务。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2310486.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

免费分享一个软件SKUA-GOCAD-2022版本

若有需要&#xff0c;可以下载。 下载地址 通过网盘分享的文件&#xff1a;Paradigm SKUA-GOCAD 22 build 2022.06.20 (x64).rar 链接: https://pan.baidu.com/s/10plenNcMDftzq3V-ClWpBg 提取码: tm3b 安装教程 Paradigm SKUA-GOCAD 2022版本v2022.06.20安装和破解教程-CS…

学习threejs,使用LineBasicMaterial基础线材质

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.LineBasicMaterial1.…

java面试题(一)基础部分

1.【String】StringBuffer和StringBuilder区别&#xff1f; String对象是final修饰的不可变的。对String对象的任何操作只会生成新对象&#xff0c;不会对原有对象进行操作。 StringBuilder和StringBuffer是可变的。 其中StringBuilder线程不安全&#xff0c;但开销小。 St…

Mac mini M4安装nvm 和node

先要安装Homebrew&#xff08;如果尚未安装&#xff09;。在终端中输入以下命令&#xff1a; /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)" 根据提示操作完成Homebrew的安装。 安装nvm。在终端中输入以下命令&#xf…

Ubuntu20.04双系统安装及软件安装(四):国内版火狐浏览器

Ubuntu20.04双系统安装及软件安装&#xff08;四&#xff09;&#xff1a;国内版火狐浏览器 Ubuntu系统会自带火狐浏览器&#xff0c;但该浏览器不是国内版的&#xff0c;如果平常有记录书签、浏览记录、并且经常使用浏览器插件的习惯&#xff0c;建议重装火狐浏览器为国内版的…

react中如何使用使用react-redux进行数据管理

以上就是react-redux的使用过程&#xff0c;下面我们开始优化部分&#xff1a;当一个组件只有一个render生命周期&#xff0c;那么我们可以改写成一个无状态组件&#xff08;UI组件到无状态组件&#xff0c;性能提升更好&#xff09;

DeepSeek使用手册分享-附PDF下载连接

本次主要分享DeepSeek从技术原理到使用技巧内容&#xff0c;这里展示一些基本内容&#xff0c;后面附上详细PDF下载链接。 DeepSeek基本介绍 DeepSeek公司和模型的基本简介&#xff0c;以及DeepSeek高性能低成本获得业界的高度认可的原因。 DeepSeek技术路线解析 DeepSeek V3…

新品速递 | 多通道可编程衰减器+矩阵系统,如何破解复杂通信测试难题?

在无线通信技术快速迭代的今天&#xff0c;多通道可编程数字射频衰减器和衰减矩阵已成为测试领域不可或缺的核心工具。它们凭借高精度、灵活配置和强大的多通道协同能力&#xff0c;为5G、物联网、卫星通信等前沿技术的研发与验证提供了关键支持。从基站性能测试到终端设备校准…

Data truncation: Out of range value for column ‘allow_invite‘ at row 1

由于前端传递的数值超过了mysql数据库中tinyint类型的取值范围&#xff0c;所以就会报错。 Caused by: com.mysql.cj.jdbc.exceptions.MysqlDataTruncation: Data truncation: Out of range value for column allow_invite at row 1at com.mysql.cj.jdbc.exceptions.SQLExcept…

HCIA—IP路由静态

一、概念及作用 1、概念&#xff1a;IP路由是指在IP网络中&#xff0c;数据从源节点到目的节点所经过的路径选择和数据转发的过程。 2、作用 ①实现网络互联&#xff1a;使不同网段的设备能够相互通信&#xff0c;构建大规模的互联网络 ②优化网络拓扑&#xff1a;根据网络…

Hz的DP总结

前言&#xff1a; 鉴于本人是一个DP低手&#xff0c;以后每写一道DP都会在本篇博客下进行更新&#xff0c;包括解题思路&#xff0c;方法&#xff0c;尽量做到分类明确&#xff0c;其中的题目来自包括但并不限于牛客&#xff0c;洛谷&#xff0c;CodeForces&#xff0c;AtCode…

【三极管8050和8550贴片封装区分脚位】

这里写自定义目录标题 三极管8050和8550贴片封装区分脚位三极管8050三极管8550 三极管8050和8550贴片封装区分脚位 三极管8050 增加了 检查列表 功能。 [ NPN型三极管&#xff08;SS8050&#xff09; ]: SS8050的使用及引脚判断方法 三极管8550

C# Unity 唐老狮 No.6 模拟面试题

本文章不作任何商业用途 仅作学习与交流 安利唐老狮与其他老师合作的网站,内有大量免费资源和优质付费资源,我入门就是看唐老师的课程 打好坚实的基础非常非常重要: 全部 - 游习堂 - 唐老狮创立的游戏开发在线学习平台 - Powered By EduSoho 如果你发现了文章内特殊的字体格式,…

二、Visual Studio2022配置OpenGL环境

文章目录 一、OpenGL库的下载二、OpenGL环境配置三、测试代码演示 一、OpenGL库的下载 OpenGL配置的库是GLFWGLAD &#xff0c;GLFW 主要用于创建 OpenGL 窗口和管理输入&#xff1b;GLAD 主要用于加载 OpenGL 函数 GLFW下载地址 下载Windows的32bit版本即可。 下载完成解压如…

YOLOv8改进------------SPFF-LSKA

YOLOv8改进------------SPFF-LSKA 1、LSAK.py代码2、添加YAML文件yolov8_SPPF_LSKA.yaml3、添加SPPF_LSKA代码4、ultralytics/nn/modules/__init__.py注册模块5、ultralytics/nn/tasks.py注册模块6、导入yaml文件训练 1、LSAK.py代码 论文 代码 LSKA.py添加到ultralytics/nn/…

240 Vocabulary Words Kids Need to Know

《240 Vocabulary Words Kids Need to Know》是美国学乐出版社&#xff08;Scholastic&#xff09;推出的词汇学习系列练习册&#xff0c;专为美国小学阶段&#xff08;G1-G6&#xff09;设计&#xff0c;基于CCSS&#xff08;美国共同核心州立标准&#xff09;编写&#xff0c…

AI-Deepseek + PPT

01--Deepseek提问 首先去Deepseek问一个问题&#xff1a; Deepseek的回答&#xff1a; 在汽车CAN总线通信中&#xff0c;DBC文件里的信号处理&#xff08;如初始值、系数、偏移&#xff09;主要是为了 将原始二进制数据转换为实际物理值&#xff0c;确保不同电子控制单元&…

【五.LangChain技术与应用】【8.LangChain提示词模板基础:从入门到精通】

早上八点,你端着咖啡打开IDE,老板刚甩来需求:“做个能自动生成产品描述的AI工具”。你自信满满地打开ChatGPT的API文档,结果半小时后对着满屏的"输出结果不稳定"、"格式总出错"抓耳挠腮——这时候你真需要好好认识下LangChain里的提示词模板了。 一、…

LeetCode 718.最长重复子数组(动态规划,Python)

给两个整数数组 nums1 和 nums2 &#xff0c;返回 两个数组中 公共的 、长度最长的子数组的长度 。 示例 1&#xff1a; 输入&#xff1a;nums1 [1,2,3,2,1], nums2 [3,2,1,4,7] 输出&#xff1a;3 解释&#xff1a;长度最长的公共子数组是 [3,2,1] 。 示例 2&#xff1a; 输…

python-leetcode-零钱兑换 II

518. 零钱兑换 II - 力扣&#xff08;LeetCode&#xff09; 这个问题是 完全背包问题 的一个变体&#xff0c;可以使用 动态规划 来解决。我们定义 dp[i] 为凑成金额 i 的硬币组合数。 思路&#xff1a; 定义 DP 数组 设 dp[i] 表示凑成金额 i 的组合数&#xff0c;初始化 dp[…