首页 文章

多租户环境中的Spring Boot Thymeleaf数据库TemplateResolver

提问于
浏览
0

我在多租户应用程序中使用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 .

这很有效,所以看起来如此 . 上面指定的范围是为域确定的多租户环境中的一个租户定义的自定义范围 . 但是,我认为这也可能是针对这个问题的 Session 范围 . 我的想法是每个范围的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初始化,因此也是例外 .

任何人都可以给我一个正确的方向吗?谢谢 .

EDIT: 以下是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 上初始化( calls initialize() ),然后将引擎标记为 initialized .

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

似乎使用所有这些标志的主要原因之一是在完成配置之前阻止它运行并防止一旦运行就改变配置 .

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

VERSIONS:

  • Thymeleaf 2.1.4.RELEASE

  • Spring 季4.1.6.RELEASE

  • Spring Boot 1.2.5.RELEASE

1 回答

  • 0

    通常, TemplateEngine 在引擎初始化期间在 TemplateResolver 上调用 initialize() . 但是在这种情况下,没有初始化作用域注入的 TemplateResolver .

    相反,我们将创建/注入 TemplateResolver 已经"initialized."我们只是将这一步添加到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 . 但是,到目前为止,这么好 .

相关问题