多租户环境中的Spring Boot + Thymeleaf + Database TemplateResolver

时间:2016-01-01 15:32:50

标签: java spring spring-mvc spring-boot thymeleaf

我在多租户应用程序中使用Spring Boot和Thymeleaf。我也使用Thymeleaf处理电子邮件模板并生成HTML电子邮件。为此,我创建了一个ITemplateResolver,用于从数据库中检索前缀为“db:”的百万美元模板。

Spring Boot自动配置选择任何模板解析器并将它们添加到SpringTemplateEngine。所以我的模板解析器设置如下:

@Bean
@Scope(value = "tenant", proxyMode = ScopedProxyMode.INTERFACES)
public ITemplateResolver databaseTemplateResolver() {

    final DatabaseTemplateResolver resolver = 
        new DatabaseTemplateResolver(systemSettingService, emailTemplateService );

    resolver.setTemplateMode("HTML5");
    resolver.setCacheTTLMs((long) (1000*60*5)); // 5 Minutes
    resolver.setOrder(2);

    return resolver;
}

正如所料,解析器被添加到TemplateEngine,并且从数据库中读取名称以“db:”开头的所有模板。这允许我们存储由Thymeleaf引擎处理的专用电子邮件模板,以生成生成的html。

这很有效,所以看起来如此。上面指定的范围是为域确定的多租户环境中的一个租户定义的自定义范围。但是,我认为这也可能是针对这个问题的会议范围。我的想法是每个范围的TemplateResolver都不同。我们需要它,因为我们正在从租户的数据库中读取模板。

最后,我的症状:似乎第一个租户工作正常。对于任何后续租户,我在尝试处理数据库模板时遇到异常。

org.thymeleaf.exceptions.NotInitializedException: Template Resolver has not been initialized
    at org.thymeleaf.templateresolver.AbstractTemplateResolver.checkInitialized(AbstractTemplateResolver.java:156)
    at org.thymeleaf.templateresolver.AbstractTemplateResolver.resolveTemplate(AbstractTemplateResolver.java:316)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    ...

我已经尝试禁用Thymeleaf的Spring Boot自动配置并手动设置TemplateEngine,ViewResolver,TemplateResolvers等,但是遇到了同样的问题。我也试过制作所有tenant范围,但遇到了完全不同的混乱和回溯。

我有一种感觉,我做错了,或者错误的想法,在这种情况下依赖注入应该如何工作。或者,Thymeleaf引擎的实现方式与代理对象不兼容。我倾向于后者。也许我需要以某种方式扩展模板引擎,以便为每个租户初始化解析器一次?我相信,也许,Thymeleaf认为解析器已经初始化,然后当spring注入一个新的解析器时,它从未被Thymeleaf初始化,因此是例外。

任何人都可以向我推进正确的方向吗?谢谢。

修改 以下是Thymeaf的TemplateEngine方法initialize()的代码,该方法在处理任何模板之前调用。

/**
 * <p>
 *   Internal method that initializes the Template Engine instance. This method 
 *   is called before the first execution of {@link #process(String, IContext)} 
 *   in order to create all the structures required for a quick execution of 
 *   templates.
 * </p>
 * <p>
 *   THIS METHOD IS INTERNAL AND SHOULD <b>NEVER</b> BE CALLED DIRECTLY.
 * </p>
 * <p>
 *   If a subclass of <tt>TemplateEngine</tt> needs additional steps for
 *   initialization, the {@link #initializeSpecific()} method should
 *   be overridden.
 * </p>
 */
public final synchronized void initialize() {

    if (!isInitialized()) {

        logger.info("[THYMELEAF] INITIALIZING TEMPLATE ENGINE");

        this.configuration.initialize();

        this.templateRepository = new TemplateRepository(this.configuration);

        initializeSpecific();

        this.initialized = true;

        // Log configuration details
        this.configuration.printConfiguration();

        logger.info("[THYMELEAF] TEMPLATE ENGINE INITIALIZED");

    }

}

this.configuration.initialize();中,初始化各种引擎配置。除此之外,该方法在所有TemplateResolver上初始化(调用initialize()),然后将引擎标记为已初始化

一旦TemplateEngine标记为“已初始化”,引擎将不会再次初始化,也不会初始化任何配置(按设计)。所以我想也许我的想法是正确的,为新范围注入的新TemplateResolver永远不会被初始化。或者,更准确地说,它不会被标记为已初始化。

似乎使用所有这些initialized的主要原因之一是在完成配置之前阻止它运行,并防止在运行后更改配置。

通过我发现并使用上述假设,我将TemplateResolver更改为在处理每个模板之前始终检查初始化。这种强力方法似乎有效,似乎不会干扰Thymeleaf作者的意图。 (基于我的猜测,当然。我真的不知道。我希望。)

版本:

  • Thymeleaf 2.1.4.RELEASE
  • Spring 4.1.6.RELEASE
  • Spring Boot 1.2.5.RELEASE

1 个答案:

答案 0 :(得分:0)

通常情况下,TemplateEngine在引擎初始化期间调用initialize()上的TemplateResolver。但是在这种情况下,范围注入的TemplateResolver未被初始化。

相反,我们将创建/注入已经初始化的TemplateResolver。&#34;我们只是将这一步添加到bean创建中:

@Bean(initMethod="initialize")
@Scope(value = "tenant", proxyMode = ScopedProxyMode.INTERFACES)
public ITemplateResolver databaseTemplateResolver() {

    final DatabaseTemplateResolver resolver = 
        new DatabaseTemplateResolver(systemSettingService, emailTemplateService );

    resolver.setTemplateMode("HTML5");
    resolver.setCacheTTLMs((long) (1000*60*5)); // 5 Minutes
    resolver.setOrder(2);

    return resolver;
}

我唯一担心的是在这里手动调用initialize可能会有一些无法预料的副作用。我不太了解Thymeleaf,可以肯定地说。但是,到目前为止,这么好。