写完代码启动项目,访问接口返回404或500,这是开发中最常见的场景。这节教你3步快速定位问题。
一、先搞清楚404和500的区别
| 状态码 | 含义 | 通俗解释 |
|---|---|---|
| 404 | Not Found | 请求的地址找不到,路径对不上 |
| 500 | Internal Server Error | 服务器内部出错了,代码有bug |
| 401 | Unauthorized | 没登录或Token过期 |
| 403 | Forbidden | 登录了但没权限 |
| 400 | Bad Request | 请求参数不对 |
404是路径问题,500是代码问题。 定位思路完全不同。
二、404定位:3步找到问题
第1步:确认请求URL
F12 → Network → 找到报404的请求 → 看Request URL:
Request URL: http://localhost:8080/api/user/list
记录下完整的URL路径。
第2步:确认后端路由
在后端代码里找到对应的Controller:
@RestController
@RequestMapping("/api/user")
public class UserController {
@GetMapping("/list")
public Result list() {
return userService.list();
}
}
完整路径 = 类上的@RequestMapping + 方法上的@GetMapping/@PostMapping
所以这个接口的完整路径是:/api/user/list
第3步:对比路径
| 检查点 | 常见错误 |
|---|---|
| 路径拼写 | /user/list vs /user/List(大小写) |
| 前后斜杠 | /api/user/list vs /api/user/list/(尾部斜杠) |
| context-path | 配了/api但请求没加,或反过来 |
| 请求方法 | 后端是@GetMapping但前端用POST |
404常见原因
原因1:context-path没配对
server:
servlet:
context-path: /api
配了context-path后,所有接口前面都要加/api:
- 后端
@RequestMapping("/user")→ 实际路径是/api/user - 前端请求要发到
http://localhost:8080/api/user
原因2:请求方法不匹配
@PostMapping("/add") // 后端只接受POST
前端发GET请求就404:
// 错误:用了GET
axios.get('/api/user/add')
// 正确:用POST
axios.post('/api/user/add', data)
原因3:Controller没被扫描到
SpringBoot默认扫描启动类所在包及其子包。如果Controller在启动类包的外面,扫描不到。
// 启动类在 com.company.project
@SpringBootApplication
public class App { ... }
// Controller在 com.company.controller(不在project的子包下)
@RestController // 不会被扫描到!
解决:加扫描注解
@SpringBootApplication(scanBasePackages = "com.company")
或
@ComponentScan("com.company")
原因4:接口被拦截器拦了
有些项目有拦截器或过滤器,没登录的请求直接返回404或401。
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, ...) {
String token = request.getHeader("Authorization");
if (token == null) {
response.setStatus(401);
return false; // 拦截
}
return true;
}
}
检查拦截器配置,确认测试接口没有被拦截。
三、500定位:3步找到bug
第1步:看响应体
F12 → Network → 找到500的请求 → Response标签页:
{
"timestamp": "2026-01-01T10:00:00.000+00:00",
"status": 500,
"error": "Internal Server Error",
"message": "Cannot invoke \"String.length()\" because \"str\" is null",
"path": "/api/user/info"
}
message字段通常有报错信息,告诉你具体哪里出错了。
第2步:看后端日志
IDEA控制台搜索ERROR或Exception,找到完整的错误堆栈:
java.lang.NullPointerException: Cannot invoke "String.length()" because "str" is null
at com.company.project.service.impl.UserServiceImpl.getInfo(UserServiceImpl.java:45)
at com.company.project.controller.UserController.info(UserController.java:30)
...
看堆栈的技巧:找你自己写的代码。
堆栈里会有很多Spring框架内部的方法调用,忽略它们,找com.公司名开头的行:
at com.company.project.service.impl.UserServiceImpl.getInfo(UserServiceImpl.java:45)
这行告诉你:UserServiceImpl类的第45行出了空指针。
第3步:打断点调试
在报错的那行代码打断点:
- 找到
UserServiceImpl.java第45行 - 左边栏点一下,出现红点
- 重新请求接口
- 程序停在断点处
- 查看变量值,确认哪个是null
- 找到根因,修复
四、500常见原因
原因1:空指针异常(最常见)
java.lang.NullPointerException
常见场景:
// 场景1:对象没注入
@Autowired
private UserService userService; // userService为null
// 场景2:方法返回null
String name = user.getName(); // user为null
// 场景3:集合为null
List<User> list = mapper.selectList(null);
int size = list.size(); // list可能为null
排查方法: 打断点看哪个变量是null,加上null判断:
if (user != null && user.getName() != null) {
// 处理逻辑
}
或用Optional:
String name = Optional.ofNullable(user)
.map(User::getName)
.orElse("default");
原因2:参数类型转换错误
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
常见场景:
// 从Redis取数据,类型不对
Object obj = redisTemplate.opsForValue().get("key");
Integer num = (Integer) obj; // obj实际是String
解决: 用安全转换:
String str = String.valueOf(obj);
Integer num = Integer.parseInt(str);
原因3:SQL执行出错
java.sql.SQLSyntaxErrorException: Table 'my_project.user' doesn't exist
或:
BadSqlGrammarException: ...
排查:
- 表是否存在:
SHOW TABLES; - 字段名是否正确
- SQL语法是否正确
MyBatis开启SQL打印,看实际执行的SQL:
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
原因4:JSON序列化/反序列化错误
HttpMessageNotReadableException: JSON parse error
常见原因:
- 前端传的JSON格式不对
- JSON字段名和Java类属性名不匹配
- 日期格式不对
排查: F12 → Network → Payload,看前端传的JSON:
{
"username": "test",
"createTime": "2026-01-01 10:00:00" // 格式可能不对
}
后端用@JsonFormat指定格式:
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
原因5:依赖注入失败
UnsatisfiedDependencyException: Error creating bean with name 'xxx'
原因: Spring容器创建Bean时失败了。
排查:
- 看具体的
Caused by信息 - 检查
@Service、@Component等注解是否加了 - 检查构造方法或
@Autowired的依赖是否能找到
五、调试技巧
1. 条件断点
不想每次请求都停,只在特定条件停:
右键断点 → Condition → 输入条件:
userId != null && userId.equals(1001)
只有userId等于1001时才停。
2. 日志打印
不想打断点,加日志看执行过程:
@PostMapping("/add")
public Result add(@RequestBody UserDTO dto) {
log.info("收到请求,参数:{}", dto);
// 处理逻辑
Result result = userService.add(dto);
log.info("处理完成,结果:{}", result);
return result;
}
3. Postman调试
用Postman单独调试接口,排除前端干扰:
- 先调登录接口,拿到Token
- Token加到后续请求的Header里
- 逐个调试有问题的接口
4. 全局异常处理
项目应该有全局异常处理,统一返回格式:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public Result handleException(Exception e) {
log.error("系统异常:", e);
return Result.error("系统异常:" + e.getMessage());
}
@ExceptionHandler(NullPointerException.class)
public Result handleNullPointer(NullPointerException e) {
log.error("空指针异常:", e);
return Result.error("参数不能为空");
}
@ExceptionHandler(BusinessException.class)
public Result handleBusiness(BusinessException e) {
log.error("业务异常:", e);
return Result.error(e.getMessage());
}
}
有了全局异常处理,500错误也会返回统一的JSON格式,方便前端处理。
六、快速定位流程
404定位流程
返回404
│
├─ URL路径对吗? → 对比前端URL和后端@RequestMapping
│ ├─ 不对 → 改路径
│ └─ 对 ↓
├─ context-path加了吗? → URL需要加context-path
│ ├─ 没加 → 加上
│ └─ 加了 ↓
├─ 请求方法对吗? → GET/POST/PUT/DELETE
│ ├─ 不对 → 改请求方法
│ └─ 对 ↓
├─ Controller被扫描到了吗? → 检查包路径
│ ├─ 没有 → 加scanBasePackages
│ └─ 扫描了 ↓
└─ 被拦截器拦了吗? → 检查拦截器配置
500定位流程
返回500
│
├─ 看Response的message → 有具体报错信息
│
├─ 看后端日志 → 搜ERROR/Exception
│ │
│ ├─ NullPointerException → 空指针,找null变量
│ ├─ ClassCastException → 类型转换错
│ ├─ SQLSyntaxErrorException → SQL语法错
│ ├─ HttpMessageNotReadableException → JSON格式错
│ └─ UnsatisfiedDependencyException → Bean创建失败
│
└─ 打断点调试 → 找到根因 → 修复
小结
| 状态码 | 本质 | 定位方法 |
|---|---|---|
| 404 | 路径不对 | 对比前端URL和后端路由 |
| 500 | 代码有bug | 看日志堆栈 + 断点调试 |
404查路径,500查代码。记住这个,遇到报错就不慌了。