本章将介绍 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 开发阶段
及早测试原生编译
- 在开发过程中定期进行原生编译测试
- 使用 CI/CD 流水线自动化测试
配置管理
- 维护完整的反射配置
- 定期更新资源配置
- 使用配置生成工具
依赖管理
- 选择支持原生编译的依赖库
- 避免使用动态代理和反射
- 测试第三方库的兼容性
6.2 测试阶段
全面测试
- 功能测试
- 性能测试
- 集成测试
- 压力测试
环境一致性
- 保持开发、测试、生产环境一致
- 使用容器化部署
- 统一配置管理
6.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 故障排除需要掌握以下关键点:
理解原生编译特性
- 编译时优化
- 运行时限制
- 配置要求
建立完善的监控体系
- 应用监控
- 系统监控
- 业务监控
制定标准化的故障处理流程
- 问题发现
- 问题定位
- 问题解决
- 问题预防
持续优化和改进
- 性能调优
- 配置优化
- 流程改进
通过本章的学习,您应该能够: - 快速定位和解决 Spring Native 应用的常见问题 - 建立有效的监控和诊断体系 - 制定完善的故障处理流程 - 持续优化应用性能和稳定性
下一步学习
深入学习 GraalVM
- SubstrateVM 原理
- 编译优化技术
- 高级配置选项
扩展监控能力
- 自定义指标
- 分布式追踪
- APM 集成
自动化运维
- CI/CD 集成
- 自动化部署
- 智能运维