( 一 )、 SpringBoot 整合 Freemarker

( 一 )、 SpringBoot 整合 Freemarker

 

 

1、简介

官网: http://freemarker.foofun.cn/

 这是一个相当老牌的开源的免费的模版引擎。通过 Freemarker 模版,我们可以将数据渲染成 HTML 网页、电子邮件、配置文件以及源代码等。Freemarker 不是面向最终用户的,而是一个 Java 类库,我们可以将之作为一个普通的组件嵌入到我们的产品中。

   Freemarker 模版后缀为 .ftl(FreeMarker Template Language)。FTL 是一种简单的、专用的语言,它不是像 Java 那样成熟的编程语言。在模板中,你可以专注于如何展现数据, 而在模板之外可以专注于要展示什么数据。
好了,这是一个简单的介绍,接下来我们来看看 Freemarker 和 Spring Boot 的一个整合操作

 

2、整合

1、maven依赖

         <!--freemarker 模板引擎-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>

 

3、freemarker 自动配置分析

1、在 org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration 类中,可以看到关于 Freemarker 的自动化配置:

@Configuration
@ConditionalOnClass({ freemarker.template.Configuration.class, FreeMarkerConfigurationFactory.class })
@EnableConfigurationProperties(FreeMarkerProperties.class)
@Import({ FreeMarkerServletWebConfiguration.class, FreeMarkerReactiveWebConfiguration.class, FreeMarkerNonWebConfiguration.class })
public class FreeMarkerAutoConfiguration {
}

从这里可以看出,当 classpath 下存在 freemarker.template.Configuration 以及 FreeMarkerConfigurationFactory 时,配置才会生效,也就是说当我们引入了 Freemarker 之后,配置就会生效。但是这里的自动化配置只做了模板位置检查,其他配置则是在导入的 FreeMarkerServletWebConfiguration 配置中完成的。那么我们再来看看 FreeMarkerServletWebConfiguration 类,部分源码如下:

@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass({ Servlet.class, FreeMarkerConfigurer.class })
@AutoConfigureAfter(WebMvcAutoConfiguration.class)
class FreeMarkerServletWebConfiguration extends AbstractFreeMarkerConfiguration {
        protected FreeMarkerServletWebConfiguration(FreeMarkerProperties properties) {
                super(properties);
        }
        @Bean
        @ConditionalOnMissingBean(FreeMarkerConfig.class)
        public FreeMarkerConfigurer freeMarkerConfigurer() {
                FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
                applyProperties(configurer);
                return configurer;
        }
        @Bean
        @ConditionalOnMissingBean(name = "freeMarkerViewResolver")
        @ConditionalOnProperty(name = "spring.freemarker.enabled", matchIfMissing = true)
        public FreeMarkerViewResolver freeMarkerViewResolver() {
                FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
                getProperties().applyToMvcViewResolver(resolver);
                return resolver;
        }
}

我们来简单看下这段源码:

  1. @ConditionalOnWebApplication 表示当前配置在 web 环境下才会生效。
  2. ConditionalOnClass 表示当前配置在存在 Servlet 和 FreeMarkerConfigurer 时才会生效。
  3. @AutoConfigureAfter 表示当前自动化配置在 WebMvcAutoConfiguration 之后完成。
  4. 代码中,主要提供了 FreeMarkerConfigurer 和 FreeMarkerViewResolver。
  5. FreeMarkerConfigurer 是 Freemarker 的一些基本配置,例如 templateLoaderPath、defaultEncoding 等
  6. FreeMarkerViewResolver 则是视图解析器的基本配置,包含了viewClass、suffix、allowRequestOverride、allowSessionOverride 等属性。

另外还有一点,在这个类的构造方法中,注入了 FreeMarkerProperties:

@ConfigurationProperties(prefix = "spring.freemarker")
public class FreeMarkerProperties extends AbstractTemplateViewResolverProperties {

    public static final String DEFAULT_TEMPLATE_LOADER_PATH = "classpath:/templates/";

