关于 debug:spring-boot
debug:spring-boot 是面向 Spring Boot 开发者的故障排查指南,介绍常见问题、诊断工具及系统化处理流程,涵盖复现、修复和验证。
主要功能
- 01排查 Bean 与启动错误
- 02涵盖 JPA、安全和配置问题
- 03使用 Actuator 与 JVM 诊断工具
- 04提供四阶段调试流程
- 05包含 Maven、Gradle、Docker 和 Kubernetes 命令
适用场景
- 01调查应用上下文启动失败
- 02追踪数据库或事务问题
- 03调试已部署的 Spring Boot 服务
- 04通过测试复现问题并验证修复
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ snakeo/ claude-debug-and-refactor-skills-plugin --skill debug:spring-boot安装后在 Claude Code 中输入/debug:spring-boot确认已加载。
详细介绍
Spring Boot 调试指南
你是一位 Spring Boot 调试专家。请按照以下系统化方法,高效诊断并解决问题。
常见错误模式
1. NoSuchBeanDefinitionException
症状:
- “Field xyz required a bean of type 'X' that could not be found”
- “No qualifying bean of type 'X' available”
调试步骤:
- 确认类上有
@Component、@Service、@Repository或@Controller注解 - 检查类是否位于
@ComponentScan扫描的包中(必须位于@SpringBootApplication类所在的包或其子包中) - 确认带有
@Bean方法的@Configuration类已加载 - 检查可能会排除该 Bean 的条件注解(
@ConditionalOnProperty、@Profile) - 检查
@Qualifier中的限定符名称是否有拼写错误
快速修复:
// Ensure main class is at root package
@SpringBootApplication
public class Application { ... }
// Explicit component scan if needed
@ComponentScan(basePackages = {"com.example.main", "com.example.other"})
// Check bean registration
@Autowired
private ApplicationContext context;
Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);
2. 应用启动失败
症状:
- “Web server failed to start. Port 8080 was already in use”
- “Application run failed”
- 上下文初始化错误
调试步骤:
- 检查端口冲突:
lsof -i :8080或netstat -an | grep 8080 - 查看完整堆栈跟踪,找到根本原因(向上滚动,越过 Spring 横幅)
- 如果使用 JPA,检查数据库连接
- 确认已设置所有必需的环境变量
- 检查 pom.xml 或 build.gradle 中是否缺少依赖项
快速修复:
# Change port if in use
server.port=8081
# Enable debug startup logging
debug=true
logging.level.org.springframework=DEBUG
# Fail fast on missing properties
spring.main.allow-bean-definition-overriding=false
3. 循环依赖
症状:
- “The dependencies of some of the beans in the application context form a cycle”
- “Requested bean is currently in creation”
调试步骤:
- 阅读错误消息中的依赖循环链(A -> B -> C -> A)
- 找出可以解除的依赖关系
- 考虑是否需要重构设计
快速修复:
// Option 1: Use @Lazy on one dependency
@Autowired
@Lazy
private ServiceB serviceB;
// Option 2: Use setter injection
private ServiceB serviceB;
@Autowired
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
}
// Option 3: Refactor to event-based communication
@EventListener
public void handleEvent(CustomEvent event) { ... }
4. JPA/Hibernate 问题
症状:
- “No EntityManager with actual transaction available”
- LazyInitializationException
- “Table doesn't exist” / 架构验证错误
- N+1 查询问题
调试步骤:
- 启用 SQL 日志,查看实际执行的查询
- 检查
@Transactional的位置(必须用于 public 方法) - 确认实体关系和级联类型
- 检查数据库架构是否与实体定义一致
快速修复:
# Enable SQL debugging
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
# Schema handling
spring.jpa.hibernate.ddl-auto=validate # Recommended for debugging
spring.jpa.hibernate.ddl-auto=update # Auto-update schema (dev only)
// Fix LazyInitializationException
@Transactional(readOnly = true)
public Entity getWithChildren(Long id) {
Entity e = repository.findById(id).orElseThrow();
e.getChildren().size(); // Force initialization
return e;
}
// Or use EntityGraph
@EntityGraph(attributePaths = {"children", "children.grandchildren"})
Optional<Entity> findById(Long id);
5. 安全配置问题
症状:
- 所有端点均返回 403 Forbidden
- 身份验证不起作用
- CORS 错误
- CSRF 令牌问题
调试步骤:
- 启用安全调试日志
- 检查过滤器链顺序
- 确认身份验证提供程序配置
- 查看 SecurityFilterChain Bean 配置
快速修复:
# Enable security debugging
logging.level.org.springframework.security=DEBUG
logging.level.org.springframework.security.web.FilterChainProxy=DEBUG
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/actuator/health").permitAll()
.anyRequest().authenticated()
)
.csrf(csrf -> csrf.disable()) // Only for APIs with token auth
.cors(Customizer.withDefaults());
return http.build();
}
}
6. 属性绑定失败
症状:
- “Failed to bind properties under 'x.y.z'”
- “Could not resolve placeholder”
- 未读取配置值
调试步骤:
- 检查属性文件位置(src/main/resources)
- 确认属性名称完全匹配(区分大小写,注意连字符与驼峰命名的区别)
- 检查激活的配置文件(
spring.profiles.active) - 确认
@ConfigurationProperties前缀匹配
快速修复:
// Debug property sources
@Autowired
private Environment env;
@PostConstruct
public void debugProperties() {
System.out.println("Active profiles: " + Arrays.toString(env.getActiveProfiles()));
System.out.println("Property value: " + env.getProperty("my.property"));
}
// Ensure @ConfigurationProperties is scanned
@EnableConfigurationProperties(MyProperties.class)
@SpringBootApplication
public class Application { ... }
# Add to see property resolution
logging.level.org.springframework.boot.context.properties=DEBUG
调试工具
Spring Boot Actuator
运行时诊断必备:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
# Expose all actuator endpoints (dev only)
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
关键端点:
/actuator/health- 应用健康状态/actuator/beans- 所有已注册的 Bean/actuator/env- 环境属性/actuator/mappings- 请求映射/actuator/configprops- 配置属性/actuator/conditions- 自动配置报告
远程 JVM 调试
# Maven
mvn spring-boot:run -Dspring-boot.run.jvmArguments="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"
# Gradle
./gradlew bootRun --debug-jvm
# Java directly
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 -jar app.jar
# Docker Compose
environment:
- JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
ports:
- "5005:5005"
Logback/Log4j2 配置
创建 src/main/resources/logback-spring.xml:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="org.springframework.web" level="DEBUG"/>
<logger name="org.hibernate.SQL" level="DEBUG"/>
<logger name="com.yourapp" level="DEBUG"/>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
Spring Boot DevTools
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
功能:
- 代码更改时自动重启
- 集成 LiveReload 浏览器
- 开发环境中宽松的属性绑定
- H2 控制台自动配置
IntelliJ IDEA Spring 调试器(2025.2+)
- 查看已加载的活动 Bean 及其元数据
- 在 .properties/.yaml 文件中内联查看实际属性值
- 检查 Bean 作用域、配置文件和上下文
- 查看数据库连接
- 跳转到 Bean 定义
四个阶段(Spring Boot 特定)
阶段 1:收集信息
# Check application logs
tail -f logs/spring.log
# Check JVM status
jps -lv
jstat -gc <pid>
jstack <pid>
# Check actuator endpoints
curl localhost:8080/actuator/health
curl localhost:8080/actuator/env
curl localhost:8080/actuator/beans
阶段 2:复现并隔离问题
// Create minimal test case
@SpringBootTest(classes = {TestConfig.class, ProblemBean.class})
class IsolatedTest {
@Autowired
private ProblemBean bean;
@Test
void reproduceIssue() {
// Minimal steps to reproduce
}
}
// Use test slices for faster isolation
@WebMvcTest(ProblemController.class)
@DataJpaTest
@JsonTest
阶段 3:诊断根本原因
// Add diagnostic logging
@Aspect
@Component
public class DiagnosticAspect {
private static final Logger log = LoggerFactory.getLogger(DiagnosticAspect.class);
@Around("execution(* com.yourapp.service.*.*(..))")
public Object logMethodExecution(ProceedingJoinPoint jp) throws Throwable {
log.debug("Entering: {}", jp.getSignature());
try {
Object result = jp.proceed();
log.debug("Exiting: {} with result: {}", jp.getSignature(), result);
return result;
} catch (Exception e) {
log.error("Exception in {}: {}", jp.getSignature(), e.getMessage());
throw e;
}
}
}
阶段 4:修复并验证
# Run targeted tests
./mvnw test -Dtest=ProblemTest
# Run full test suite
./mvnw verify
# Check for regressions
./mvnw test -Dtest=*IntegrationTest
快速参考命令
Maven
# Run with debug logging
./mvnw spring-boot:run -Dspring-boot.run.arguments="--debug"
# Run with specific profile
./mvnw spring-boot:run -Dspring-boot.run.profiles=dev
# Skip tests and run
./mvnw spring-boot:run -DskipTests
# Generate dependency tree
./mvnw dependency:tree
# Check for dependency conflicts
./mvnw dependency:analyze
# Force update dependencies
./mvnw clean install -U
Gradle
# Run with debug logging
./gradlew bootRun --args='--debug'
# Run with specific profile
./gradlew bootRun --args='--spring.profiles.active=dev'
# Show dependencies
./gradlew dependencies
# Refresh dependencies
./gradlew build --refresh-dependencies
Docker
# Check container logs
docker logs <container_name> -f
# Shell into container
docker exec -it <container_name> /bin/sh
# Check container health
docker inspect --format='{{.State.Health.Status}}' <container_name>
# View container environment
docker exec <container_name> env | grep SPRING
Kubernetes
# Get pod logs
kubectl logs <pod-name> -f
# Get previous container logs (after crash)
kubectl logs <pod-name> --previous
# Describe pod for events
kubectl describe pod <pod-name>
# Port forward for debugging
kubectl port-forward <pod-name> 5005:5005
# Shell into pod
kubectl exec -it <pod-name> -- /bin/sh
数据库调试
# Connect to PostgreSQL
psql -h localhost -U user -d database
# Show running queries
SELECT * FROM pg_stat_activity WHERE state = 'active';
# Connect to MySQL
mysql -h localhost -u user -p database
# Show process list
SHOW PROCESSLIST;
JVM 诊断
# List Java processes
jps -lv
# Thread dump
jstack <pid> > thread_dump.txt
# Heap dump
jmap -dump:format=b,file=heap.hprof <pid>
# GC stats
jstat -gcutil <pid> 1000
# JVM flags
jinfo -flags <pid>
微服务调试
分布式追踪
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
</dependency>
# Zipkin configuration
management.tracing.sampling.probability=1.0
management.zipkin.tracing.endpoint=http://zipkin:9411/api/v2/spans
关联 ID
@Component
public class CorrelationIdFilter extends OncePerRequestFilter {
private static final String CORRELATION_ID = "X-Correlation-ID";
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain) {
String correlationId = request.getHeader(CORRELATION_ID);
if (correlationId == null) {
correlationId = UUID.randomUUID().toString();
}
MDC.put("correlationId", correlationId);
response.setHeader(CORRELATION_ID, correlationId);
try {
chain.doFilter(request, response);
} finally {
MDC.remove("correlationId");
}
}
}
常见陷阱
- 在 private 方法上使用 @Transactional - 不起作用,方法必须是 public
- 在同一个类中调用 @Transactional 方法 - 会绕过代理,不会开启事务
- 自定义实现缺少 @Repository - 不会应用异常转换
- 在构造函数中使用 @Value - 字段尚未注入,请使用 @PostConstruct
- 带有 @Autowired 的静态字段 - 永远不会被注入
- 使用 @ComponentScan 时未指定 basePackages - 只扫描当前包及其子包
- application.properties 位于错误的位置 - 必须位于 src/main/resources 根目录
- 未加载特定配置文件的配置 - 检查是否已设置 spring.profiles.active
延伸阅读
评论
支持 Markdown