本章将介绍 Spring Native 应用开发和部署过程中常见的问题及其解决方案,帮助开发者快速定位和解决问题。

1. 编译时问题

1.1 反射配置问题

问题描述

com.oracle.svm.core.util.UserError$UserException: 
Classes that should be initialized at run time got initialized during image building

解决方案

创建 ReflectionConfigurationGenerator.java

package com.example.springnative.debug;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.StreamUtils;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Pattern;

/**
 * 反射配置生成器
 * 用于自动生成 GraalVM 原生镜像所需的反射配置
 */
public class ReflectionConfigurationGenerator {
    
    private static final ObjectMapper objectMapper = new ObjectMapper();
    private static final Set<String> processedClasses = new HashSet<>();
    private static final List<ReflectionEntry> reflectionEntries = new ArrayList<>();
    
    public static void main(String[] args) throws Exception {
        System.out.println("开始生成反射配置...");
        
        // 启动 Spring 应用以收集反射信息
        SpringApplication app = new SpringApplication(Application.class);
        app.setAdditionalProfiles("reflection-analysis");
        
        try (ConfigurableApplicationContext context = app.run(args)) {
            // 分析 Spring Bean
            analyzeSpringBeans(context);
            
            // 分析 JPA 实体
            analyzeJpaEntities();
            
            // 分析 Jackson 序列化类
            analyzeJacksonClasses();
            
            // 分析自定义注解
            analyzeCustomAnnotations();
            
            // 生成配置文件
            generateReflectionConfig();
            
            System.out.println("反射配置生成完成!");
        }
    }
    
    private static void analyzeSpringBeans(ConfigurableApplicationContext context) {
        System.out.println("分析 Spring Bean...");
        
        String[] beanNames = context.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            try {
                Object bean = context.getBean(beanName);
                Class<?> beanClass = bean.getClass();
                
                // 跳过代理类
                if (beanClass.getName().contains("$$")) {
                    continue;
                }
                
                addReflectionEntry(beanClass, true, true, true);
                
                // 分析接口
                for (Class<?> interfaceClass : beanClass.getInterfaces()) {
                    addReflectionEntry(interfaceClass, false, false, false);
                }
                
            } catch (Exception e) {
                System.err.println("分析 Bean 失败: " + beanName + " - " + e.getMessage());
            }
        }
    }
    
    private static void analyzeJpaEntities() {
        System.out.println("分析 JPA 实体...");
        
        // 扫描带有 @Entity 注解的类
        List<Class<?>> entityClasses = findClassesWithAnnotation("javax.persistence.Entity");
        entityClasses.addAll(findClassesWithAnnotation("jakarta.persistence.Entity"));
        
        for (Class<?> entityClass : entityClasses) {
            addReflectionEntry(entityClass, true, true, true);
            
            // 分析字段
            Arrays.stream(entityClass.getDeclaredFields())
                .forEach(field -> {
                    addReflectionEntry(field.getType(), false, false, false);
                });
        }
    }
    
    private static void analyzeJacksonClasses() {
        System.out.println("分析 Jackson 序列化类...");
        
        // 扫描 DTO 和 Request/Response 类
        List<String> packages = Arrays.asList(
            "com.example.springnative.dto",
            "com.example.springnative.model",
            "com.example.springnative.request",
            "com.example.springnative.response"
        );
        
        for (String packageName : packages) {
            List<Class<?>> classes = findClassesInPackage(packageName);
            for (Class<?> clazz : classes) {
                addReflectionEntry(clazz, true, true, false);
            }
        }
    }
    
    private static void analyzeCustomAnnotations() {
        System.out.println("分析自定义注解...");
        
        List<Class<?>> annotationClasses = findClassesWithAnnotation("java.lang.annotation.Retention");
        
        for (Class<?> annotationClass : annotationClasses) {
            if (annotationClass.getPackage().getName().startsWith("com.example")) {
                addReflectionEntry(annotationClass, false, false, false);
            }
        }
    }
    
    private static void addReflectionEntry(Class<?> clazz, boolean allDeclaredConstructors, 
                                         boolean allDeclaredMethods, boolean allDeclaredFields) {
        if (processedClasses.contains(clazz.getName())) {
            return;
        }
        
        processedClasses.add(clazz.getName());
        
        ReflectionEntry entry = new ReflectionEntry();
        entry.name = clazz.getName();
        entry.allDeclaredConstructors = allDeclaredConstructors;
        entry.allDeclaredMethods = allDeclaredMethods;
        entry.allDeclaredFields = allDeclaredFields;
        
        reflectionEntries.add(entry);
    }
    
    private static List<Class<?>> findClassesWithAnnotation(String annotationName) {
        // 实际实现中应该使用类路径扫描
        // 这里简化为示例
        return new ArrayList<>();
    }
    
    private static List<Class<?>> findClassesInPackage(String packageName) {
        // 实际实现中应该使用类路径扫描
        // 这里简化为示例
        return new ArrayList<>();
    }
    
    private static void generateReflectionConfig() throws IOException {
        System.out.println("生成 reflect-config.json...");
        
        String json = objectMapper.writerWithDefaultPrettyPrinter()
            .writeValueAsString(reflectionEntries);
        
        // 写入文件
        System.out.println("生成的反射配置:");
        System.out.println(json);
        
        // 保存到 META-INF/native-image 目录
        // Files.write(Paths.get("src/main/resources/META-INF/native-image/reflect-config.json"), 
        //            json.getBytes(StandardCharsets.UTF_8));
    }
    
    static class ReflectionEntry {
        public String name;
        public boolean allDeclaredConstructors;
        public boolean allDeclaredMethods;
        public boolean allDeclaredFields;
    }
}

