mcpField
D
debug:spring-boot

debug:spring-boot

github.com/snakeo开发941 次安装41 周前更新
代码仓库

关于 debug:spring-boot

debug:spring-boot 是面向 Spring Boot 开发者的故障排查指南,介绍常见问题、诊断工具及系统化处理流程,涵盖复现、修复和验证。

主要功能

  1. 01排查 Bean 与启动错误
  2. 02涵盖 JPA、安全和配置问题
  3. 03使用 Actuator 与 JVM 诊断工具
  4. 04提供四阶段调试流程
  5. 05包含 Maven、Gradle、Docker 和 Kubernetes 命令

适用场景

  1. 01调查应用上下文启动失败
  2. 02追踪数据库或事务问题
  3. 03调试已部署的 Spring Boot 服务
  4. 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”

调试步骤:

  1. 确认类上有 @Component、@Service、@Repository 或 @Controller 注解
  2. 检查类是否位于 @ComponentScan 扫描的包中(必须位于 @SpringBootApplication 类所在的包或其子包中)
  3. 确认带有 @Bean 方法的 @Configuration 类已加载
  4. 检查可能会排除该 Bean 的条件注解(@ConditionalOnProperty、@Profile)
  5. 检查 @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”
  • 上下文初始化错误

调试步骤:

  1. 检查端口冲突:lsof -i :8080 或 netstat -an | grep 8080
  2. 查看完整堆栈跟踪,找到根本原因(向上滚动,越过 Spring 横幅)
  3. 如果使用 JPA,检查数据库连接
  4. 确认已设置所有必需的环境变量
  5. 检查 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”

调试步骤:

  1. 阅读错误消息中的依赖循环链(A -> B -> C -> A)
  2. 找出可以解除的依赖关系
  3. 考虑是否需要重构设计

快速修复:

// 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 查询问题

调试步骤:

  1. 启用 SQL 日志,查看实际执行的查询
  2. 检查 @Transactional 的位置(必须用于 public 方法)
  3. 确认实体关系和级联类型
  4. 检查数据库架构是否与实体定义一致

快速修复:

# 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 令牌问题

调试步骤:

  1. 启用安全调试日志
  2. 检查过滤器链顺序
  3. 确认身份验证提供程序配置
  4. 查看 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”
  • 未读取配置值

调试步骤:

  1. 检查属性文件位置(src/main/resources)
  2. 确认属性名称完全匹配(区分大小写,注意连字符与驼峰命名的区别)
  3. 检查激活的配置文件(spring.profiles.active)
  4. 确认 @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");
        }
    }
}

常见陷阱

  1. 在 private 方法上使用 @Transactional - 不起作用,方法必须是 public
  2. 在同一个类中调用 @Transactional 方法 - 会绕过代理,不会开启事务
  3. 自定义实现缺少 @Repository - 不会应用异常转换
  4. 在构造函数中使用 @Value - 字段尚未注入,请使用 @PostConstruct
  5. 带有 @Autowired 的静态字段 - 永远不会被注入
  6. 使用 @ComponentScan 时未指定 basePackages - 只扫描当前包及其子包
  7. application.properties 位于错误的位置 - 必须位于 src/main/resources 根目录
  8. 未加载特定配置文件的配置 - 检查是否已设置 spring.profiles.active

延伸阅读

评论

支持 Markdown