一、Freemarker 简介

FreeMarker 是一款 老牌的开源的免费的模版引擎, 通过 Freemarker 模版,我们可以将数据渲染成 HTML 网页、电子邮件、配置文件以及源代码等。 它不是面向最终用户的,而是一个Java类库,我们可以将之作为一个普通的组件嵌入到我们的产品中。参见官方中文文档:http://freemarker.foofun.cn/index.html

来看一张来自官网的Freemarker 显示过程的图片:

springboot freemarker 生成包含图片的pdf_后缀


可以看到,Freemarker 可以将模版和数据渲染成 HTML 。

Freemarker 模版后缀为 .ftlh(FreeMarker Template Language)。FTL 是一种简单的、专用的语言,它不是像 Java 那样成熟的编程语言。在模板中,你可以专注于如何展现数据, 而在模板之外可以专注于要展示什么数据。

好了,这是一个简单的介绍,接下来我们来看看 Freemarker 和 Spring Boot 的一个整合操作。

二、实践
1、创建工程

首先创建一个 Spring Boot 工程,引入 Web、Freemarker 依赖,如下图:

springboot freemarker 生成包含图片的pdf_ide_02


springboot freemarker 生成包含图片的pdf_spring_03


值的一提的是,相较于SSM中配置freemarker,springboot中配置freemarker更为容易一些,这是因为springboot中为我们提供了freemarker的自动化配置,在idea中我们可以双击shift,搜索FreeMarkerAutoConfiguration这个类,可以看到相关的自动化配置:

@Configuration(
    proxyBeanMethods = false
)
@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 的相关依赖之后,配置就会生效。我们在创建工程的时候由于勾选了freemarker的依赖,如下:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

依赖中就带了这两个类,所以自动化配置才会生效,如果没有加依赖,此类中的配置是不生效的。由此可以看出@ConditionalOnClass这个注解在Springboot中发挥的作用非常的重要,我们见到的好多自动化配置都跟它有关系。@import注解则导入了各种环境下的配置如:Web、ReactiveWeb、NonWeb。

但是这里的自动化配置只做了模板位置检查,因为我们是Web环境,所以其他配置则是要看在导入的 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;
        }
}

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

  • @ConditionalOnWebApplication 表示当前配置在 web 环境下才会生效。
  • ConditionalOnClass 表示当前配置在存在 Servlet 和 FreeMarkerConfigurer 时才会生效。
  • @AutoConfigureAfter 表示当前自动化配置在 WebMvcAutoConfiguration 之后完成。
  • 代码中,主要提供了 FreeMarkerConfigurer 和 FreeMarkerViewResolver。
  • FreeMarkerConfigurer 是 Freemarker 的一些基本配置,例如 templateLoaderPath、defaultEncoding 等
  • 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";
        /**
         * Well-known FreeMarker keys which are passed to FreeMarker's Configuration.
         */
        private Map<String, String> settings = new HashMap<>();
}

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

注意注意,这里踩坑了

由于是参考别人的文章,里面的freemarker的后缀是“.ftl”,源码里面也是如此,所以自己的源码也没细看,后面创建模板的时候是以“.ftl”为后缀,结果后面访问请求是一直报错:

springboot freemarker 生成包含图片的pdf_spring_04


springboot freemarker 生成包含图片的pdf_ide_05


看错误应该是视图解析出错了,果不其然,点开源码,我的里面后缀是“.ftlh”,这可能是因为我用的Springboot高版本的缘故吧,所以,切记,一定要多看源码

如果我们在 SSM 的 XML 文件中自己配置 Freemarker ,也不过就是配置这些东西。现在,这些配置由 FreeMarkerServletWebConfiguration 帮我们完成了。

2、创建类

首先创建一个User类,如下:

public class User {
    private String name;
    private int id;
    private String address;
    //省略 getter/setter

再创建一个controller:

@Controller
public class UserController {
    @GetMapping("/user")
    public String user(Model model){
        List<User> users = new ArrayList<>();
        for (int i = 0; i < 10 ; i++) {
            User user = new User();
            user.setId(i);
            user.setName("macay--"+i);
            user.setAddress("西安"+i);
            users.add(user);
        }
        model.addAttribute("users",users);
        return "user";
    }
}

根据freemarker的自动配置,它回去工程中的resource/templates中去寻找一个名叫user.ftlh的文件,所以我们在该目录下创建一个user.ftlh文件:

首先我们可以自定义一个freemarker的模板,便于以后使用,其实内容与HTML是一样的:

springboot freemarker 生成包含图片的pdf_ide_06


springboot freemarker 生成包含图片的pdf_spring_07


最后在 freemarker 中渲染数据:

<body>
<table border="1">
    <tr>
        <td>编号</td>
        <td>姓名</td>
        <td>住址</td>
    </tr>
    <#list users as user>
        <tr>
            <td>${user.id}</td>
            <td>${user.name}</td>
            <td>${user.address}</td>
        </tr>
    </#list>
</table>
</body>

运行效果如下:

springboot freemarker 生成包含图片的pdf_ide_08


springboot freemarker 生成包含图片的pdf_后缀_09

3、其他配置

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

spring.freemarker.allow-request-override=false
spring.freemarker.allow-session-override=false
spring.freemarker.cache=false
spring.freemarker.charset=UTF-8
spring.freemarker.check-template-location=true
spring.freemarker.content-type=text/html
spring.freemarker.expose-request-attributes=false
spring.freemarker.expose-session-attributes=false
spring.freemarker.suffix=.ftl
spring.freemarker.template-loader-path=classpath:/templates/

配置文件按照顺序依次解释如下:

  1. HttpServletRequest的属性是否可以覆盖controller中model的同名项
  2. HttpSession的属性是否可以覆盖controller中model的同名项
  3. 是否开启缓存
  4. 模板文件编码
  5. 是否检查模板位置
  6. Content-Type的值
  7. 是否将HttpServletRequest中的属性添加到Model中
  8. 是否将HttpSession中的属性添加到Model中
  9. 模板文件后缀
  10. 模板文件位置