1.2 资源配置问题

问题描述

java.io.FileNotFoundException: Resource not found: application.yml

解决方案

创建 ResourceConfigurationGenerator.java

package com.example.springnative.debug;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import java.io.IOException;
import java.util.*;
import java.util.regex.Pattern;

/**
 * 资源配置生成器
 * 用于自动生成 GraalVM 原生镜像所需的资源配置
 */
public class ResourceConfigurationGenerator {
    
    private static final ObjectMapper objectMapper = new ObjectMapper();
    private static final Set<String> resourcePatterns = new HashSet<>();
    
    public static void main(String[] args) throws IOException {
        System.out.println("开始生成资源配置...");
        
        // 扫描配置文件
        scanConfigurationFiles();
        
        // 扫描模板文件
        scanTemplateFiles();
        
        // 扫描静态资源
        scanStaticResources();
        
        // 扫描消息文件
        scanMessageFiles();
        
        // 生成配置文件
        generateResourceConfig();
        
        System.out.println("资源配置生成完成!");
    }
    
    private static void scanConfigurationFiles() throws IOException {
        System.out.println("扫描配置文件...");
        
        String[] patterns = {
            "classpath*:application*.yml",
            "classpath*:application*.yaml",
            "classpath*:application*.properties",
            "classpath*:bootstrap*.yml",
            "classpath*:bootstrap*.yaml",
            "classpath*:bootstrap*.properties",
            "classpath*:logback*.xml",
            "classpath*:log4j*.xml"
        };
        
        for (String pattern : patterns) {
            scanResources(pattern);
        }
    }
    
    private static void scanTemplateFiles() throws IOException {
        System.out.println("扫描模板文件...");
        
        String[] patterns = {
            "classpath*:templates/**/*.html",
            "classpath*:templates/**/*.ftl",
            "classpath*:templates/**/*.vm",
            "classpath*:templates/**/*.mustache"
        };
        
        for (String pattern : patterns) {
            scanResources(pattern);
        }
    }
    
    private static void scanStaticResources() throws IOException {
        System.out.println("扫描静态资源...");
        
        String[] patterns = {
            "classpath*:static/**/*",
            "classpath*:public/**/*",
            "classpath*:resources/**/*",
            "classpath*:META-INF/resources/**/*"
        };
        
        for (String pattern : patterns) {
            scanResources(pattern);
        }
    }
    
    private static void scanMessageFiles() throws IOException {
        System.out.println("扫描消息文件...");
        
        String[] patterns = {
            "classpath*:messages*.properties",
            "classpath*:ValidationMessages*.properties",
            "classpath*:i18n/**/*.properties"
        };
        
        for (String pattern : patterns) {
            scanResources(pattern);
        }
    }
    
    private static void scanResources(String pattern) throws IOException {
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        Resource[] resources = resolver.getResources(pattern);
        
        for (Resource resource : resources) {
            if (resource.exists() && resource.isReadable()) {
                String path = getResourcePath(resource);
                if (path != null) {
                    resourcePatterns.add(path);
                }
            }
        }
    }
    
    private static String getResourcePath(Resource resource) {
        try {
            String uri = resource.getURI().toString();
            
            // 提取类路径相对路径
            if (uri.contains("!")) {
                String[] parts = uri.split("!");
                if (parts.length > 1) {
                    return parts[1].startsWith("/") ? parts[1].substring(1) : parts[1];
                }
            } else if (uri.contains("classes/")) {
                int index = uri.indexOf("classes/");
                return uri.substring(index + 8);
            }
            
            return null;
        } catch (Exception e) {
            return null;
        }
    }
    
    private static void generateResourceConfig() throws IOException {
        System.out.println("生成 resource-config.json...");
        
        Map<String, Object> config = new HashMap<>();
        
        List<Map<String, String>> resources = new ArrayList<>();
        List<Map<String, String>> bundles = new ArrayList<>();
        
        for (String pattern : resourcePatterns) {
            if (pattern.endsWith(".properties") && 
                (pattern.contains("messages") || pattern.contains("ValidationMessages"))) {
                // 消息包
                Map<String, String> bundle = new HashMap<>();
                bundle.put("name", pattern.replaceAll("\\.properties$", "").replace("/", "."));
                bundles.add(bundle);
            } else {
                // 普通资源
                Map<String, String> resource = new HashMap<>();
                resource.put("pattern", pattern);
                resources.add(resource);
            }
        }
        
        config.put("resources", Map.of("includes", resources));
        if (!bundles.isEmpty()) {
            config.put("bundles", bundles);
        }
        
        String json = objectMapper.writerWithDefaultPrettyPrinter()
            .writeValueAsString(config);
        
        System.out.println("生成的资源配置:");
        System.out.println(json);
        
        // 保存到 META-INF/native-image 目录
        // Files.write(Paths.get("src/main/resources/META-INF/native-image/resource-config.json"), 
        //            json.getBytes(StandardCharsets.UTF_8));
    }
}

1.3 序列化配置问题

问题描述

com.oracle.svm.core.util.UserError$UserException: 
Serialization support for class is not configured

解决方案

创建 SerializationConfigurationGenerator.java

package com.example.springnative.debug;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

import java.io.IOException;
import java.io.Serializable;
import java.util.*;

/**
 * 序列化配置生成器
 * 用于自动生成 GraalVM 原生镜像所需的序列化配置
 */
public class SerializationConfigurationGenerator {
    
    private static final ObjectMapper objectMapper = new ObjectMapper();
    private static final Set<String> serializableClasses = new HashSet<>();
    
