java-FreeMarker3.4自定义异常处理

java-FreeMarker3.4自定义异常处理

问题背景

最近在项目中使用 FreeMarker 作为模板引擎时,遇到了一个问题:当模板编译过程中出现语法错误或运行时异常时,FreeMarker 默认的行为是直接打印异常堆栈,跟踪了一些内容后发现堆栈的内容只有小部分有用,大部分都是freemarker底层的逻辑调用,对于问题定位没有什么帮助,反而增加了问题定难度。

于是我希望能够自定义异常处理逻辑,在编译出错时,能够提供更友好的错误信息,只展示必要的错误内容,不看堆栈信息。

环境

  • JDK17
  • Spring Boot 3.4.5
  • FreeMarker 2.3.34

问题分析

FreeMarker 默认的异常处理机制(TemplateExceptionHandler)提供了几种策略:

  1. RETHROW_HANDLER:直接重新抛出异常(默认)
  2. DEBUG_HANDLER:打印异常栈信息到标准错误输出
  3. HTML_DEBUG_HANDLER:将异常栈信息格式化为HTML输出
  4. 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);
            }
        }
    }
}
相关推荐
用户69371750013842 小时前
Google 正在“收紧侧加载”:陌生 APK 安装或需等待 24 小时
android·前端
蓝帆傲亦2 小时前
Web 前端搜索文字高亮实现方法汇总
前端
用户69371750013842 小时前
Room 3.0:这次不是升级,是重来
android·前端·google
Leinwin4 小时前
OpenClaw 多 Agent 协作框架的并发限制与企业化规避方案痛点直击
java·运维·数据库
qq_417695054 小时前
机器学习与人工智能
jvm·数据库·python
漫随流水4 小时前
旅游推荐系统(view.py)
前端·数据库·python·旅游
薛定谔的悦4 小时前
MQTT通信协议业务层实现的完整开发流程
java·后端·mqtt·struts
enjoy嚣士4 小时前
springboot之Exel工具类
java·spring boot·后端·easyexcel·excel工具类
罗超驿4 小时前
独立实现双向链表_LinkedList
java·数据结构·链表·linkedlist
yy我不解释5 小时前
关于comfyui的mmaudio音频生成插件时时间不一致问题(一)
python·ai作画·音视频·comfyui