第09章 - 异常处理体系

第09章 - 异常处理体系

9.1 异常体系设计

9.1.1 设计理念

OGU4J的异常体系遵循以下设计原则:

  1. 语义清晰:每种异常都有明确的含义
  2. 层次分明:继承自统一基类
  3. 信息丰富:包含足够的错误信息
  4. 易于处理:支持分类捕获

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();
        }
    }
}

← 上一章:坐标系管理与转换 | 下一章:实用工具类详解 →

posted @ 2025-12-02 10:30  我才是银古  阅读(3)  评论(0)    收藏  举报