    public static void main(String[] args) throws Exception {
        System.out.println("开始生成序列化配置...");
        
        // 启动 Spring 应用以收集序列化信息
        SpringApplication app = new SpringApplication(Application.class);
        app.setAdditionalProfiles("serialization-analysis");
        
        try (ConfigurableApplicationContext context = app.run(args)) {
            // 分析 Serializable 类
            analyzeSerializableClasses();
            
            // 分析 Session 属性类
            analyzeSessionAttributes();
            
            // 分析缓存对象类
            analyzeCacheObjects();
            
            // 分析消息队列对象类
            analyzeMessageObjects();
            
            // 生成配置文件
            generateSerializationConfig();
            
            System.out.println("序列化配置生成完成!");
        }
    }
    
    private static void analyzeSerializableClasses() {
        System.out.println("分析 Serializable 类...");
        
        // 扫描实现 Serializable 接口的类
        List<String> packages = Arrays.asList(
            "com.example.springnative.model",
            "com.example.springnative.dto",
            "com.example.springnative.entity",
            "com.example.springnative.exception"
        );
        
        for (String packageName : packages) {
            List<Class<?>> classes = findClassesInPackage(packageName);
            for (Class<?> clazz : classes) {
                if (Serializable.class.isAssignableFrom(clazz)) {
                    serializableClasses.add(clazz.getName());
                }
            }
        }
    }
    
    private static void analyzeSessionAttributes() {
        System.out.println("分析 Session 属性类...");
        
        // 添加常见的 Session 属性类
        serializableClasses.add("java.lang.String");
        serializableClasses.add("java.lang.Integer");
        serializableClasses.add("java.lang.Long");
        serializableClasses.add("java.util.HashMap");
        serializableClasses.add("java.util.ArrayList");
        serializableClasses.add("java.util.Date");
        
        // 添加 Spring Security 相关类
        serializableClasses.add("org.springframework.security.core.userdetails.User");
        serializableClasses.add("org.springframework.security.core.authority.SimpleGrantedAuthority");
        serializableClasses.add("org.springframework.security.authentication.UsernamePasswordAuthenticationToken");
    }
    
    private static void analyzeCacheObjects() {
        System.out.println("分析缓存对象类...");
        
        // 添加缓存中可能存储的对象类型
        List<String> cacheablePackages = Arrays.asList(
            "com.example.springnative.dto",
            "com.example.springnative.model"
        );
        
        for (String packageName : cacheablePackages) {
            List<Class<?>> classes = findClassesInPackage(packageName);
            for (Class<?> clazz : classes) {
                // 检查是否有 @Cacheable 注解
                if (hasAnnotation(clazz, "org.springframework.cache.annotation.Cacheable")) {
                    serializableClasses.add(clazz.getName());
                }
            }
        }
    }
    
    private static void analyzeMessageObjects() {
        System.out.println("分析消息队列对象类...");
        
        // 添加消息队列中可能传输的对象类型
        List<String> messagePackages = Arrays.asList(
            "com.example.springnative.message",
            "com.example.springnative.event"
        );
        
        for (String packageName : messagePackages) {
            List<Class<?>> classes = findClassesInPackage(packageName);
            for (Class<?> clazz : classes) {
                serializableClasses.add(clazz.getName());
            }
        }
    }
    
    private static List<Class<?>> findClassesInPackage(String packageName) {
        // 实际实现中应该使用类路径扫描
        // 这里简化为示例
        return new ArrayList<>();
    }
    
    private static boolean hasAnnotation(Class<?> clazz, String annotationName) {
        // 实际实现中应该检查注解
        // 这里简化为示例
        return false;
    }
    
    private static void generateSerializationConfig() throws IOException {
        System.out.println("生成 serialization-config.json...");
        
        List<Map<String, String>> entries = new ArrayList<>();
        
        for (String className : serializableClasses) {
            Map<String, String> entry = new HashMap<>();
            entry.put("name", className);
            entries.add(entry);
        }
        
        String json = objectMapper.writerWithDefaultPrettyPrinter()
            .writeValueAsString(entries);
        
        System.out.println("生成的序列化配置:");
        System.out.println(json);
        
        // 保存到 META-INF/native-image 目录
        // Files.write(Paths.get("src/main/resources/META-INF/native-image/serialization-config.json"), 
        //            json.getBytes(StandardCharsets.UTF_8));
    }
}

2. 运行时问题

2.1 内存问题诊断

创建 MemoryDiagnostics.java

package com.example.springnative.debug;

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import java.util.HashMap;
import java.util.Map;

/**
 * 内存诊断工具
 * 用于监控和诊断内存使用情况
 */
@Component
public class MemoryDiagnostics implements HealthIndicator {
    
    private final MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
    
    @Override
    public Health health() {
        try {
            Map<String, Object> details = getMemoryDetails();
            
            // 检查内存使用率
            double heapUsageRatio = (Double) details.get("heapUsageRatio");
            double nonHeapUsageRatio = (Double) details.get("nonHeapUsageRatio");
            
            if (heapUsageRatio > 0.9 || nonHeapUsageRatio > 0.9) {
                return Health.down()
                    .withDetails(details)
                    .withDetail("warning", "内存使用率过高")
                    .build();
            } else if (heapUsageRatio > 0.8 || nonHeapUsageRatio > 0.8) {
                return Health.up()
                    .withDetails(details)
                    .withDetail("warning", "内存使用率较高")
                    .build();
            } else {
                return Health.up()
                    .withDetails(details)
                    .build();
            }
        } catch (Exception e) {
            return Health.down()
                .withDetail("error", e.getMessage())
                .build();
        }
    }
    
