
在使用spring boot构建restful api并结合postman进行测试时,常见的404 "not found" 错误往往源于对api路径的误解。本文将深入探讨spring boot中api路径的构成,特别是`@requestmapping`注解的使用,并指导如何正确配置postman请求url,以避免因包含不必要的应用上下文路径而导致的404错误,确保api请求能够准确路由到目标控制器方法。
1. 理解Spring Boot中的API路径映射
Spring Boot应用程序通过注解来定义API端点及其对应的处理方法。其中,@RestController 标识一个类为RESTful控制器,@RequestMapping、@GetMapping、@PostMapping 等注解则用于定义请求的URL路径和HTTP方法。
1.1 @RequestMapping 注解的作用
@RequestMapping 注解可以应用于类级别和方法级别:
- 类级别: 定义控制器中所有方法的共同基础路径。例如,@RequestMapping("/api") 意味着该控制器中的所有方法都将以 /api 开头。
- 方法级别: 定义具体方法的路径,它会与类级别的路径拼接形成完整的API路径。
1.2 应用程序上下文路径
默认情况下,Spring Boot应用程序在嵌入式服务器(如Tomcat)上运行时,其上下文路径是根路径 /。这意味着,除非在 application.properties 或 application.yml 中明确配置了 server.servlet.context-path 属性,否则应用程序的根URL就是 http://localhost:8080/ (假设端口是8080)。
例如,如果 application.properties 中没有 server.servlet.context-path 配置,那么你的应用程序的根路径就是 /。如果配置了 server.servlet.context-path=/my-app,那么应用程序的根路径将是 /my-app。
2. 404 "Not Found" 错误分析
当Spring Boot应用程序返回404错误时,通常表示服务器未能找到与请求URL匹配的资源或处理程序。这可能是由以下原因造成的:
- URL路径不匹配: 请求的URL与任何定义的API端点都不匹配。
- HTTP方法不匹配: 请求的HTTP方法(GET, POST, PUT, DELETE等)与API端点期望的方法不符。
- 控制器未被扫描: Spring Boot未能发现并注册控制器。
- 应用程序未启动或端口错误: 应用程序未成功启动,或者请求发送到了错误的端口。
在本文提供的场景中,Postman请求的URL是 http://localhost:8080/mdb-spring-boot-product-organizer/api/addProduct,而控制器代码如下:
package com.example.mdbspringbootproductorganizer.controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
// ... 其他导入
@RestController
@RequestMapping("/api") // 类级别路径
public class ProductController {
// ... 依赖注入
@PostMapping("/addProduct") // 方法级别路径
public String saveProduct(@RequestBody Product product) {
// ... 保存逻辑
return "Added product with id : " + product.getId();
}
// ... 其他方法
}从代码中可以看出,ProductController 的类级别路径是 /api,saveProduct 方法的路径是 /addProduct。因此,该方法的完整有效路径应该是 /api/addProduct。
问题在于请求URL中多了一个 /mdb-spring-boot-product-organizer。这个部分通常是项目名称,但在默认的Spring Boot配置下,它不应该作为API路径的一部分。Spring Boot应用程序通常直接运行在根上下文路径下,因此,控制器定义的 /api/addProduct 路径会直接映射到 http://localhost:8080/api/addProduct。
3. 解决方案:修正Postman请求URL
解决此404错误的关键是移除URL中不必要的应用程序上下文路径。
正确的Postman请求URL应该是:
http://localhost:8080/api/addProduct
操作步骤:
- 打开Postman。
- 选择请求方法: 对于 saveProduct 方法,应选择 POST。
- 输入正确的URL: 在URL输入框中填写 http://localhost:8080/api/addProduct。
-
配置请求体 (Body):
- 选择 Body 标签页。
- 选择 raw 类型。
- 选择 JSON 格式。
- 输入符合 Product 模型结构的JSON数据。
示例JSON请求体:
{
"id": 1,
"name": "Sample Product",
"listedPrice": 19.99,
"purchasePrice": 10.50,
"condition": "New",
"brand": "BrandX",
"shelf": "A",
"bin": 101
}4. 完整的Spring Boot应用示例代码
为了提供一个完整的上下文,以下是涉及到的关键代码片段:
4.1 Product 模型 (POJO)
package com.example.mdbspringbootproductorganizer.model;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
@Document(collection = "ProductInventory") // 映射到MongoDB集合
public class Product {
@Id // 标识为主键
private int id;
private String name;
private double listedPrice;
private double purchasePrice;
private String condition;
private String brand;
private char shelf;
private int bin;
// 构造函数
public Product(int id, String name, double listedPrice, double purchasePrice, String condition, String brand,
char shelf, int bin) {
this.id = id;
this.name = name;
this.listedPrice = listedPrice;
this.purchasePrice = purchasePrice;
this.condition = condition;
this.brand = brand;
this.shelf = shelf;
this.bin = bin;
}
// Getter和Setter方法
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public double getListedPrice() { return listedPrice; }
public void setListedPrice(double listedPrice) { this.listedPrice = listedPrice; }
public double getPurchasePrice() { return purchasePrice; }
public void setPurchasePrice(double purchasePrice) { this.purchasePrice = purchasePrice; }
public String getCondition() { return condition; }
public void setCondition(String condition) { this.condition = condition; }
public String getBrand() { return brand; }
public void setBrand(String brand) { this.brand = brand; }
public char getShelf() { return shelf; }
public void setShelf(char shelf) { this.shelf = shelf; }
public int getBin() { return bin; }
public void setBin(int bin) { this.bin = bin; }
@Override
public String toString() {
return "Product [id=" + id + ", name=" + name + ", listedPrice=" + listedPrice + ", purchasePrice="
+ purchasePrice + ", condition=" + condition + ", brand=" + brand + ", shelf=" + shelf + ", bin=" + bin
+ "]";
}
}4.2 ProductRepository 接口
package com.example.mdbspringbootproductorganizer.repository; import org.springframework.data.mongodb.repository.MongoRepository; import com.example.mdbspringbootproductorganizer.model.Product; public interface ProductRepository extends MongoRepository{ // MongoRepository 提供了基本的CRUD操作,无需额外实现 }
4.3 ProductController 控制器
package com.example.mdbspringbootproductorganizer.controller;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.mdbspringbootproductorganizer.model.Product;
import com.example.mdbspringbootproductorganizer.repository.ProductRepository;
@RestController
@RequestMapping("/api") // 所有API的基础路径
public class ProductController {
@Autowired
private ProductRepository repository;
@PostMapping("/addProduct") // POST请求到 /api/addProduct
public String saveProduct(@RequestBody Product product) {
repository.save(product);
return "Added product with id : " + product.getId();
}
@GetMapping("/findAllProducts") // GET请求到 /api/findAllProducts
public List getProducts() {
return repository.findAll();
}
@GetMapping("/findAllProducts/{id}") // GET请求到 /api/findAllProducts/{id}
public Optional getProduct(@PathVariable int id) {
return repository.findById(id);
}
@DeleteMapping("/delete/{id}") // DELETE请求到 /api/delete/{id}
public String deleteBook(@PathVariable int id) {
repository.deleteById(id);
return "Product deleted with id: " + id;
}
} 4.4 MdbSpringBootApplication 主类
package com.example.mdbspringbootproductorganizer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
@SpringBootApplication // 启用Spring Boot自动配置
@EnableMongoRepositories // 启用Spring Data MongoDB仓库
public class MdbSpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(MdbSpringBootApplication.class, args);
}
}5. 注意事项与调试技巧
- 检查应用程序日志: Spring Boot启动时会在控制台输出日志,确认应用程序是否成功启动,是否有任何错误或警告信息。例如,Started MdbSpringBootApplication... 表示启动成功。
- 确认端口: 确保Postman请求的端口(默认8080)与Spring Boot应用程序实际运行的端口一致。
- HTTP方法匹配: 再次检查Postman中选择的HTTP方法是否与控制器方法上的注解(如 @PostMapping)一致。
- 请求头 (Headers): 对于POST请求发送JSON数据,通常需要设置 Content-Type: application/json 请求头。Postman在选择 Body 为 raw 和 JSON 时会自动添加此头部。
- MongoDB连接: 确保MongoDB服务正在运行,并且应用程序的 application.properties 或 application.yml 中配置的MongoDB连接信息正确无误。
- 浏览器测试GET请求: 对于简单的GET请求,可以直接在浏览器中输入URL进行测试,例如 http://localhost:8080/api/findAllProducts,这有助于快速验证路径是否正确。
总结
Spring Boot应用程序的404 "Not Found" 错误,尤其在使用Postman进行API测试时,最常见的原因是请求URL与定义的API路径不匹配。核心在于理解 @RequestMapping 注解如何构建API路径,以及应用程序默认的上下文路径。通过移除URL中不必要的项目名称或应用程序上下文路径,并确保HTTP方法和请求体配置正确,即可有效解决此类问题。在开发过程中,仔细核对URL、查看应用程序日志以及利用调试工具是排查问题的关键。










