第09章 - 异常处理体系
第09章 - 异常处理体系
9.1 异常体系设计
9.1.1 设计理念
OGU4J的异常体系遵循以下设计原则:
- 语义清晰:每种异常都有明确的含义
- 层次分明:继承自统一基类
- 信息丰富:包含足够的错误信息
- 易于处理:支持分类捕获
9.1.2 异常继承图
RuntimeException
└── OguException (OGU4J基础异常)
├── DataSourceException (数据源异常)
├── FormatParseException (格式解析异常)
├── EngineNotSupportedException (引擎不支持异常)
├── LayerValidationException (图层验证异常)
└── TopologyException (拓扑异常)
9.1.3 为什么使用RuntimeException
OGU4J的异常继承自RuntimeException(非受检异常),理由如下:
| 方面 | 受检异常 | 非受检异常 |
|---|---|---|
| 代码简洁性 | 需要显式处理 | 可选择性处理 |
| 调用链 | 需要层层抛出 | 自动传播 |
| 适用场景 | 可恢复的错误 | 编程错误/不可恢复 |
| OGU4J场景 | - | 数据格式错误、引擎问题等 |
9.2 各异常类详解
9.2.1 OguException - 基础异常
/**
* OGU4J基础异常
* 所有OGU4J异常的父类
*/
public class OguException extends RuntimeException {
public OguException(String message) {
super(message);
}
public OguException(String message, Throwable cause) {
super(message, cause);
}
}
使用场景:
- 作为其他异常的父类
- 通用错误
示例:
try {
// 任何OGU4J操作
} catch (OguException e) {
// 捕获所有OGU4J相关异常
log.error("OGU4J错误: {}", e.getMessage());
}
9.2.2 DataSourceException - 数据源异常
/**
* 数据源异常
* 用于数据读写相关的错误
*/
public class DataSourceException extends OguException {
public DataSourceException(String message) {
super(message);
}
public DataSourceException(String message, Throwable cause) {
super(message, cause);
}
}
使用场景:
- 文件不存在或无法访问
- 数据库连接失败
- 读写权限不足
- 数据源格式不支持
示例:
// 读取不存在的文件
try {
OguLayer layer = OguLayerUtil.readLayer(
DataFormatType.SHP,
"D:/nonexistent.shp",
null, null, null,
GisEngineType.GEOTOOLS
);
} catch (DataSourceException e) {
System.err.println("数据源错误: " + e.getMessage());
// 输出: 数据源错误: 文件不存在: D:/nonexistent.shp
}
// 数据库连接失败
try {
OguLayer layer = OguLayerUtil.readLayer(
DataFormatType.POSTGIS,
"PG: host=invalid port=5432 ...",
"table_name",
null, null,
GisEngineType.GEOTOOLS
);
} catch (DataSourceException e) {
System.err.println("连接失败: " + e.getMessage());
}
9.2.3 FormatParseException - 格式解析异常
/**
* 格式解析异常
* 用于数据格式相关的解析错误
*/
public class FormatParseException extends OguException {
public FormatParseException(String message) {
super(message);
}
public FormatParseException(String message, Throwable cause) {
super(message, cause);
}
}
使用场景:
- WKT格式错误
- GeoJSON格式错误
- CQL表达式语法错误
- JSON反序列化错误
示例:
// 无效的WKT
try {
Geometry geom = GeometryUtil.wkt2Geometry("INVALID WKT");
} catch (FormatParseException e) {
System.err.println("WKT解析错误: " + e.getMessage());
}
// 无效的CQL
try {
OguLayer layer = OguLayerUtil.readLayer(
DataFormatType.SHP,
"D:/data/cities.shp",
null,
"INVALID CQL >>>", // 语法错误
null,
GisEngineType.GEOTOOLS
);
} catch (FormatParseException e) {
System.err.println("CQL语法错误: " + e.getMessage());
}
// 无效的GeoJSON
try {
Geometry geom = GeometryUtil.geojson2Geometry("{invalid json}");
} catch (FormatParseException e) {
System.err.println("GeoJSON解析错误: " + e.getMessage());
}
9.2.4 EngineNotSupportedException - 引擎不支持异常
/**
* 引擎不支持异常
* 用于GIS引擎相关的错误
*/
public class EngineNotSupportedException extends OguException {
public EngineNotSupportedException(String message) {
super(message);
}
public EngineNotSupportedException(String message, Throwable cause) {
super(message, cause);
}
}
使用场景:
- GDAL未安装或配置错误
- 请求的格式不被引擎支持
- 引擎初始化失败
示例:
// GDAL未安装
try {
OguLayer layer = OguLayerUtil.readLayer(
DataFormatType.FILEGDB,
"D:/data/sample.gdb",
"Layer1",
null, null,
GisEngineType.GDAL
);
} catch (EngineNotSupportedException e) {
System.err.println("引擎错误: " + e.getMessage());
// 输出: 引擎错误: GDAL引擎不可用,请检查GDAL安装和环境变量配置
}
// GeoTools不支持FileGDB
try {
OguLayer layer = OguLayerUtil.readLayer(
DataFormatType.FILEGDB,
"D:/data/sample.gdb",
"Layer1",
null, null,
GisEngineType.GEOTOOLS // 错误:应使用GDAL
);
} catch (EngineNotSupportedException e) {
System.err.println("格式不支持: " + e.getMessage());
// 输出: 格式不支持: GeoTools不支持格式: FILEGDB
}
9.2.5 LayerValidationException - 图层验证异常
/**
* 图层验证异常
* 用于图层数据完整性验证相关的错误
*/
public class LayerValidationException extends OguException {
public LayerValidationException(String message) {
super(message);
}
public LayerValidationException(String message, Throwable cause) {
super(message, cause);
}
}
使用场景:
- 图层名称为空
- 几何类型未设置
- 字段定义缺失
- 要素几何类型与图层不匹配
示例:
// 验证不完整的图层
OguLayer layer = new OguLayer();
// 未设置必要属性
try {
layer.validate();
} catch (LayerValidationException e) {
System.err.println("验证失败: " + e.getMessage());
// 输出: 验证失败: 图层名称不能为空
}
// 几何类型不匹配
OguLayer polygonLayer = new OguLayer();
polygonLayer.setName("测试");
polygonLayer.setGeometryType(GeometryType.POLYGON);
polygonLayer.setFields(new ArrayList<>());
OguFeature feature = new OguFeature();
feature.setFid("1");
feature.setWkt("POINT(116 39)"); // 点,但图层是面
try {
polygonLayer.setFeatures(Arrays.asList(feature));
polygonLayer.validate();
} catch (LayerValidationException e) {
System.err.println("类型不匹配: " + e.getMessage());
}
9.2.6 TopologyException - 拓扑异常
/**
* 拓扑异常
* 用于几何拓扑相关的错误
*/
public class TopologyException extends OguException {
public TopologyException(String message) {
super(message);
}
public TopologyException(String message, Throwable cause) {
super(message, cause);
}
}
使用场景:
- 自相交多边形
- 环未闭合
- 无效的几何拓扑
- 空间分析失败
示例:
// 自相交多边形操作
try {
Geometry invalid = GeometryUtil.wkt2Geometry(
"POLYGON((0 0, 10 10, 0 10, 10 0, 0 0))"); // 8字形
// 对无效几何进行操作可能抛出异常
Geometry buffer = GeometryUtil.buffer(invalid, 1.0);
} catch (TopologyException e) {
System.err.println("拓扑错误: " + e.getMessage());
}
9.3 异常处理最佳实践
9.3.1 分层捕获
public class ExceptionHandlingExample {
public void processLayer(String path) {
try {
OguLayer layer = readLayer(path);
validateLayer(layer);
processGeometries(layer);
saveLayer(layer, "output.shp");
} catch (DataSourceException e) {
// 数据源问题:记录并通知用户
log.error("数据源错误: {}", e.getMessage());
notifyUser("无法访问数据文件,请检查路径");
} catch (FormatParseException e) {
// 格式问题:记录详细信息
log.error("格式错误: {}", e.getMessage(), e);
notifyUser("数据格式有误,请检查数据");
} catch (EngineNotSupportedException e) {
// 引擎问题:建议解决方案
log.error("引擎错误: {}", e.getMessage());
notifyUser("请检查GDAL安装或使用支持的格式");
} catch (LayerValidationException e) {
// 验证问题:提供具体错误
log.warn("验证失败: {}", e.getMessage());
notifyUser("数据验证失败: " + e.getMessage());
} catch (TopologyException e) {
// 拓扑问题:尝试修复
log.warn("拓扑错误: {}", e.getMessage());
tryRepairAndReprocess(path);
} catch (OguException e) {
// 其他OGU4J错误
log.error("处理错误: {}", e.getMessage(), e);
notifyUser("处理过程中发生错误");
} catch (Exception e) {
// 未预期的错误
log.error("未知错误", e);
notifyUser("发生未知错误,请联系技术支持");
}
}
}
9.3.2 异常包装
public class LayerService {
/**
* 读取图层,将底层异常转换为业务异常
*/
public OguLayer loadLayer(String path) throws BusinessException {
try {
return OguLayerUtil.readLayer(
DataFormatType.SHP,
path,
null, null, null,
GisEngineType.GEOTOOLS
);
} catch (DataSourceException e) {
throw new BusinessException(ErrorCode.DATA_NOT_FOUND,
"找不到数据文件: " + path, e);
} catch (FormatParseException e) {
throw new BusinessException(ErrorCode.INVALID_FORMAT,
"数据格式无效", e);
} catch (OguException e) {
throw new BusinessException(ErrorCode.PROCESSING_ERROR,
"数据处理失败", e);
}
}
}
9.3.3 资源清理
public class ResourceCleanup {
public void processWithCleanup(String inputPath, String outputPath) {
OguLayer layer = null;
try {
// 读取
layer = OguLayerUtil.readLayer(
DataFormatType.SHP,
inputPath,
null, null, null,
GisEngineType.GEOTOOLS
);
// 处理
processFeatures(layer);
// 写入
OguLayerUtil.writeLayer(
DataFormatType.SHP,
layer,
outputPath,
null, null,
GisEngineType.GEOTOOLS
);
} catch (OguException e) {
// 清理可能创建的临时文件
cleanupTempFiles(outputPath);
throw e;
} finally {
// 释放资源
if (layer != null) {
layer.setFeatures(null); // 帮助GC
}
}
}
}
9.3.4 链式异常处理
public class ChainedExceptionHandling {
public OguLayer safeRead(String path) {
// 尝试多种方式读取
// 1. 尝试使用GeoTools
try {
return OguLayerUtil.readLayer(
DataFormatType.SHP, path,
null, null, null,
GisEngineType.GEOTOOLS
);
} catch (OguException e1) {
log.debug("GeoTools读取失败,尝试GDAL: {}", e1.getMessage());
}
// 2. 尝试使用GDAL
try {
if (GisEngineFactory.isEngineAvailable(GisEngineType.GDAL)) {
return OguLayerUtil.readLayer(
DataFormatType.SHP, path,
null, null, null,
GisEngineType.GDAL
);
}
} catch (OguException e2) {
log.debug("GDAL读取失败: {}", e2.getMessage());
}
// 3. 都失败了
throw new DataSourceException("无法读取文件: " + path);
}
}
9.3.5 日志记录
public class ExceptionLogging {
private static final Logger log = LoggerFactory.getLogger(ExceptionLogging.class);
public void process(String path) {
try {
// 业务逻辑...
} catch (DataSourceException e) {
// 错误级别:影响业务的严重错误
log.error("数据源访问失败 - 路径: {}, 错误: {}", path, e.getMessage());
throw e;
} catch (FormatParseException e) {
// 警告级别:可能的数据问题
log.warn("格式解析警告 - 路径: {}, 错误: {}", path, e.getMessage());
// 可能尝试恢复...
} catch (LayerValidationException e) {
// 信息级别:预期的验证失败
log.info("验证失败 - 路径: {}, 原因: {}", path, e.getMessage());
// 返回验证结果...
} catch (OguException e) {
// 包含堆栈跟踪的完整日志
log.error("处理失败 - 路径: {}", path, e);
throw e;
}
}
}
9.4 自定义异常
9.4.1 扩展OguException
/**
* 自定义业务异常示例
*/
public class GisBusinessException extends OguException {
private final String errorCode;
private final String layerName;
public GisBusinessException(String errorCode, String message, String layerName) {
super(message);
this.errorCode = errorCode;
this.layerName = layerName;
}
public GisBusinessException(String errorCode, String message,
String layerName, Throwable cause) {
super(message, cause);
this.errorCode = errorCode;
this.layerName = layerName;
}
public String getErrorCode() {
return errorCode;
}
public String getLayerName() {
return layerName;
}
@Override
public String toString() {
return String.format("[%s] %s (图层: %s)", errorCode, getMessage(), layerName);
}
}
9.4.2 使用自定义异常
public class CustomExceptionUsage {
public void processLayer(String layerName) {
try {
OguLayer layer = loadLayer(layerName);
if (layer.getFeatureCount() == 0) {
throw new GisBusinessException(
"EMPTY_LAYER",
"图层没有要素",
layerName
);
}
// 处理...
} catch (GisBusinessException e) {
System.out.printf("业务错误 [%s]: %s%n",
e.getErrorCode(), e.getMessage());
}
}
}
9.5 异常处理模式
9.5.1 快速失败模式
public class FailFastPattern {
/**
* 验证输入,快速失败
*/
public OguLayer readLayer(DataFormatType format, String path,
GisEngineType engine) {
// 快速验证
if (format == null) {
throw new IllegalArgumentException("格式不能为null");
}
if (path == null || path.isEmpty()) {
throw new IllegalArgumentException("路径不能为空");
}
if (engine == null) {
throw new IllegalArgumentException("引擎不能为null");
}
// 文件检查
if (format != DataFormatType.POSTGIS) {
File file = new File(path);
if (!file.exists()) {
throw new DataSourceException("文件不存在: " + path);
}
}
// 引擎检查
if (!GisEngineFactory.isEngineAvailable(engine)) {
throw new EngineNotSupportedException("引擎不可用: " + engine);
}
// 格式兼容性检查
if (format == DataFormatType.FILEGDB && engine != GisEngineType.GDAL) {
throw new EngineNotSupportedException(
"FileGDB格式需要使用GDAL引擎");
}
// 执行读取
return OguLayerUtil.readLayer(format, path, null, null, null, engine);
}
}
9.5.2 优雅降级模式
public class GracefulDegradation {
/**
* 尝试读取,失败时返回空图层
*/
public Optional<OguLayer> tryReadLayer(String path) {
try {
OguLayer layer = OguLayerUtil.readLayer(
DataFormatType.SHP,
path,
null, null, null,
GisEngineType.GEOTOOLS
);
return Optional.of(layer);
} catch (OguException e) {
log.warn("读取失败,返回空结果: {}", e.getMessage());
return Optional.empty();
}
}
/**
* 批量读取,跳过失败的文件
*/
public List<OguLayer> batchReadLayers(List<String> paths) {
List<OguLayer> layers = new ArrayList<>();
List<String> failed = new ArrayList<>();
for (String path : paths) {
try {
OguLayer layer = OguLayerUtil.readLayer(
DataFormatType.SHP,
path,
null, null, null,
GisEngineType.GEOTOOLS
);
layers.add(layer);
} catch (OguException e) {
failed.add(path);
log.warn("跳过文件: {} - {}", path, e.getMessage());
}
}
if (!failed.isEmpty()) {
log.info("批量读取完成: 成功 {}, 失败 {}",
layers.size(), failed.size());
}
return layers;
}
}
9.5.3 重试模式
public class RetryPattern {
private static final int MAX_RETRIES = 3;
private static final long RETRY_DELAY_MS = 1000;
/**
* 带重试的读取
*/
public OguLayer readWithRetry(String connStr, String layerName) {
int attempt = 0;
OguException lastException = null;
while (attempt < MAX_RETRIES) {
try {
return OguLayerUtil.readLayer(
DataFormatType.POSTGIS,
connStr,
layerName,
null, null,
GisEngineType.GEOTOOLS
);
} catch (DataSourceException e) {
lastException = e;
attempt++;
if (attempt < MAX_RETRIES) {
log.warn("读取失败,{}ms后重试 ({}/{}): {}",
RETRY_DELAY_MS, attempt, MAX_RETRIES, e.getMessage());
sleep(RETRY_DELAY_MS);
}
} catch (OguException e) {
// 其他异常不重试
throw e;
}
}
throw new DataSourceException(
"重试" + MAX_RETRIES + "次后仍然失败", lastException);
}
private void sleep(long ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}

浙公网安备 33010602011771号