    public Map<String, Object> getMemoryDetails() {
        Map<String, Object> details = new HashMap<>();
        
        // 堆内存信息
        MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
        details.put("heapInit", formatBytes(heapUsage.getInit()));
        details.put("heapUsed", formatBytes(heapUsage.getUsed()));
        details.put("heapCommitted", formatBytes(heapUsage.getCommitted()));
        details.put("heapMax", formatBytes(heapUsage.getMax()));
        details.put("heapUsageRatio", (double) heapUsage.getUsed() / heapUsage.getMax());
        
        // 非堆内存信息
        MemoryUsage nonHeapUsage = memoryBean.getNonHeapMemoryUsage();
        details.put("nonHeapInit", formatBytes(nonHeapUsage.getInit()));
        details.put("nonHeapUsed", formatBytes(nonHeapUsage.getUsed()));
        details.put("nonHeapCommitted", formatBytes(nonHeapUsage.getCommitted()));
        details.put("nonHeapMax", formatBytes(nonHeapUsage.getMax()));
        details.put("nonHeapUsageRatio", (double) nonHeapUsage.getUsed() / nonHeapUsage.getMax());
        
        // 系统内存信息
        Runtime runtime = Runtime.getRuntime();
        details.put("totalMemory", formatBytes(runtime.totalMemory()));
        details.put("freeMemory", formatBytes(runtime.freeMemory()));
        details.put("maxMemory", formatBytes(runtime.maxMemory()));
        details.put("availableProcessors", runtime.availableProcessors());
        
        return details;
    }
    
    private String formatBytes(long bytes) {
        if (bytes < 0) return "N/A";
        
        String[] units = {"B", "KB", "MB", "GB", "TB"};
        int unitIndex = 0;
        double size = bytes;
        
        while (size >= 1024 && unitIndex < units.length - 1) {
            size /= 1024;
            unitIndex++;
        }
        
        return String.format("%.2f %s", size, units[unitIndex]);
    }
    
    /**
     * 触发垃圾回收
     */
    public void triggerGC() {
        System.gc();
    }
    
    /**
     * 获取内存使用趋势
     */
    public Map<String, Object> getMemoryTrend() {
        Map<String, Object> trend = new HashMap<>();
        
        // 记录当前时间点的内存使用情况
        long timestamp = System.currentTimeMillis();
        MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
        
        trend.put("timestamp", timestamp);
        trend.put("heapUsed", heapUsage.getUsed());
        trend.put("heapMax", heapUsage.getMax());
        trend.put("heapUsageRatio", (double) heapUsage.getUsed() / heapUsage.getMax());
        
        return trend;
    }
}

2.2 性能监控工具

创建 PerformanceMonitor.java

package com.example.springnative.debug;

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * 性能监控工具
 * 用于监控应用性能指标
 */
@Component
public class PerformanceMonitor implements HandlerInterceptor {
    
    private final MeterRegistry meterRegistry;
    private final ConcurrentHashMap<String, Long> requestStartTimes = new ConcurrentHashMap<>();
    
    // 计数器
    private final Counter requestCounter;
    private final Counter errorCounter;
    
    // 计时器
    private final Timer requestTimer;
    
    @Autowired
    public PerformanceMonitor(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
        
        // 初始化指标
        this.requestCounter = Counter.builder("http_requests_total")
            .description("HTTP 请求总数")
            .register(meterRegistry);
            
        this.errorCounter = Counter.builder("http_errors_total")
            .description("HTTP 错误总数")
            .register(meterRegistry);
            
        this.requestTimer = Timer.builder("http_request_duration")
            .description("HTTP 请求持续时间")
            .register(meterRegistry);
    }
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String requestId = getRequestId(request);
        requestStartTimes.put(requestId, System.nanoTime());
        
        // 记录请求
        requestCounter.increment();
        
        return true;
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
                              Object handler, Exception ex) {
        String requestId = getRequestId(request);
        Long startTime = requestStartTimes.remove(requestId);
        
        if (startTime != null) {
            long duration = System.nanoTime() - startTime;
            
            // 记录请求时间
            requestTimer.record(duration, TimeUnit.NANOSECONDS);
            
            // 记录错误
            if (response.getStatus() >= 400 || ex != null) {
                errorCounter.increment();
            }
            
            // 记录慢请求
            if (duration > TimeUnit.SECONDS.toNanos(5)) {
                recordSlowRequest(request, duration);
            }
        }
    }
    
    private String getRequestId(HttpServletRequest request) {
        return Thread.currentThread().getId() + "-" + System.nanoTime();
    }
    
    private void recordSlowRequest(HttpServletRequest request, long duration) {
        String uri = request.getRequestURI();
        String method = request.getMethod();
        double seconds = duration / 1_000_000_000.0;
        
        System.err.printf("慢请求警告: %s %s 耗时 %.3f 秒%n", method, uri, seconds);
        
        // 记录到指标中
        Counter.builder("slow_requests_total")
            .description("慢请求总数")
            .tag("method", method)
            .tag("uri", uri)
            .register(meterRegistry)
            .increment();
    }
    
    /**
     * 获取性能统计信息
     */
    public PerformanceStats getPerformanceStats() {
        PerformanceStats stats = new PerformanceStats();
        
        stats.totalRequests = (long) requestCounter.count();
        stats.totalErrors = (long) errorCounter.count();
        stats.averageResponseTime = requestTimer.mean(TimeUnit.MILLISECONDS);
        stats.maxResponseTime = requestTimer.max(TimeUnit.MILLISECONDS);
        stats.errorRate = stats.totalRequests > 0 ? 
            (double) stats.totalErrors / stats.totalRequests : 0.0;
        
        return stats;
    }
    
    public static class PerformanceStats {
        public long totalRequests;
        public long totalErrors;
        public double averageResponseTime;
        public double maxResponseTime;
        public double errorRate;
        
        @Override
        public String toString() {
            return String.format(
                "PerformanceStats{totalRequests=%d, totalErrors=%d, " +
                "averageResponseTime=%.2fms, maxResponseTime=%.2fms, errorRate=%.2f%%}",
                totalRequests, totalErrors, averageResponseTime, maxResponseTime, errorRate * 100
            );
        }
    }
}