    public static final String DEFAULT_PREFIX = "";

    public static final String DEFAULT_SUFFIX = ".ftlh";

    private Map<String, String> settings = new HashMap<>();

    /**
     * Comma-separated list of template paths.
     */
    private String[] templateLoaderPath = new String[] { DEFAULT_TEMPLATE_LOADER_PATH };

    /**
     * Whether to prefer file system access for template loading to enable hot detection
     * of template changes. When a template path is detected as a directory, templates are
     * loaded from the directory only and other matching classpath locations will not be
     * considered.
     */
    private boolean preferFileSystemAccess;
}

FreeMarkerProperties 中则配置了 Freemarker 的基本信息,例如模板位置在 classpath:/templates/ ,再例如模板后缀为 .ftlh,那么这些配置我们以后都可以在 application.properties 中进行修改。

 

4、自定义其他配置

如果我们要修改模版文件位置等,可以在 application.properties | yml 中进行配置:

 #  HttpServletRequest的属性是否可以覆盖controller中model的同名项
spring.freemarker.allow-request-override=false
 # HttpSession的属性是否可以覆盖controller中model的同名项
spring.freemarker.allow-session-override=false
 # 是否开启缓存
spring.freemarker.cache=false
 # 模板文件编码
spring.freemarker.charset=UTF-8
 # 是否检查模板位置
spring.freemarker.check-template-location=true
 # Content-Type的值
spring.freemarker.content-type=text/html
 # 是否将HttpServletRequest中的属性添加到Model中
spring.freemarker.expose-request-attributes=false
 # 是否将HttpSession中的属性添加到Model中
spring.freemarker.expose-session-attributes=false
 # 模板文件后缀
spring.freemarker.suffix=.ftlh
 # 模板文件位置
spring.freemarker.template-loader-path=classpath:/templates/

 

5、使用案例

1、首先我们来创建一个 User 类,如下:

public class User {
    private Long id;
    private String username;
    private String address;
    //省略 getter/setter
}

2、再来创建 UserController

@Controller
public class UserController {
    @GetMapping("/index")
    public String index(Model model) {
        List<User> users = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            User user = new User();
            user.setId((long) i);
            user.setUsername("dwTest>>>>" + i);
            user.setAddress("www.dw.com>>>>" + i);
            users.add(user);
        }
        model.addAttribute("users", users);
        return "index";
    }
}

3、最后在 freemarker 中渲染数据:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<table border="1">
    <tr>
        <td>用户编号</td>
        <td>用户名称</td>
        <td>用户地址</td>
    </tr>
    <#list users as user>
        <tr>
            <td>${user.id}</td>
            <td>${user.username}</td>
            <td>${user.address}</td>
        </tr>
    </#list>
</table>
</body>
</html>

 

6、Freemaker 模板解析工具

package com.dw.study.utils;

import com.dw.study.model.User;
import freemarker.cache.StringTemplateLoader;
import freemarker.core.Environment;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author dw
 * @ClassName FreemarkerUtils
 * @Description freemarker 模板解析工具
 * @Date 2021/10/27 22:44
 * @Version 1.0
 */
public class FreemarkerUtils {

    private static final Logger logger = LoggerFactory.getLogger(FreemarkerUtils.class);
    /**
     * 模板编码格式
     */
    private static final String DEFAULT_CHARACTER = "UTF-8";
    /**
     * freemarker 模板配置
     */
    private static Configuration cfg;


    static {
        // 指定使用的freemarker版本
        cfg = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
        cfg.setDefaultEncoding(DEFAULT_CHARACTER);
        cfg.setTagSyntax(Configuration.AUTO_DETECT_TAG_SYNTAX);
        // 取消模板的日志记录.由程序抛出,否则会记录两条
        cfg.setLogTemplateExceptions(false);
        cfg.setTemplateExceptionHandler(new MyTemplateExceptionHandler());
    }

