Skip to content

异常处理

自 v6.2.0 起

自动捕获和处理服务方法中的异常。

UltiTools 通过 @ExceptionCatch 注解提供声明式异常处理。无需在服务方法调用处用 try-catch 块包裹,只需为方法添加注解,框架根据你的配置自动处理异常。

v6.2.5 中没有任何代码读取 @ExceptionCatch

在 v6.2.5 里,aop 包与框架其余代码之间只剩两处 javadoc 引用:没有代理被创建,没有 advisor 被注册,ExceptionInterceptor 从不被实例化,因此带注解的方法抛出的异常与不带注解时完全一样,silentvaluedefaultValuehandler 四个属性均不产生作用。 接线发布之前,用普通的 try-catch 包住调用:本页描述的全部内容,包括下文按名字查找处理器的部分,都依赖这同一处缺失的连接。 该接线已合入开发分支,未包含在 v6.2.5,跟踪于 issue #190

基本用法

在任意受容器管理的 Bean(如 @Service)的方法上添加 @ExceptionCatch

java
package com.ultikits.docs.exception;

import com.ultikits.ultitools.abstracts.UltiToolsPlugin;
import com.ultikits.ultitools.annotations.*;
import com.ultikits.ultitools.aop.ExceptionHandler;
import com.ultikits.ultitools.interfaces.DataOperator;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

@Service
public class FileService {

    @ExceptionCatch
    // @ExceptionCatch is runtime AOP. It catches the exception when the method
    // is invoked through the proxy, but javac still requires a checked exception
    // to be declared, so `throws IOException` is not optional here.
    public String readFile(String path) throws IOException {
        // If any exception occurs, it will be caught and logged
        // The method returns null
        return new String(Files.readAllBytes(Paths.get(path)));
    }
}