3. 调试工具

3.1 调试配置

创建 application-debug.yml

# 调试配置
spring:
  profiles:
    active: debug
    
  # 日志配置
  logging:
    level:
      com.example.springnative: DEBUG
      org.springframework: INFO
      org.hibernate: DEBUG
      org.hibernate.SQL: DEBUG
      org.hibernate.type.descriptor.sql.BasicBinder: TRACE
      
  # 数据源配置
  datasource:
    hikari:
      leak-detection-threshold: 10000  # 连接泄漏检测
      
  # JPA 配置
  jpa:
    show-sql: true
    properties:
      hibernate:
        format_sql: true
        use_sql_comments: true
        generate_statistics: true
        
  # Web 配置
  web:
    resources:
      add-mappings: true
      
# 管理端点配置
management:
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    health:
      show-details: always
    metrics:
      enabled: true
    threaddump:
      enabled: true
    heapdump:
      enabled: true
      
# 调试特定配置
debug:
  # 启用 SQL 日志
  sql:
    enabled: true
    
  # 启用性能监控
  performance:
    enabled: true
    slow-request-threshold: 1000  # 慢请求阈值(毫秒)
    
  # 启用内存监控
  memory:
    enabled: true
    warning-threshold: 0.8  # 内存警告阈值
    
  # 启用请求追踪
  tracing:
    enabled: true
    sample-rate: 1.0  # 采样率

3.2 调试工具类

创建 DebugUtils.java

package com.example.springnative.debug;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 调试工具类
 * 提供各种调试和诊断功能
 */
@Component
public class DebugUtils {
    
    private final ApplicationContext applicationContext;
    private final ObjectMapper objectMapper;
    private final ThreadMXBean threadBean;
    
    @Autowired
    public DebugUtils(ApplicationContext applicationContext, ObjectMapper objectMapper) {
        this.applicationContext = applicationContext;
        this.objectMapper = objectMapper;
        this.threadBean = ManagementFactory.getThreadMXBean();
    }
    
    /**
     * 获取应用上下文信息
     */
    public Map<String, Object> getApplicationContextInfo() {
        Map<String, Object> info = new HashMap<>();
        
        // Bean 信息
        String[] beanNames = applicationContext.getBeanDefinitionNames();
        info.put("totalBeans", beanNames.length);
        info.put("beanNames", Arrays.asList(beanNames));
        
        // 环境信息
        info.put("activeProfiles", applicationContext.getEnvironment().getActiveProfiles());
        info.put("defaultProfiles", applicationContext.getEnvironment().getDefaultProfiles());
        
        // 应用信息
        info.put("applicationName", applicationContext.getApplicationName());
        info.put("startupDate", new Date(applicationContext.getStartupDate()));
        
        return info;
    }
    
    /**
     * 获取线程信息
     */
    public Map<String, Object> getThreadInfo() {
        Map<String, Object> info = new HashMap<>();
        
        // 基本线程统计
        info.put("threadCount", threadBean.getThreadCount());
        info.put("peakThreadCount", threadBean.getPeakThreadCount());
        info.put("daemonThreadCount", threadBean.getDaemonThreadCount());
        info.put("totalStartedThreadCount", threadBean.getTotalStartedThreadCount());
        
        // 线程详情
        ThreadInfo[] threadInfos = threadBean.getThreadInfo(threadBean.getAllThreadIds());
        List<Map<String, Object>> threads = new ArrayList<>();
        
        for (ThreadInfo threadInfo : threadInfos) {
            if (threadInfo != null) {
                Map<String, Object> thread = new HashMap<>();
                thread.put("id", threadInfo.getThreadId());
                thread.put("name", threadInfo.getThreadName());
                thread.put("state", threadInfo.getThreadState().toString());
                thread.put("blockedTime", threadInfo.getBlockedTime());
                thread.put("waitedTime", threadInfo.getWaitedTime());
                thread.put("cpuTime", threadBean.getThreadCpuTime(threadInfo.getThreadId()));
                threads.add(thread);
            }
        }
        
        info.put("threads", threads);
        
        return info;
    }
    
    /**
     * 获取死锁信息
     */
    public Map<String, Object> getDeadlockInfo() {
        Map<String, Object> info = new HashMap<>();
        
        long[] deadlockedThreads = threadBean.findDeadlockedThreads();
        
        if (deadlockedThreads != null && deadlockedThreads.length > 0) {
            info.put("hasDeadlock", true);
            info.put("deadlockedThreadCount", deadlockedThreads.length);
            
            ThreadInfo[] threadInfos = threadBean.getThreadInfo(deadlockedThreads);
            List<Map<String, Object>> deadlocks = new ArrayList<>();
            
            for (ThreadInfo threadInfo : threadInfos) {
                Map<String, Object> deadlock = new HashMap<>();
                deadlock.put("threadId", threadInfo.getThreadId());
                deadlock.put("threadName", threadInfo.getThreadName());
                deadlock.put("lockName", threadInfo.getLockName());
                deadlock.put("lockOwnerName", threadInfo.getLockOwnerName());
                deadlocks.add(deadlock);
            }
            
            info.put("deadlocks", deadlocks);
        } else {
            info.put("hasDeadlock", false);
        }
        
        return info;
    }
    
