简体   繁体   English

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

[英]Spring Boot + Thymeleaf + Database TemplateResolver in a Multi-Tenant environment

I'm using Spring Boot and Thymeleaf in a multi-tenant application. 我在多租户应用程序中使用Spring Boot和Thymeleaf。 I am also using Thymeleaf to process email templates and generate html email. 我还使用Thymeleaf处理电子邮件模板并生成html电子邮件。 In order to do this, I created a ITemplateResolver that retrieves thymeleaf templates prefixed with "db:" from a database. 为此,我创建了一个ITemplateResolver ,该ITemplateResolver从数据库中检索以“ db:”为前缀的百里香模板。

Spring Boot auto-configuration picks up any template-resolvers and adds them the the SpringTemplateEngine . Spring Boot自动配置会拾取所有模板解析器,并将其添加到SpringTemplateEngine So I have a my template resolver set up like this: 所以我有一个这样的模板解析器:

@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;
}

As expected, the resolver gets added to the TemplateEngine and any templates with names that start with "db:" are read from the database. 正如预期的那样,将解析程序添加到TemplateEngine并且将从数据库中读取任何名称以“ db:”开头的模板。 This allows us to store specialized email templates that are processed by the Thymeleaf engine to produce the resulting html. 这使我们可以存储由Thymeleaf引擎处理以生成结果html的专用电子邮件模板。

This worked very well, so it seemed. 效果很好,看来。 The scope specified above is a custom scope defined for one tenant in a multi-tenant environment determined by the domain. 上面指定的范围是在域确定的多租户环境中为一个租户定义的自定义范围。 But this might as well be session scoped, I believe, for the purpose of this question. 但我认为,出于这个问题的目的,这也可能是会议范围的问题。 My thought here is that TemplateResolver is different for each scope. 我的想法是,每个范围的TemplateResolver都不相同。 We need it to be because we are reading from the tenant's database for the template. 我们之所以需要这样做是因为我们正在从租户的数据库中读取模板。

Finally, my symptom: It seems that the first tenant works fine. 最后,我的症状是: 第一个租户工作正常。 For any subsequent tenants, I get an exception when attempting to process a database template. 对于任何后续租户,尝试处理数据库模板时都会遇到异常。

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)
    ...

I've tried disabling Spring Boot auto-configuration for Thymeleaf and manually setting up the TemplateEngine, ViewResolver, TemplateResolvers, etc. but have the same problem. 我试过为Thymeleaf禁用Spring Boot自动配置并手动设置TemplateEngine,ViewResolver,TemplateResolvers等,但是有相同的问题。 I also tried making everything tenant scope but ran into an entirely different mess and back-tracked. 我还尝试过使所有tenant都使用,但遇到了完全不同的混乱并回溯。

I have a feeling that I'm doing something wrong, or have the wrong idea, of how dependency injection should work in this situation. 对于这种情况下依赖注入的工作方式,我感觉自己做错了或有错误的想法。 Or the Thymeleaf engine is implemented in such a way that it is not compatible with the proxy object. 或者Thymeleaf引擎以与代理对象不兼容的方式实现。 I'm leaning towards the latter. 我倾向于后者。 Perhaps I need to somehow extend the template engine so that it initializes the resolvers once for each tenant? 也许我需要以某种方式扩展模板引擎,以便它为每个租户初始化一次解析器? I believe, maybe, Thymeleaf thinks the resolver is already initialized, then when spring injects a new resolver, it is never initialized by Thymeleaf and hence the exception. 我相信,也许Thymeleaf认为解析器已经初始化,然后当spring注入新的解析器时,Thymeleaf从未对其进行初始化,因此是异常。

Can anyone give me a push in the right direction? 谁能给我正确的方向推动? Thank you. 谢谢。

EDIT: Here is the code for Thymeaf's TemplateEngine method initialize() which is called before any templates are processed. 编辑:这是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");

    }

}

In this.configuration.initialize(); this.configuration.initialize(); , various engine configuration is initialized. ,各种引擎配置被初始化。 Among other things, the method initializes ( calls initialize() ) on all TemplateResolver s and then marks the engine as initialized . 除其他外,该方法在所有TemplateResolver上初始化( 调用initialize() ),然后将引擎标记为initialized

Once the TemplateEngine is marked "initialized," the engine will not be initialized again, nor will any configuration be initialized (by design). 一旦将TemplateEngine标记为“已初始化”,就不会再次初始化引擎,也不会初始化任何配置(通过设计)。 So I am thinking that maybe my thought is correct that a new TemplateResolver injected for a new scope will never be initialized. 因此,我认为也许我的想法是正确的,因为永远不会初始化为新作用域注入的新TemplateResolver Or, more accurately, it will not be marked as initialized. 或者,更准确地说,它不会被标记为已初始化。

It seems that one of the major reasons for the use of all these initialized flags it to keep from running before complete configuration and to prevent changes to configuration once running. 似乎使用所有这些initialized标志的主要原因之一是,它会在完整配置之前停止运行,并防止运行后对配置进行更改。

With what I found, and using the above assumptions, I changed my TemplateResolver to always check for initialization before every template is processed. 根据我的发现,并使用上述假设,我将TemplateResolver更改为始终在处理每个模板之前检查初始化。 This brute force method appears to work and does not seem to interfere with the intent of the Thymeleaf authors. 这种蛮力方法似乎行之有效,似乎并没有影响Thymeleaf作者的意图。 (Based on my guess, of course. I really do not know. I am hoping.) (基于我的猜测,当然。我真的不知道。我希望如此。)

VERSIONS: 版本:

  • Thymeleaf 2.1.4.RELEASE 胸腺2.1.4。发布
  • Spring 4.1.6.RELEASE 春天4.1.6.RELEASE
  • Spring Boot 1.2.5.RELEASE Spring Boot 1.2.5。发布

Normally, the TemplateEngine calls initialize() on the TemplateResolver s during Engine initialization. 通常,在引擎初始化期间, TemplateResolverTemplateEngine调用initialize() However in this case, the scoped injected TemplateResolver s are not being initialized. 但是,在这种情况下,作用域注入的TemplateResolver不会被初始化。

Instead, we'll create/inject a TemplateResolver that is already "initialized." 相反,我们将创建/注入一个已经“初始化”的TemplateResolver We just add that step to the bean creation: 我们只是将这一步添加到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;
}

My only concern is that there might be some unforeseen side-effect to manually calling initialize here. 我唯一关心的是,在此处手动调用initialize可能会有一些无法预料的副作用。 I don't know Thymeleaf well enough to say for certain. 我对Thymeleaf的了解不足以肯定地说。 However, so far, so good. 但是,到目前为止,一切都很好。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM