java-FreeMarker3.4自定义异常处理
问题背景
最近在项目中使用 FreeMarker 作为模板引擎时,遇到了一个问题:当模板编译过程中出现语法错误或运行时异常时,FreeMarker 默认的行为是直接打印异常堆栈,跟踪了一些内容后发现堆栈的内容只有小部分有用,大部分都是freemarker底层的逻辑调用,对于问题定位没有什么帮助,反而增加了问题定难度。
于是我希望能够自定义异常处理逻辑,在编译出错时,能够提供更友好的错误信息,只展示必要的错误内容,不看堆栈信息。
环境
- JDK17
- Spring Boot 3.4.5
- FreeMarker 2.3.34
问题分析
FreeMarker 默认的异常处理机制(TemplateExceptionHandler)提供了几种策略:
RETHROW_HANDLER:直接重新抛出异常(默认)DEBUG_HANDLER:打印异常栈信息到标准错误输出HTML_DEBUG_HANDLER:将异常栈信息格式化为HTML输出IGNORE_HANDLER:忽略异常,继续执行
但在实际的生产环境中,我们往往需要更灵活的控制,比如:
- 将异常信息以特定的格式输出到页面
- 记录详细的异常栈信息到日志文件
- 根据不同的环境(开发/生产)采用不同的处理策略
解决方案
FreeMarker 提供了自定义异常处理的扩展点,我们可以通过实现 TemplateExceptionHandler 接口来自定义异常处理逻辑。下面是现方案:
1. 创建自定义异常处理器
java
@Configuration
public class FreemarkerConfig {
/**
* FreeMarker配置器
*/
@Bean
public FreeMarkerConfigurer freeMarkerConfigurer(DbTemplateLoader dbLoader, ResourceTemplateLoader resLoader) {
FreeMarkerConfigurer fmkConf = new FreeMarkerConfigurer();
// 设置自定义异常处理器
Properties properties = new Properties();
properties.setProperty(
freemarker.template.Configuration.TEMPLATE_EXCEPTION_HANDLER_KEY,
MyTemplateExceptionHandler.class.getName()
);
fmkConf.setFreemarkerSettings(properties);
return fmkConf;
}
/**
* 自定义异常处理器
* 用于在模板编译或执行出错时,提供更友好的错误信息
*/
public static class MyTemplateExceptionHandler implements TemplateExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(MyTemplateExceptionHandler.class);
@Override
public void handleTemplateException(
TemplateException te,
Environment env,
Writer writer
) throws TemplateException {
var sbr = new StringBuilder();
fillExceptionStackTrace(te, sbr, 1);
try {
writer.write(sbr.toString());
} catch (IOException e) {
throw new TemplateException("Failed to print error message. Cause: " + e, env);
}
}
/**
* 递归构建异常栈信息
*
* @param ex 异常对象
* @param err 错误信息构建器
* @param level 当前递归深度(防止无限递归)
*/
private void fillExceptionStackTrace(Throwable ex, StringBuilder err, int level){
if(ex==null || level>=5)
return;
err.append(" <br> at ").append(ex.getMessage());
fillExceptionStackTrace(ex.getCause(),err,level++);
}
}
}
2. 支持环境区分(增强版)
在实际项目中,我们可能需要在不同环境中使用不同的错误处理策略:
java
/**
* 支持环境区分的异常处理器工厂
*/
@Component
public class TemplateExceptionHandlerFactory {
@Value("${spring.profiles.active:dev}")
private String activeProfile;
/**
* 根据环境创建异常处理器
*/
public TemplateExceptionHandler createHandler() {
if ("prod".equals(activeProfile)) {
// 生产环境:只记录日志,不显示详细错误
return new ProductionExceptionHandler();
} else {
// 开发/测试环境:显示详细错误信息
return new DevelopmentExceptionHandler();
}
}
/**
* 开发环境异常处理器
*/
private static class DevelopmentExceptionHandler implements TemplateExceptionHandler {
@Override
public void handleTemplateException(TemplateException te, Environment env, Writer writer)
throws TemplateException {
// 显示详细错误信息(同上文实现)
// ...
}
}
/**
* 生产环境异常处理器
*/
private static class ProductionExceptionHandler implements TemplateExceptionHandler {
@Override
public void handleTemplateException(TemplateException te, Environment env, Writer writer)
throws TemplateException {
// 只记录日志,不暴露详细错误
logger.error("模板执行异常(已屏蔽详细信息)", te);
try {
writer.write("<div class='error'>系统繁忙,请稍后再试</div>");
} catch (IOException e) {
throw new TemplateException("输出错误信息失败", env);
}
}
}
}