    /**
     * 获取系统属性
     */
    public Map<String, Object> getSystemProperties() {
        Map<String, Object> info = new HashMap<>();
        
        Properties props = System.getProperties();
        Map<String, String> systemProps = props.entrySet().stream()
            .collect(Collectors.toMap(
                e -> e.getKey().toString(),
                e -> e.getValue().toString()
            ));
        
        info.put("systemProperties", systemProps);
        info.put("environmentVariables", System.getenv());
        
        return info;
    }
    
    /**
     * 获取类加载器信息
     */
    public Map<String, Object> getClassLoaderInfo() {
        Map<String, Object> info = new HashMap<>();
        
        ClassLoader classLoader = this.getClass().getClassLoader();
        info.put("classLoaderName", classLoader.getClass().getName());
        info.put("classLoaderString", classLoader.toString());
        
        // 获取已加载的类数量(如果可能)
        try {
            java.lang.management.ClassLoadingMXBean classLoadingBean = 
                ManagementFactory.getClassLoadingMXBean();
            info.put("loadedClassCount", classLoadingBean.getLoadedClassCount());
            info.put("totalLoadedClassCount", classLoadingBean.getTotalLoadedClassCount());
            info.put("unloadedClassCount", classLoadingBean.getUnloadedClassCount());
        } catch (Exception e) {
            info.put("classLoadingError", e.getMessage());
        }
        
        return info;
    }
    
    /**
     * 生成诊断报告
     */
    public String generateDiagnosticReport() {
        try {
            Map<String, Object> report = new HashMap<>();
            
            report.put("timestamp", new Date());
            report.put("applicationContext", getApplicationContextInfo());
            report.put("threadInfo", getThreadInfo());
            report.put("deadlockInfo", getDeadlockInfo());
            report.put("systemProperties", getSystemProperties());
            report.put("classLoaderInfo", getClassLoaderInfo());
            
            return objectMapper.writerWithDefaultPrettyPrinter()
                .writeValueAsString(report);
        } catch (Exception e) {
            return "生成诊断报告失败: " + e.getMessage();
        }
    }
}

4. 故障排除脚本

4.1 诊断脚本

创建 scripts/diagnose.sh

#!/bin/bash

# Spring Native 应用诊断脚本
# 用法: ./diagnose.sh [应用URL] [输出目录]

set -euo pipefail

# 配置
APP_URL="${1:-http://localhost:8080}"
OUTPUT_DIR="${2:-./diagnosis-$(date +%Y%m%d-%H%M%S)}"
TIMEOUT=30

# 创建输出目录
mkdir -p "$OUTPUT_DIR"

# 日志函数
log_info() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] $1" | tee -a "$OUTPUT_DIR/diagnosis.log"
}

log_error() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $1" | tee -a "$OUTPUT_DIR/diagnosis.log" >&2
}

log_success() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] [SUCCESS] $1" | tee -a "$OUTPUT_DIR/diagnosis.log"
}

# 检查应用可用性
check_application() {
    log_info "检查应用可用性..."
    
    if curl -f -s --connect-timeout $TIMEOUT "$APP_URL/actuator/health" > "$OUTPUT_DIR/health.json"; then
        log_success "应用健康检查通过"
        return 0
    else
        log_error "应用健康检查失败"
        return 1
    fi
}

# 收集应用信息
collect_app_info() {
    log_info "收集应用信息..."
    
    # 基本信息
    curl -s --connect-timeout $TIMEOUT "$APP_URL/actuator/info" > "$OUTPUT_DIR/info.json" || true
    
    # 环境信息
    curl -s --connect-timeout $TIMEOUT "$APP_URL/actuator/env" > "$OUTPUT_DIR/env.json" || true
    
    # 配置属性
    curl -s --connect-timeout $TIMEOUT "$APP_URL/actuator/configprops" > "$OUTPUT_DIR/configprops.json" || true
    
    # Bean 信息
    curl -s --connect-timeout $TIMEOUT "$APP_URL/actuator/beans" > "$OUTPUT_DIR/beans.json" || true
    
    # 映射信息
    curl -s --connect-timeout $TIMEOUT "$APP_URL/actuator/mappings" > "$OUTPUT_DIR/mappings.json" || true
    
    log_success "应用信息收集完成"
}

# 收集性能指标
collect_metrics() {
    log_info "收集性能指标..."
    
    # 基本指标
    curl -s --connect-timeout $TIMEOUT "$APP_URL/actuator/metrics" > "$OUTPUT_DIR/metrics.json" || true
    
    # JVM 指标
    curl -s --connect-timeout $TIMEOUT "$APP_URL/actuator/metrics/jvm.memory.used" > "$OUTPUT_DIR/jvm-memory.json" || true
    curl -s --connect-timeout $TIMEOUT "$APP_URL/actuator/metrics/jvm.threads.live" > "$OUTPUT_DIR/jvm-threads.json" || true
    curl -s --connect-timeout $TIMEOUT "$APP_URL/actuator/metrics/jvm.gc.pause" > "$OUTPUT_DIR/jvm-gc.json" || true
    
    # HTTP 指标
    curl -s --connect-timeout $TIMEOUT "$APP_URL/actuator/metrics/http.server.requests" > "$OUTPUT_DIR/http-requests.json" || true
    
    # 数据库指标
    curl -s --connect-timeout $TIMEOUT "$APP_URL/actuator/metrics/hikaricp.connections" > "$OUTPUT_DIR/db-connections.json" || true
    
    log_success "性能指标收集完成"
}

