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);
            }
        }
    }
}
相关推荐
美狐美颜sdk2 小时前
抖动特效在直播美颜sdk中的实现方式与优化思路
前端·图像处理·人工智能·深度学习·美颜sdk·直播美颜sdk·美颜api
Mr Xu_2 小时前
Vue3 + Element Plus 实战:App 版本管理后台——动态生成下载二维码与封装文件上传
前端·javascript·vue.js
java1234_小锋2 小时前
Java中读写锁的应用场景是什么?
java·开发语言
闻哥2 小时前
从 AJAX 到浏览器渲染:前端底层原理与性能指标全解析
java·前端·spring boot·ajax·okhttp·面试
比特森林探险记2 小时前
Vue基础语法与响应式系统详解
前端·javascript·vue.js
「QT(C++)开发工程师」2 小时前
C++ 多种单例模式
java·c++·单例模式
短剑重铸之日2 小时前
《SpringCloud实用版》统一认证授权:Spring Authorization Server + OAuth2 + JWT 生产级方案
java·后端·spring·jwt·oauth2
hrrrrb2 小时前
【算法设计与分析】随机化算法
人工智能·python·算法