SpringBoot集成FreeMarker 作者: nbboy 时间: 2020-04-02 分类: 默认分类,Java 评论 ## SpringBoot 集成Freemarker Freemarker是一个Java模板系统,集成非常简单。 1. 第一步在POM文件中引入自动配置包 ``` org.springframework.boot spring-boot-starter-freemarker ``` 2. 第二部在模板目录下创建模板test.ftlh,内容为 ```html ${msg} ``` 3. 第三部创建调用的Controller ```java @Controller @RequestMapping("/auth") public class AuthController { @RequestMapping("/test") public String test(Model model){ model.addAttribute("msg", "Hello,Freemarker"); return "auth/test"; } } ``` 4.运行,就能看到动态内容已经渲染出来了。 ## Freemarker API简单使用 ## SpringMVC调用Freemarker浅析 完整的MVC调用链这里先隐去,从org.springframework.web.servlet.DispatcherServlet#doDispatch看下调用的逻辑: ```java protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // Determine handler for the current request. mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } //这里的开始处理控制器返回的内容,渲染视图就在这里面处理 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } } ``` 在org.springframework.web.servlet.DispatcherServlet#processDispatchResult里面,我们看到了渲染的方法。 ```java private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception { boolean errorView = false; //省略部分代码 // Did the handler return a view to render? if (mv != null && !mv.wasCleared()) { render(mv, request, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); } } //省略部分代码 } ``` 再看下这个方法org.springframework.web.servlet.DispatcherServlet#render,完整的查找模板解析器和解析器渲染的过程就在这里。 ```java protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { // Determine locale for request and apply it to the response. //省略部分代码 View view; //获得视图名称 String viewName = mv.getViewName(); if (viewName != null) { // We need to resolve the view name. //这里查找到模板解析器 view = resolveViewName(viewName, mv.getModelInternal(), locale, request); } try { if (mv.getStatus() != null) { response.setStatus(mv.getStatus().value()); } //做真正的渲染 view.render(mv.getModelInternal(), request, response); } //省略部分代码 } ``` OK,我们看下org.springframework.web.servlet.DispatcherServlet#resolveViewName的方法是怎样的。 ```java protected View resolveViewName(String viewName, @Nullable Map model, Locale locale, HttpServletRequest request) throws Exception { if (this.viewResolvers != null) { for (ViewResolver viewResolver : this.viewResolvers) { View view = viewResolver.resolveViewName(viewName, locale); if (view != null) { return view; } } } return null; } ``` 我们的Freemarker解析器FreeMarkerViewResolver也在里面,这个解析器FreeMarkerViewResolver实现了ViewResolver接口就可以了。 至于渲染,刚才也看到会调用view的render方法,然后通过层层调用,最后调用org.springframework.web.servlet.view.freemarker.FreeMarkerView#doRender方法,方法如下所述: ```java protected void doRender(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception { // Expose model to JSP tags (as request attributes). exposeModelAsRequestAttributes(model, request); // Expose all standard FreeMarker hash models. SimpleHash fmModel = buildTemplateModel(model, request, response); // Grab the locale-specific version of the template. Locale locale = RequestContextUtils.getLocale(request); processTemplate(getTemplate(locale), fmModel, response); } protected void processTemplate(Template template, SimpleHash model, HttpServletResponse response) throws IOException, TemplateException { template.process(model, response.getWriter()); } ``` 至此,MVC里的调用就结束了,Template是Freemarker包下的模板对象。也可看到最后带过去的是数据模型和输出流,有了这两个东西,要动态输出是轻而易举的事情了。 ## SpringBoot Freemarker 自动配置如何实现 配置Freemarker主要是通过org.springframework.boot.autoconfigure.freemarker.FreeMarkerProperties实现的,不过有一些属性是来自基类org.springframework.boot.autoconfigure.template.AbstractViewResolverProperties,其中属性还是挺多,主要常用的比如 ```java /** * Whether to enable MVC view resolution for this technology. */ private boolean enabled = true; /** * Whether to enable template caching. */ private boolean cache; /** * Content-Type value. */ private MimeType contentType = DEFAULT_CONTENT_TYPE; /** * Template encoding. */ private Charset charset = DEFAULT_CHARSET; /** * Prefix that gets prepended to view names when building a URL. */ private String prefix; /** * Suffix that gets appended to view names when building a URL. */ private String suffix; ``` ## 总结 虽然现在都提倡前后端分离,不过适当了解了解模板渲染有关的内容也是挺有帮助的,因为就目前来说毕竟用SPA一体化富应用在SEO方面做得不够理想,还有对于比较简单不怎么交互的页面也完全可以用后端渲染完成,所以在这方面花适当的时间还是值得的。