# 收集线程信息
collect_thread_info() {
    log_info "收集线程信息..."
    
    # 线程转储
    curl -s --connect-timeout $TIMEOUT "$APP_URL/actuator/threaddump" > "$OUTPUT_DIR/threaddump.json" || true
    
    log_success "线程信息收集完成"
}

# 收集内存信息
collect_memory_info() {
    log_info "收集内存信息..."
    
    # 堆转储(如果启用)
    if curl -f -s --connect-timeout $TIMEOUT "$APP_URL/actuator/heapdump" > "$OUTPUT_DIR/heapdump.hprof"; then
        log_success "堆转储收集完成"
    else
        log_info "堆转储不可用或已禁用"
    fi
}

# 收集日志信息
collect_logs() {
    log_info "收集日志信息..."
    
    # 日志级别
    curl -s --connect-timeout $TIMEOUT "$APP_URL/actuator/loggers" > "$OUTPUT_DIR/loggers.json" || true
    
    # 如果有日志文件路径,尝试收集最近的日志
    if [ -f "/app/logs/application.log" ]; then
        tail -n 1000 "/app/logs/application.log" > "$OUTPUT_DIR/recent-logs.txt" || true
    fi
    
    log_success "日志信息收集完成"
}

# 收集系统信息
collect_system_info() {
    log_info "收集系统信息..."
    
    # 系统信息
    {
        echo "=== 系统信息 ==="
        uname -a
        echo ""
        
        echo "=== CPU 信息 ==="
        cat /proc/cpuinfo | grep "model name" | head -1
        nproc
        echo ""
        
        echo "=== 内存信息 ==="
        free -h
        echo ""
        
        echo "=== 磁盘信息 ==="
        df -h
        echo ""
        
        echo "=== 网络信息 ==="
        ss -tuln
        echo ""
        
        echo "=== 进程信息 ==="
        ps aux | grep java | head -10
        echo ""
        
        echo "=== 环境变量 ==="
        env | grep -E "(JAVA|SPRING|APP)" | sort
        
    } > "$OUTPUT_DIR/system-info.txt" 2>&1
    
    log_success "系统信息收集完成"
}

# 分析诊断结果
analyze_results() {
    log_info "分析诊断结果..."
    
    local analysis_file="$OUTPUT_DIR/analysis.txt"
    
    {
        echo "=== Spring Native 应用诊断分析 ==="
        echo "诊断时间: $(date)"
        echo "应用URL: $APP_URL"
        echo ""
        
        # 分析健康状态
        if [ -f "$OUTPUT_DIR/health.json" ]; then
            echo "=== 健康状态分析 ==="
            local health_status=$(jq -r '.status // "UNKNOWN"' "$OUTPUT_DIR/health.json" 2>/dev/null || echo "UNKNOWN")
            echo "应用状态: $health_status"
            
            if [ "$health_status" != "UP" ]; then
                echo "⚠️  应用状态异常,需要检查具体组件状态"
            fi
            echo ""
        fi
        
        # 分析内存使用
        if [ -f "$OUTPUT_DIR/jvm-memory.json" ]; then
            echo "=== 内存使用分析 ==="
            local memory_used=$(jq -r '.measurements[0].value // 0' "$OUTPUT_DIR/jvm-memory.json" 2>/dev/null || echo "0")
            echo "JVM 内存使用: $(echo "scale=2; $memory_used / 1024 / 1024" | bc 2>/dev/null || echo "N/A") MB"
            
            if (( $(echo "$memory_used > 1073741824" | bc -l 2>/dev/null || echo 0) )); then
                echo "⚠️  内存使用较高,建议监控内存泄漏"
            fi
            echo ""
        fi
        
        # 分析线程状态
        if [ -f "$OUTPUT_DIR/jvm-threads.json" ]; then
            echo "=== 线程状态分析 ==="
            local thread_count=$(jq -r '.measurements[0].value // 0' "$OUTPUT_DIR/jvm-threads.json" 2>/dev/null || echo "0")
            echo "活跃线程数: $thread_count"
            
            if (( thread_count > 200 )); then
                echo "⚠️  线程数较多,可能存在线程泄漏"
            fi
            echo ""
        fi
        
        # 分析HTTP请求
        if [ -f "$OUTPUT_DIR/http-requests.json" ]; then
            echo "=== HTTP 请求分析 ==="
            echo "HTTP 请求指标已收集,请查看详细数据"
            echo ""
        fi
        
        # 建议
        echo "=== 建议 ==="
        echo "1. 检查 health.json 中的详细健康状态"
        echo "2. 监控 jvm-memory.json 中的内存使用趋势"
        echo "3. 分析 threaddump.json 查找潜在的死锁或阻塞"
        echo "4. 查看 recent-logs.txt 中的错误日志"
        echo "5. 检查 system-info.txt 中的系统资源使用情况"
        
    } > "$analysis_file"
    
    log_success "诊断分析完成"
}

