一、乱码问题
JSP中的乱码问题一般处在<@page>
头部,而在FreeMarker中为避免乱码,需要统一下配置:
#application.properties配置文件中添加spring.freemarker.settings.defaultEncoding=UTF-8spring.freemarker.charset=UTF-8spring.freemarker.content-type=text/html;charset=UTF-8
二、request中取值问题
如果你是从JSP转到了FreeMarker,除了语法上需要重新学习之外,还有很多在JSP中用的比较顺手的属性可能在FreeMarker中需要重新的认知了。
原先的EL表达式中有一些属性在FreeMarker中就不能使用了,例如常用到的${pageContext.request.contextPath}
取绝对路径前缀的,在FreeMarker中恐怕不能做到了。 那么像EL中的requestScope和sessionScope还可以用不?其实在FreeMarker中是可以用的,取值范围有JspTaglibs、Application、Session、Request、RequestParameters,例如${Request.name}
。 不过因为FreeMarker的机制问题,这些前缀并不能像EL中那样省略。 上述的代码逻辑在FreeMarkerView.buildTemplateModel()中: protected SimpleHash buildTemplateModel(Mapmodel, HttpServletRequest request, HttpServletResponse response) { AllHttpScopesHashModel fmModel = new AllHttpScopesHashModel(getObjectWrapper(), getServletContext(), request); fmModel.put(FreemarkerServlet.KEY_JSP_TAGLIBS, this.taglibFactory); fmModel.put(FreemarkerServlet.KEY_APPLICATION, this.servletContextHashModel); fmModel.put(FreemarkerServlet.KEY_SESSION, buildSessionModel(request, response)); fmModel.put(FreemarkerServlet.KEY_REQUEST, new HttpRequestHashModel(request, response, getObjectWrapper())); fmModel.put(FreemarkerServlet.KEY_REQUEST_PARAMETERS, new HttpRequestParametersHashModel(request)); fmModel.putAll(model); return fmModel; }
另外,我发现spring boot的配置中有这么两行:
spring.freemarker.expose-request-attributes=truespring.freemarker.expose-session-attributes=true
意思是把request和session中的属性导出到Freemarker的dataModel当中,这又是什么意思呢?
怀着好奇心,我探索了一下spring boot的源码,因为spring boot有很多自动装载的配置,我们找到FreeMarkerAutoConfiguration类:@Configuration@ConditionalOnClass({ freemarker.template.Configuration.class, FreeMarkerConfigurationFactory.class })@AutoConfigureAfter(WebMvcAutoConfiguration.class)@EnableConfigurationProperties(FreeMarkerProperties.class)public class FreeMarkerAutoConfiguration{ ...}
这里引用了FreeMarkerProperties
属性文件,FreeMarkerProperties
中有ExposeRequestAttributes
属性。我们将在AbstractTemplateViewResolverProperties.applyToViewResolver()
方法中找到痕迹:
public void applyToViewResolver(Object viewResolver) { ... resolver.setExposeRequestAttributes(isExposeRequestAttributes()); ... }
然后在AbstractTemplateView
中会用到这两个属性进行逻辑处理:
protected final void renderMergedOutputModel( Mapmodel, HttpServletRequest request, HttpServletResponse response) throws Exception { if (this.exposeRequestAttributes) { for (Enumeration en = request.getAttributeNames(); en.hasMoreElements();) { String attribute = en.nextElement(); if (model.containsKey(attribute) && !this.allowRequestOverride) { throw new ServletException("Cannot expose request attribute '" + attribute + "' because of an existing model object of the same name"); } Object attributeValue = request.getAttribute(attribute); if (logger.isDebugEnabled()) { logger.debug("Exposing request attribute '" + attribute + "' with value [" + attributeValue + "] to model"); } model.put(attribute, attributeValue); } } if (this.exposeSessionAttributes) { HttpSession session = request.getSession(false); if (session != null) { for (Enumeration en = session.getAttributeNames(); en.hasMoreElements();) { String attribute = en.nextElement(); if (model.containsKey(attribute) && !this.allowSessionOverride) { throw new ServletException("Cannot expose session attribute '" + attribute + "' because of an existing model object of the same name"); } Object attributeValue = session.getAttribute(attribute); if (logger.isDebugEnabled()) { logger.debug("Exposing session attribute '" + attribute + "' with value [" + attributeValue + "] to model"); } model.put(attribute, attributeValue); } } } if (this.exposeSpringMacroHelpers) { if (model.containsKey(SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE)) { throw new ServletException( "Cannot expose bind macro helper '" + SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE + "' because of an existing model object of the same name"); } // Expose RequestContext instance for Spring macros. model.put(SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE, new RequestContext(request, response, getServletContext(), model)); } applyContentType(response); renderMergedTemplateModel(model, request, response); }
我们看到这里进行了判断,如果exposeRequestAttributes
为真,会把request和session中的属性复制到model中,也就是FreeMarker的root中。 其实流程最后还是到FreemarkerView
类,可以参考他的renderMergedTemplateModel()方法。
spring.freemarker.allow-request-override=truespring.freemarker.allow-session-override=true