在开发Spring Boot接口时,GET和POST的选择看似简单,实则暗藏玄机。我见过太多项目因为方法选择不当导致的安全漏洞、性能问题和后期维护噩梦。比如某电商平台曾错误地用GET实现支付接口,结果被恶意爬虫重复调用造成资金损失;另一个社交应用用POST获取公开数据,导致CDN缓存失效,服务器负载飙升30%。
RESTful规范中GET用于获取资源,POST用于创建资源,但实际开发中远不止这么简单。选择依据需要同时考虑语义正确性、安全性、性能、缓存机制、浏览器兼容性等多个维度。以下是新手常踩的坑:
GET和POST在HTTP协议中的定位完全不同。GET是幂等的安全方法(safe method),意味着多次执行不会改变资源状态。POST是非幂等的,每次调用可能导致资源变化。这个特性直接影响以下方面:
在TCP包层面,GET请求会把参数放在起始行(URL),而POST放在消息体。这导致:
http复制GET /api/users?id=123 HTTP/1.1
Host: example.com
vs
POST /api/users HTTP/1.1
Host: example.com
Content-Type: application/json
{"id": 123}
GET请求的参数限制主要来自浏览器和服务器:
POST理论上无限制,但实际需要考虑:
在Spring Boot中获取参数的典型方式:
java复制// GET /api/users?name=张三
@GetMapping
public List<User> getUsers(@RequestParam String name) {
//...
}
// POST /api/users
@PostMapping
public User createUser(@RequestBody UserDTO dto) {
//...
}
HTTP缓存对GET的支持最为完善:
条件请求(Conditional GET)
缓存控制头
公共缓存
POST请求默认不被缓存,但可通过特殊头控制:
http复制Cache-Control: no-cache
实测案例:某新闻列表接口改用GET后,CDN命中率从15%提升到82%,服务器负载下降65%。
虽然GET/POST在抓包面前都不安全,但GET有额外风险:
安全建议:
Spring Security配置示例:
java复制http.logging()
.enableLoggingRequestDetails(false); // 不记录敏感参数
GET和POST需要不同的CSRF防护:
错误示范:
java复制@GetMapping("/delete")
public void deleteUser(@RequestParam Long id) {
// 危险!GET执行删除操作
}
正确做法:
java复制@PostMapping("/users/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteUser(@PathVariable Long id) {
// 安全删除
}
GET请求的优化空间:
POST请求优化方案:
启用body压缩
properties复制# application.properties
server.compression.enabled=true
server.compression.mime-types=text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json
server.compression.min-response-size=1024
二进制协议(如Protocol Buffers)
java复制@PostMapping(value = "/data", consumes = "application/x-protobuf")
public byte[] processData(@RequestBody byte[] data) {
//...
}
HTTP/2下GET和POST都支持多路复用,但需要注意:
java复制@Bean
public TomcatServletWebServerFactory tomcatFactory() {
return new TomcatServletWebServerFactory() {
@Override
protected void customizeConnector(Connector connector) {
super.customizeConnector(connector);
connector.setProperty("connectionTimeout", "30000");
}
};
}
文件操作的特殊考量:
Spring Boot文件上传示例:
java复制@PostMapping("/upload")
public String handleUpload(@RequestParam MultipartFile file) {
String fileName = StringUtils.cleanPath(file.getOriginalFilename());
Path path = Paths.get("/uploads/" + fileName);
Files.copy(file.getInputStream(), path, StandardCopyOption.REPLACE_EXISTING);
return "Upload success";
}
现代Web应用的实践建议:
查询类API优先用GET
code复制GET /users?filter=age>25&sort=-createdAt
命令类API用POST/PUT/DELETE
plaintext复制是否修改服务器状态?
├─ 是 → POST/PUT/DELETE
└─ 否 → 是否需要缓存?
├─ 是 → GET
└─ 否 → 参数是否敏感?
├─ 是 → POST
└─ 否 → 参数是否过大?
├─ 是 → POST
└─ 否 → GET
在代码评审时需要检查:
问题现象:POST请求收不到参数
排查步骤:
GET和POST的CORS配置差异:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedMethods("GET", "POST", "PUT") // 显式声明允许的方法
.allowedOrigins("*");
}
}
测试不同方法的Mock示例:
java复制// GET测试
mockMvc.perform(get("/api/users?name=test"))
.andExpect(status().isOk());
// POST测试
String json = "{\"username\":\"test\"}";
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(json))
.andExpect(status().isCreated());
对于超媒体驱动的API,方法选择更复杂:
java复制@GetMapping("/orders/{id}")
public EntityModel<Order> getOrder(@PathVariable Long id) {
Order order = orderService.findById(id);
return EntityModel.of(order,
linkTo(methodOn(OrderController.class).getOrder(id)).withSelfRel(),
linkTo(methodOn(OrderController.class).payOrder(id)).withRel("pay"));
}
@PostMapping("/orders/{id}/pay")
public ResponseEntity<?> payOrder(@PathVariable Long id) {
// 支付逻辑
return ResponseEntity.noContent().build();
}
这种设计下,客户端不需要硬编码URL,而是通过链接关系发现可用操作。