感谢每一个帮助。我找不到我犯的错误。模板在最初的“${error}”时不会被 FreeMarker 进一步处理。我阅读了文档并与其他人进行了比较。但它以这个 stackstrace 结束,我真的不知道我的具体错误是什么:
我收到了这个堆栈跟踪,但“错误”在 ModelMap 中填充为字符串对象:
FreeMarker template error (DEBUG mode; use RETHROW in production!): The following has evaluated to null or missing: ==> error [in template "freemarker/api-error.ftl" at line 128, column 62] ---- Tip: If the failing expression is known to legally refer to something that's sometimes null or missing, either specify a default value like myOptionalVar!myDefault, or use <#if myOptionalVar??>when-present<#else>when-missing. (These only cover the last step of the expression; to cover the whole expression, use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)?? ---- ---- FTL stack trace ("~" means nesting-related): - Failed at: ${error} [in template "freemarker/api-error.ftl" at line 128, column 60] ---- Java stack trace (for programmers): ---- freemarker.core.InvalidReferenceException: [... Exception message was already printed; see it above ...] at freemarker.core.InvalidReferenceException.getInstance(InvalidReferenceException.java:134) at freemarker.core.EvalUtil.coerceModelToTextualCommon(EvalUtil.java:481) at freemarker.core.EvalUtil.coerceModelToStringOrMarkup(EvalUtil.java:401) at freemarker.core.EvalUtil.coerceModelToStringOrMarkup(EvalUtil.java:370) at freemarker.core.DollarVariable.calculateInterpolatedStringOrMarkup(DollarVariable.java:104) at freemarker.core.DollarVariable.accept(DollarVariable.java:63) at freemarker.core.Environment.visit(Environment.java:335) at freemarker.core.Environment.visit(Environment.java:341) at freemarker.core.Environment.process(Environment.java:314) at freemarker.template.Template.process(Template.java:383) at org.springframework.web.servlet.view.freemarker.FreeMarkerView.processTemplate(FreeMarkerView.java:391) at org.springframework.web.servlet.view.freemarker.FreeMarkerView.doRender(FreeMarkerView.java:304) at org.springframework.web.servlet.view.freemarker.FreeMarkerView.renderMergedTemplateModel(FreeMarkerView.java:255) at org.springframework.web.servlet.view.AbstractTemplateView.renderMergedOutputModel(AbstractTemplateView.java:181) at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:316) at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1406) at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1150) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:965) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) at javax.servlet.http.HttpServlet.service(HttpServlet.java:529) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) at javax.servlet.http.HttpServlet.service(HttpServlet.java:623) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:209) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:352) at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:117) at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:83) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:361) at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:126) at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:120) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:361) at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:131) at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:361) at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:361) at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:164) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:361) at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:361) at edu.remad.springconfig.security.filters.TenantFilter.doFilter(TenantFilter.java:27) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:361) at org.springframework.security.web.session.ConcurrentSessionFilter.doFilter(ConcurrentSessionFilter.java:151) at org.springframework.security.web.session.ConcurrentSessionFilter.doFilter(ConcurrentSessionFilter.java:129) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:361) at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:227) at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:221) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:361) at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:361) at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:118) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:361) at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:361) at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:361) at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:361) at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:361) at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:361) at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:225) at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:190) at org.springframework.security.web.debug.DebugFilter.invokeWithWrappedRequest(DebugFilter.java:90) at org.springframework.security.web.debug.DebugFilter.doFilter(DebugFilter.java:78) at org.springframework.security.web.debug.DebugFilter.doFilter(DebugFilter.java:67) at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:354) at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:168) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:481) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:130) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:670) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:390) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:928) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1794) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.base/java.lang.Thread.run(Thread.java:833)
控制器:
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import edu.remad.springconfig.globalexceptions.Error;
import edu.remad.springconfig.globalexceptions.ErrorInfo;
import edu.remad.springconfig.globalexceptions.ErrorUtils;
import edu.remad.springconfig.globalexceptions.HttpStatus401Exception;
import edu.remad.springconfig.globalexceptions.HttpStatus403Exception;
import edu.remad.springconfig.globalexceptions.HttpStatus404Exception;
import edu.remad.springconfig.globalexceptions.HttpStatus500Exception;
@Controller
@RequestMapping(HttpStatusExceptionController.REQUEST_MAPPING_EXCEPTIONS)
public class HttpStatusExceptionController {
private static final String GET_MAPPING_401 = "/401";
private static final String GET_MAPPING_403 = "/403";
private static final String GET_MAPPING_404 = "/404";
private static final String GET_MAPPING_500 = "/500";
private static final String GET_API_ERROR = "/api-error";
public static final String REQUEST_MAPPING_EXCEPTIONS = "/exceptions";
@GetMapping(value = GET_API_ERROR)
public String getApiError(@ModelAttribute("model") ModelMap model) {
String url = REQUEST_MAPPING_EXCEPTIONS + GET_MAPPING_500;
ErrorInfo errorInfo = new ErrorInfo(url, Error.HTTP_500_ERROR, "Test HTTP Status 500", "HTTP 500 thrown");
HttpStatus500Exception ex = new HttpStatus500Exception("Upps here happened a Http Status 500 error", new Throwable(), errorInfo);
model.addAllAttributes(ErrorUtils.fillExceptionModelMap(ex));
String template = ex.getTemplate();
return template;
}
}
api-error.ftl:
<html>
<head>
<title>
</title>
<style>
.modal-container {
margin: 250 auto;
z-index: 1000;
background-color: transparent;
display: flex;
opacity: 1;
pointer-events: none;
transition: opacity 250ms ease;
}
.modal-section {
margin: auto;
width: 90%;
max-width: 40rem;
background: linear-gradient(135deg, hsla(212, 98%, 73%, 0.96),hsla(199, 74%, 73%, 0.74));
border-radius: 1.5rem;
box-shadow: 0 1rem 2rem #000a;
}
.modal-header {
display: flex;
justify-content: space-between;
background-color: hsla(212, 73%, 50%, 0.74);
border-radius: 1.5rem 1.5rem 0 0;
border: 4px solid hsla(212, 85%, 72%, 0.84);
border-bottom: none;
}
.modal-title {
margin: 0;
color: black;
}
.modal-close {
color: transparent;
display: block;
overflow: hidden;
background-image:
linear-gradient(
to top right,
transparent 48%,
black 48%,
black 52%,
transparent 52%
),
linear-gradient(
to top left,
transparent 48%,
black 48%,
black 52%,
transparent 52%
);
}
.modal-close:hover,
.modal-close:focus {
background-image:
linear-gradient(
to top right,
transparent 46%,
black 46%,
black 54%,
transparent 54%
),
linear-gradient(
to top left,
transparent 46%,
black 46%,
black 54%,
transparent 54%
);
}
.modal-content {
color: black;
border-radius: 0 0 1.5rem 1.5rem;
border: 4px solid #f7baf744;
border-top: none;
}
.modal-header,
.modal-content {
padding: 1.5rem;
}
.modal-container:target {
opacity: 0;
pointer-events: all;
}
.button {
text-decoration: none;
display: inline-block;
padding: 0.5rem 1.25rem;
color: white;
background: #4c90b2;
border: 1px solid #2d566b;
border-radius: 0.5rem;
background-image:
linear-gradient(
to bottom,
hsla(200, 40%, 80%, 0.4),
transparent,
hsla(200, 40%, 20%, 0.6)
);
transition: background-color 0.5s ease;
}
.button::hover,
.button::focus {
background-color: transparent;
}
body {
background-color: rgba(0,0,0,0.65);
}
</style>
</head>
<body>
<div class="modal-container">
<section class="modal-section">
<header class="modal-header">
<h2 class="modal-title">${error}</h2>
<a href="#" class="modal-close">close</a>
</header>
<div class="modal-content">
<br/>
<p>${code}</p>
<p>url: ${url}</p>
<br />
<p>${message}</p>
<p><a class="button" href="mailto:${email}">${email}</a></p>
</div>
</section>
</div>
</body>
</html>
错误实用程序:
import org.springframework.ui.ModelMap;
public final class ErrorUtils {
private ErrorUtils() {
}
public static ModelMap fillExceptionModelMap(HttpStatusException exception) {
ModelMap modelMap = new ModelMap();
modelMap.addAttribute("code", exception.getCode());
modelMap.addAttribute("email", exception.getEMail());
modelMap.addAttribute("localizedMessage", exception.getLocalizedMessage());
modelMap.addAttribute("message", exception.getMessage());
modelMap.addAttribute("url", exception.getUrl());
modelMap.addAttribute("error", exception.getError().getError());
modelMap.addAttribute("httpStatus", exception.getError().getHttpStatus().name());
return modelMap;
}
}
HttpStatusException 是一个接口:
import org.springframework.http.HttpStatus;
public interface HttpStatusException {
String getUrl();
Error getError();
String getAdditionalText();
HttpStatus getHttpStatus();
String getCode();
String getTemplate();
String getEMail();
String getMessage();
String getLocalizedMessage();
ErrorInfo getErrorInfo();
Throwable getCause();
String getNestedErrorMessage();
}
具体实现是这样的
public class HttpStatus500Exception extends HttpStatusCodeException implements HttpStatusException {
private final String message;
private final ErrorInfo errorInfo;
private final Throwable cause;
public HttpStatus500Exception(String message, Throwable cause, ErrorInfo info) {
super(info.getError().getHttpStatus());
this.message = message;
this.cause = cause;
errorInfo = info;
}
private static final long serialVersionUID = 1L;
@Override
public String getUrl() {
return errorInfo.getUrl();
}
@Override
public Error getError() {
return errorInfo.getError();
}
@Override
public String getAdditionalText() {
return errorInfo.getAdditionalText();
}
@Override
public HttpStatus getHttpStatus() {
return errorInfo.getError().getHttpStatus();
}
@Override
public String getCode() {
return errorInfo.getError().getCode();
}
@Override
public String getTemplate() {
return errorInfo.getError().getTemplate();
}
@Override
public String getEMail() {
return errorInfo.getError().geteMail();
}
@Override
public ErrorInfo getErrorInfo() {
return errorInfo;
}
@Override
public Throwable getCause() {
return cause;
}
@Override
public String getNestedErrorMessage() {
return message;
}
@Override
public String getMessage() {
return errorInfo.getError().getMessage();
}
}
错误信息:
public class ErrorInfo {
private final String url;
private final Error error;
private final String additionalText;
private final String localizedMessage;
public ErrorInfo(String url, Error error, String additionalText, String localizedMessage) {
this.url = url;
this.error = error;
this.additionalText = additionalText;
this.localizedMessage = localizedMessage;
}
public String getUrl() {
return url;
}
public Error getError() {
return error;
}
public String getAdditionalText() {
return additionalText;
}
public String getLocalizedMessage() {
return localizedMessage;
}
}
错误:
import org.springframework.http.HttpStatus;
public enum Error {
HTTP_500_ERROR(HttpStatus.INTERNAL_SERVER_ERROR,"Internal Server Error","ERR_500","api-error", "[email protected]", "Please contact by errors our support."),
HTTP_404_ERROR(HttpStatus.NOT_FOUND, "Not found", "ERR_404","api-error", "[email protected]", "Please contact by errors our support."),
HTTP_403_ERROR(HttpStatus.FORBIDDEN,"Forbidden","ERR_403","api-error","[email protected]","Please contact by errors our support."),
HTTP_401_ERROR(HttpStatus.UNAUTHORIZED,"Unauthorized","ERR_401","api-error","[email protected]","Please contact by errors our support.");
private final HttpStatus httpStatus;
private final String error;
private final String code;
private final String template;
private final String eMail;
private final String message;
Error(HttpStatus internalServerError, String error, String errorCode, String templateFile, String email,
String supportMessage) {
httpStatus = internalServerError;
this.error = error;
code = errorCode;
template = templateFile;
eMail = email;
message = supportMessage;
}
public HttpStatus getHttpStatus() {
return httpStatus;
}
public String getError() {
return error;
}
public String getCode() {
return code;
}
public String getTemplate() {
return template;
}
public String geteMail() {
return eMail;
}
public String getMessage() {
return message;
}
}
自由标记配置:
@Configuration
public class FreeMarkerConfig {
@Bean
public FreeMarkerViewResolver freeMarkerViewResolver() {
FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
resolver.setCache(true);
resolver.setPrefix("/freemarker/");
resolver.setSuffix(".ftl");
resolver.setOrder(0);
return resolver;
}
@Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
Properties properties = new Properties();
properties.put("auto_import", "/spring.ftl as spring");
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setTemplateLoaderPath("/WEB-INF/templates");
configurer.setDefaultEncoding(StandardCharsets.UTF_8.displayName());
configurer.setFreemarkerSettings(properties);
return configurer;
}
}
我尝试了有关堆栈溢出的所有类似问题来解决异常。我阅读了 FreeMarker Apache Docs 并尝试了它们,但没有任何变化。经过 3 天的调查后,我希望有人能够支持修复该异常。 我所期望的错误内容是在标记中解析的。
对我来说,我努力将类型 ModelMap 更改为 Model。为什么我不明白。