Spring Boot中如何在ControllerAdvice捕获FreeMarker TemplateException
尝试在@ControllerAdvice中捕获FreeMarker的TemplateException以实现异常日志记录,但自定义异常处理器无法捕获该异常,仅在控制台输出模板错误信息。
现有配置代码
FreeMarker配置类
@Configuration public class FreeMarkerConfig { @Bean public ViewResolver viewResolver() { FreeMarkerViewResolver resolver = new FreeMarkerViewResolver(); resolver.setCache(true); resolver.setPrefix("classpath:/templates/"); resolver.setSuffix(".ftlh"); return resolver; } @Bean public FreeMarkerConfigurer freemarkerConfig() { FreeMarkerConfigurer freeMarkerConfigurer = new FreeMarkerConfigurer(); freeMarkerConfigurer.setTemplateLoaderPath("classpath:/templates/"); Properties settings = new Properties(); settings.setProperty(freemarker.template.Configuration.TEMPLATE_EXCEPTION_HANDLER_KEY, "rethrow"); freeMarkerConfigurer.setFreemarkerSettings(settings); return freeMarkerConfigurer; } }
自定义异常处理器
@ControllerAdvice public class CustomExceptionHandler { private static final Logger logger = LogManager.getLogger(CustomExceptionHandler.class); @ExceptionHandler(Exception.class) public String handleException(final HttpServletRequest request, final Exception e) { String incidentId = logException(request, e); return "redirect:/error?incident-id=" + incidentId; } @ExceptionHandler(TemplateException.class) public ResponseEntity<Object> handleTemplEx(Exception e, HttpServletRequest request, HttpServletResponse response) { logException(request, e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } }
控制台错误信息
18:16:39 | ERROR | http-nio-8080-exec-1 | freemarker.runtime | Error executing FreeMarker template
freemarker.core.InvalidReferenceException: The following has evaluated to null or missing:
==> articleTagIndice1s [in template "themes/article.ftlh" at line 7, column 20]
解决方案
核心原因
Spring MVC渲染FreeMarker视图时,默认会捕获TemplateException并包装为ServletException,导致@ControllerAdvice无法直接捕获原始的TemplateException。即便配置了TEMPLATE_EXCEPTION_HANDLER_KEY="rethrow",也会被Spring的视图处理逻辑拦截包裹。
修复步骤
修改FreeMarkerViewResolver配置
自定义视图构建逻辑,开启视图渲染错误时直接抛出异常的配置:@Bean public ViewResolver viewResolver() { FreeMarkerViewResolver resolver = new FreeMarkerViewResolver() { @Override protected AbstractUrlBasedView buildView(String viewName) throws Exception { FreeMarkerView view = (FreeMarkerView) super.buildView(viewName); // 禁用路径变量暴露,避免异常被Spring额外包装 view.setExposePathVariables(false); // 开启渲染错误时直接抛出异常 view.setThrowExceptionOnRenderError(true); return view; } }; resolver.setCache(true); resolver.setPrefix("classpath:/templates/"); resolver.setSuffix(".ftlh"); return resolver; }优化FreeMarker配置的异常处理器
使用枚举值替代字符串配置,避免拼写错误,确保异常被重新抛出:settings.setProperty(Configuration.TEMPLATE_EXCEPTION_HANDLER_KEY, TemplateExceptionHandler.RETHROW_HANDLER.getIdentifier());调整异常处理器方法签名
直接接收TemplateException类型参数,保证匹配精准:@ExceptionHandler(TemplateException.class) public ResponseEntity<Object> handleTemplateException(TemplateException e, HttpServletRequest request, HttpServletResponse response) { logException(request, e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); }
验证
完成以上调整后,模板渲染时的InvalidReferenceException等TemplateException子类异常会被全局异常处理器捕获,执行自定义日志记录逻辑,而非仅输出默认控制台日志。
内容的提问来源于stack exchange,提问作者DrWebber