    /**
     * 对模板进行渲染
     *
     * @param data   数据Map
     * @param tplStr 模板
     * @return
     */
    public static String generateString(Map<String, Object> data, String tplStr) {
        String result = null;
        String name = "myStrTpl";
        try {
            StringTemplateLoader stringTemplateLoader = new StringTemplateLoader();
            stringTemplateLoader.putTemplate(name, tplStr);
            cfg.setTemplateLoader(stringTemplateLoader);
            Template template = cfg.getTemplate(name);
            StringWriter out = new StringWriter();
            template.process(data, out);
            out.flush();
            result = out.toString();
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("模板解析失败!!! {}", e.getMessage());
        }
        return result;
    }

    /**
     * 将模板渲染以后保存到文件
     *
     * @param templateFileDir 模板所在目录
     * @param fileName        模板文件的名称
     * @param targetFilePath  渲染后保存文件路径/名称
     * @param dataMap         数据
     * @return
     */
    public static boolean renderingTemplateFile(String templateFileDir,
                                                           String fileName,
                                                           String targetFilePath,
                                                           Map<String, Object> dataMap) {
        boolean flag = true;
        try {
            // 设置文件所在目录的路径
            cfg.setClassForTemplateLoading(FreemarkerUtils.class, templateFileDir);
            // 获取模版
            Template template = cfg.getTemplate(fileName);
            // 设置输出文件名,和保存路径
            File outFile = new File(targetFilePath);
            // 将模板和数据模型合并生成文件 重点设置编码集
            BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "UTF-8"));
            // 生成文件
            template.process(dataMap, out);
            // 关闭流
            out.flush();
            out.close();
        } catch (Exception e) {
            logger.error("生产模板文件失败!", e);
            flag = false;
        }
        return flag;
    }


    /***
     * 解析模板时存在错误, 继续解析, 不抛出异常
     */
   private static class MyTemplateExceptionHandler implements TemplateExceptionHandler {
        @Override
        public void handleTemplateException(TemplateException te, Environment env, java.io.Writer out) throws TemplateException {
//        logger.error("模板解析时存在错误:{}", te.getMessage());
            try {
                // 渲染错误到模板文件中
                out.write("[ERROR: " + te.getMessage() + "]");
            } catch (IOException e) {
//                throw new TemplateException("Failed to print error message. Cause: " + e, env);
                logger.error("模板解析时存在错误:{}", e.getMessage());
            }
        }
    }


    public static void main(String[] args) {
        Map<String, Object> map = new HashMap<>();
//        ##############【测试渲染字符串】#########################################
//        map.put("userName", "zhengxl5566");
//        map.put("caseNo", "AJ00000001");
//        map.put("descrip", "这是描述信息==========");
////        String template="案件编号为:${caseNo!}   "
////                + " 日期为:${date!} "
////                + " 自动获取日期为:${ .now?string('yyyy年MM月dd日')}"
////                + "描述:${descrip!}";
//        String template = "<p>你好,\n" +
//                "    <#if userName == \"Java 课代表\">\n" +
//                "        111<strong>${userName}</strong>\n" +
//                "    <#elseif userName == \"zhengxl5566\">\n" +
//                "        222${userName}\n" +
//                "    <#else>\n" +
//                "        333${userName}\n" +
//                "    </#if>\n" +
//                "</p>";
//        String generateString = FreemarkerUtils.generateString(map, template);
//        System.out.println("------");
//        System.out.println(generateString);
//        ##############【测试渲染ftl文件, 并输出】#########################################
        List<User> users = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            User user = new User();
            user.setAge(i);
            user.setUsername("dwTest>>>>" + i);
            user.setAddress("www.dw.com>>>>" + i);
            users.add(user);
        }
        map.put("users", users);
        FreemarkerUtils.renderingTemplateFile("/template", "test.ftl", "G:\\test.ftl", map);
    }

}

 

7、Freemaker自定义指令、函数

参考gitee源码: https://gitee.com/my-study-project/springboot-freemarker

 

posted @ 2018-09-07 00:55  邓维-java  阅读(3456)  评论(0编辑  收藏  举报