# 生成诊断报告
generate_report() {
    log_info "生成诊断报告..."
    
    local report_file="$OUTPUT_DIR/diagnosis-report.html"
    
    cat > "$report_file" << 'EOF'
<!DOCTYPE html>
<html>
<head>
    <title>Spring Native 应用诊断报告</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        .header { background-color: #f0f0f0; padding: 10px; border-radius: 5px; }
        .section { margin: 20px 0; }
        .warning { color: #ff6600; }
        .error { color: #ff0000; }
        .success { color: #00aa00; }
        pre { background-color: #f5f5f5; padding: 10px; border-radius: 3px; overflow-x: auto; }
        table { border-collapse: collapse; width: 100%; }
        th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
        th { background-color: #f2f2f2; }
    </style>
</head>
<body>
    <div class="header">
        <h1>Spring Native 应用诊断报告</h1>
        <p>生成时间: $(date)</p>
        <p>应用URL: $APP_URL</p>
    </div>
    
    <div class="section">
        <h2>诊断文件列表</h2>
        <ul>
EOF
    
    # 添加文件列表
    for file in "$OUTPUT_DIR"/*; do
        if [ -f "$file" ] && [ "$(basename "$file")" != "diagnosis-report.html" ]; then
            echo "            <li><a href='$(basename "$file")'>$(basename "$file")</a></li>" >> "$report_file"
        fi
    done
    
    cat >> "$report_file" << 'EOF'
        </ul>
    </div>
    
    <div class="section">
        <h2>快速分析</h2>
        <pre id="analysis"></pre>
    </div>
    
    <script>
        // 加载分析结果
        fetch('analysis.txt')
            .then(response => response.text())
            .then(text => {
                document.getElementById('analysis').textContent = text;
            })
            .catch(error => {
                document.getElementById('analysis').textContent = '无法加载分析结果: ' + error;
            });
    </script>
</body>
</html>
EOF
    
    log_success "诊断报告已生成: $report_file"
}

# 主函数
main() {
    log_info "开始 Spring Native 应用诊断..."
    
    # 检查必要工具
    for tool in curl jq bc; do
        if ! command -v "$tool" &> /dev/null; then
            log_error "缺少必要工具: $tool"
            exit 1
        fi
    done
    
    # 执行诊断步骤
    if check_application; then
        collect_app_info
        collect_metrics
        collect_thread_info
        collect_memory_info
        collect_logs
    else
        log_error "应用不可用,跳过部分诊断步骤"
    fi
    
    collect_system_info
    analyze_results
    generate_report
    
    log_success "诊断完成!结果保存在: $OUTPUT_DIR"
    echo "请查看诊断报告: $OUTPUT_DIR/diagnosis-report.html"
}

# 错误处理
trap 'log_error "诊断过程中发生错误,退出码: $?"' ERR

# 执行主函数
main "$@"

5. 常见问题解决方案

5.1 启动问题

问题: 应用启动失败 解决方案: 1. 检查 GraalVM 版本兼容性 2. 验证原生镜像配置文件 3. 检查依赖库的原生支持 4. 查看启动日志中的错误信息

5.2 性能问题

问题: 应用响应缓慢 解决方案: 1. 使用性能监控工具分析瓶颈 2. 检查数据库连接池配置 3. 优化 JVM 参数 4. 分析慢查询和慢接口

5.3 内存问题

问题: 内存使用过高或内存泄漏 解决方案: 1. 使用堆转储分析内存使用 2. 检查对象生命周期管理 3. 优化缓存策略 4. 调整垃圾回收参数

5.4 数据库连接问题

问题: 数据库连接异常 解决方案: 1. 检查数据库驱动的原生支持 2. 验证连接池配置 3. 检查数据库连接字符串 4. 确认网络连通性

6. 最佳实践

6.1 开发阶段

  1. 及早测试原生编译

    • 在开发过程中定期进行原生编译测试
    • 使用 CI/CD 流水线自动化测试
  2. 配置管理

    • 维护完整的反射配置
    • 定期更新资源配置
    • 使用配置生成工具
  3. 依赖管理

    • 选择支持原生编译的依赖库
    • 避免使用动态代理和反射
    • 测试第三方库的兼容性

6.2 测试阶段

  1. 全面测试

    • 功能测试
    • 性能测试
    • 集成测试
    • 压力测试
  2. 环境一致性

    • 保持开发、测试、生产环境一致
    • 使用容器化部署
    • 统一配置管理

6.3 生产阶段

  1. 监控告警

    • 设置完善的监控指标
    • 配置告警规则
    • 建立故障响应流程
  2. 日志管理

    • 结构化日志输出
    • 集中化日志收集
    • 日志分析和查询
  3. 备份恢复

    • 定期备份重要数据
    • 测试恢复流程
    • 制定灾难恢复计划

7. 工具推荐

7.1 开发工具

  • GraalVM: 原生镜像编译器
  • Spring Boot: 应用框架
  • Maven/Gradle: 构建工具
  • Docker: 容器化工具

7.2 监控工具

  • Micrometer: 指标收集
  • Prometheus: 监控系统
  • Grafana: 可视化面板
  • ELK Stack: 日志分析

7.3 诊断工具

  • JProfiler: 性能分析
  • VisualVM: JVM 监控
  • MAT: 内存分析
  • Arthas: 在线诊断

8. 总结

Spring Native 故障排除需要掌握以下关键点:

  1. 理解原生编译特性

    • 编译时优化
    • 运行时限制
    • 配置要求
  2. 建立完善的监控体系

    • 应用监控
    • 系统监控
    • 业务监控
  3. 制定标准化的故障处理流程

    • 问题发现
    • 问题定位
    • 问题解决
    • 问题预防
  4. 持续优化和改进

    • 性能调优
    • 配置优化
    • 流程改进

通过本章的学习,您应该能够: - 快速定位和解决 Spring Native 应用的常见问题 - 建立有效的监控和诊断体系 - 制定完善的故障处理流程 - 持续优化应用性能和稳定性

下一步学习

  1. 深入学习 GraalVM

    • SubstrateVM 原理
    • 编译优化技术
    • 高级配置选项
  2. 扩展监控能力

    • 自定义指标
    • 分布式追踪
    • APM 集成
  3. 自动化运维

    • CI/CD 集成
    • 自动化部署
    • 智能运维