异常处理
自 v6.2.0 起
自动捕获和处理服务方法中的异常。
UltiTools 通过 @ExceptionCatch 注解提供声明式异常处理。无需在服务方法调用处用 try-catch 块包裹,只需为方法添加注解,框架根据你的配置自动处理异常。
v6.2.5 中没有任何代码读取 @ExceptionCatch
在 v6.2.5 里,aop 包与框架其余代码之间只剩两处 javadoc 引用:没有代理被创建,没有 advisor 被注册,ExceptionInterceptor 从不被实例化,因此带注解的方法抛出的异常与不带注解时完全一样,silent、value、defaultValue、handler 四个属性均不产生作用。 接线发布之前,用普通的 try-catch 包住调用:本页描述的全部内容,包括下文按名字查找处理器的部分,都依赖这同一处缺失的连接。 该接线已合入开发分支,未包含在 v6.2.5,跟踪于 issue #190。
基本用法
在任意受容器管理的 Bean(如 @Service)的方法上添加 @ExceptionCatch:
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 等)
注解属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
value | Class<? extends Throwable>[] | {Exception.class} | 要捕获的异常类型。子类会自动被包含。 |
silent | boolean | false | 为 true 时,异常被静默捕获,不记录日志。为 false 时,异常被记录为警告。无论哪种情况,异常都会被同时上报给框架的 ErrorReportCollector。 |
handler | String | "" | 自定义异常处理器 Bean 的名称。该 Bean 必须实现 ExceptionHandler 接口。 |
defaultValue | String | "" | 异常被捕获时返回的值的表达式。 |
捕获特定异常
指定应该被捕获的异常类型:
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) 会捕获 FileNotFoundException、EOFException 等 IOException 的子类。
静默模式
对于已预期的或非关键异常,禁用日志记录:
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:
- 非关键操作(如可选备份)
- 后备逻辑(如文件未找到时使用默认值)
- 异常是预期的操作
默认返回值
控制异常被捕获时返回的值:
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 来实现自定义异常处理逻辑:
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;
}
}注册并通过名称引用处理器:
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 中的方法有效:
@Service
public class MyService {
@ExceptionCatch // 正确 - 方法在受管理的 @Service Bean 中
public void safeOperation() {
// ...
}
}
public class NonManagedClass {
@ExceptionCatch // 错误 - 此类不是 Bean
public void unsafeOperation() {
// 注解无效
}
}支持的 Bean 类型:
@Service— 服务@Component— 通用 Bean- 任何手动注册到 IoC 容器中的类
完整示例
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 一起使用。