默认行为:

  • 捕获所有 Exception 类型(及其子类)
  • 异常会被记录为警告日志(除非 silent = true
  • 返回默认值(对象返回 null,原始类型返回 0 等)

注解属性

属性类型默认值说明
valueClass<? extends Throwable>[]{Exception.class}要捕获的异常类型。子类会自动被包含。
silentbooleanfalse为 true 时,异常被静默捕获,不记录日志。为 false 时,异常被记录为警告。无论哪种情况,异常都会被同时上报给框架的 ErrorReportCollector。
handlerString""自定义异常处理器 Bean 的名称。该 Bean 必须实现 ExceptionHandler 接口。
defaultValueString""异常被捕获时返回的值的表达式。

捕获特定异常

指定应该被捕获的异常类型:

java
package com.ultikits.docs.exception;

import com.ultikits.ultitools.abstracts.UltiToolsPlugin;
import com.ultikits.ultitools.annotations.*;
import com.ultikits.ultitools.aop.ExceptionHandler;
import com.ultikits.ultitools.interfaces.DataOperator;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

@Service
public class DataService {

    @ExceptionCatch(IOException.class)
    public String loadData() {
        // Only IOException will be caught
        // Other exceptions will propagate up
        return readFromFile();
    }

    @ExceptionCatch({IOException.class, SQLException.class})
    public List<User> fetchUsers() {
        // Both IOException and SQLException will be caught
        // Subclasses are also caught
        return queryDatabase();
    }

    private String readFromFile() { return ""; }

    private List<User> queryDatabase() { return new ArrayList<>(); }
}

异常继承关系

当指定异常类型时,框架也会捕获其子类。例如,@ExceptionCatch(IOException.class) 会捕获 FileNotFoundExceptionEOFException 等 IOException 的子类。

静默模式

对于已预期的或非关键异常,禁用日志记录:

java
package com.ultikits.docs.exception;

import com.ultikits.ultitools.abstracts.UltiToolsPlugin;
import com.ultikits.ultitools.annotations.*;
import com.ultikits.ultitools.aop.ExceptionHandler;
import com.ultikits.ultitools.interfaces.DataOperator;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

@Service
public class ConfigService {

    @ExceptionCatch(silent = true)
    public void saveOptionalConfig() {
        // Any exception is caught and NOT logged
        // Useful for non-critical background operations
        writeConfigBackup();
    }

    @ExceptionCatch(value = FileNotFoundException.class, silent = true)
    public boolean fileExists(String path) {
        // FileNotFoundException is silently caught
        // Other exceptions propagate up uncaught (not caught, not logged)
        return checkFile(path);
    }

    private void writeConfigBackup() { }

    private boolean checkFile(String path) { return true; }
}

何时使用 silent = true

  • 非关键操作(如可选备份)
  • 后备逻辑(如文件未找到时使用默认值)
  • 异常是预期的操作

默认返回值

控制异常被捕获时返回的值:

java
package com.ultikits.docs.exception;

import com.ultikits.ultitools.abstracts.UltiToolsPlugin;
import com.ultikits.ultitools.annotations.*;
import com.ultikits.ultitools.aop.ExceptionHandler;
import com.ultikits.ultitools.interfaces.DataOperator;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

@Service
public class MoneyService {

    @ExceptionCatch(defaultValue = "0")
    public int getBalance(String accountId) {
        // If exception occurs, returns 0 instead of null
        return queryBalance(accountId);
    }

    @ExceptionCatch(defaultValue = "false")
    public boolean isPlayerOnline(String playerName) {
        // Returns false instead of null
        return checkDatabase(playerName);
    }

    @ExceptionCatch(defaultValue = "empty")
    public List<User> getAllUsers() {
        // Returns empty list instead of null
        return queryAllUsers();
    }

    private int queryBalance(String accountId) { return 0; }

    private boolean checkDatabase(String playerName) { return false; }

    private List<User> queryAllUsers() { return new ArrayList<>(); }
}

支持的默认值表达式:

  • "null" — 返回 null(对象的默认值)
  • "true" / "false" — 返回布尔值
  • 数字字面量 — "0""100""-5""3.14" — 返回该数字
  • "empty" — 根据返回类型返回空集合/数组/字符串

如果未指定 defaultValue,会使用类型默认值:

  • 对象:null
  • boolean:false
  • int、long 等:0
  • String:null
  • 集合:null

defaultValue 类型匹配

defaultValue 表达式会根据方法的返回类型进行解析。如果在返回 String 的方法上指定 defaultValue = "0",会返回字符串 "0",而不是数字零。

自定义异常处理器

通过创建 ExceptionHandler Bean 来实现自定义异常处理逻辑:

java
package com.ultikits.docs.exception;

import com.ultikits.ultitools.abstracts.UltiToolsPlugin;
import com.ultikits.ultitools.annotations.*;
import com.ultikits.ultitools.aop.ExceptionHandler;
import com.ultikits.ultitools.interfaces.DataOperator;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

@Service
public class LoggingExceptionHandler implements ExceptionHandler {

    @Override
    public Object handleException(Throwable exception, Object target, Method method, Object[] args) {
        // Log detailed exception information
        System.out.println("Exception in: " + method.getDeclaringClass().getSimpleName() + "." + method.getName());
        System.out.println("Message: " + exception.getMessage());
        exception.printStackTrace();
        return null;
    }

    @Override
    public boolean supports(Class<? extends Throwable> exceptionType) {
        // This handler supports any exception
        return true;
    }
}

注册并通过名称引用处理器:

java
package com.ultikits.docs.exception;

import com.ultikits.ultitools.abstracts.UltiToolsPlugin;
import com.ultikits.ultitools.annotations.*;
import com.ultikits.ultitools.aop.ExceptionHandler;
import com.ultikits.ultitools.interfaces.DataOperator;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

@Service
public class MyService {

    @ExceptionCatch(handler = "loggingExceptionHandler")
    public String processData() {
        // If an exception occurs, LoggingExceptionHandler.handleException() is called
        return getData();
    }

    private String getData() { return ""; }
}

处理器接口

自定义处理器实现 ExceptionHandler 接口,其中 handleException(Throwable, Object, Method, Object[]) 承载主处理逻辑,可以返回替换值,也可以重新抛出异常。 supports(Class) 是可选方法,用于表示该处理器是否支持某个异常类型,默认对所有类型返回 true。 getOrder() 同样可选,用于设置优先级,数值越低优先级越高,默认值为 0。

方法要求

@ExceptionCatch 仅对受 IoC 容器管理的 Bean 中的方法有效:

java
@Service
public class MyService {

    @ExceptionCatch  // 正确 - 方法在受管理的 @Service Bean 中
    public void safeOperation() {
        // ...
    }
}

public class NonManagedClass {

    @ExceptionCatch  // 错误 - 此类不是 Bean
    public void unsafeOperation() {
        // 注解无效
    }
}

支持的 Bean 类型:

  • @Service — 服务
  • @Component — 通用 Bean
  • 任何手动注册到 IoC 容器中的类

完整示例

java
package com.ultikits.docs.exception;

import com.ultikits.ultitools.abstracts.UltiToolsPlugin;
import com.ultikits.ultitools.annotations.*;
import com.ultikits.ultitools.aop.ExceptionHandler;
import com.ultikits.ultitools.interfaces.DataOperator;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

@Service
public class UserDatabaseService {

    @Autowired
    private UltiToolsPlugin plugin;

    // Safe read: returns null on any exception, with logging
    @ExceptionCatch
    public User findById(String userId) {
        DataOperator<User> op = plugin.getDataOperator(User.class);
        return op.query().where("id").eq(userId).first();
    }

    // Safe read with default: returns empty list if query fails
    @ExceptionCatch(defaultValue = "empty")
    public List<User> findByRole(String role) {
        DataOperator<User> op = plugin.getDataOperator(User.class);
        return op.query().where("role").eq(role).list();
    }

    // Safe with silent mode: no logging for file-not-found
    @ExceptionCatch(value = FileNotFoundException.class, silent = true)
    public String loadUserData(String filename) {
        return readFile(filename);
    }

    // Safe with custom handler: detailed error reporting
    @ExceptionCatch(
        value = {SQLException.class, IOException.class},
        handler = "detailedErrorHandler",
        defaultValue = "null"
    )
    public String exportUsers() {
        // If SQLException or IOException occurs, detailedErrorHandler is invoked
        return performExport();
    }

    // Critical operation: no exception catching, propagates up
    public void deleteUser(String userId) {
        // No @ExceptionCatch - exceptions must be handled by caller
        DataOperator<User> op = plugin.getDataOperator(User.class);
        op.query().where("id").eq(userId).delete();
    }

    private String readFile(String filename) { return ""; }

    private String performExport() { return ""; }
}

最佳实践

自定义处理器适合用于容错,在预期会出现故障或非关键的方法上捕获异常。 指定具体的异常类型,例如 @ExceptionCatch(IOException.class),而不是捕获所有异常;除非有充分理由,否则保持 silent = false。 提供有意义的默认值,例如集合用 defaultValue = "empty"、计数器用 "0",并把 @ExceptionCatch 配合为容错设计的 @Service Bean 一起使用。

相关文章

  • IoC 容器 — Bean 的管理和代理机制
  • 事务 — 使用 @Transactional 进行声明式事务管理
  • 定时任务 — 使用生命周期管理的自动任务调度

贡献者

The avatar of contributor named as Ling Bao Ling Bao
The avatar of contributor named as Claude Fable 5.1 Claude Fable 5.1

基于 MIT 许可发布