Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing application context beans from thymeleaf email template

I have followed this Thymeleaf tutorial "Rich HTML email in Spring with Thymeleaf" to generate an email using a Thymeleaf template. All is fine, but I can't access the ApplicationContext beans that I use elsewhere in the application.

To be more specific, in my email template I'd like to have something like:

<span th:text="${@myBean.doSomething()}">

where "myBean" is a @Component. Is that possible at all? I'm not looking for a workaround like

<span th:text="${myBean.doSomething()}">

where the bean is added as a variable in the template processing context.

The Configuration:

@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
public class MyWebConfig extends WebMvcConfigurerAdapter {
[....]
public ClassLoaderTemplateResolver emailTemplateResolver() {
    ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();
    resolver.setPrefix("emailtemplates/");
    resolver.setSuffix(".html");
    resolver.setCharacterEncoding("UTF-8");
    resolver.setTemplateMode("HTML5");
    return resolver;
}

@Bean
public SpringTemplateEngine emailTemplateEngine() {
    SpringTemplateEngine engine = new SpringTemplateEngine();
    engine.addTemplateResolver(emailTemplateResolver());
    engine.addDialect(new LayoutDialect()); // thymeleaf-layout-dialect
    addSpringSecurityDialect(engine); // thymeleaf-SpringSecurity-dialect
    return engine;
}
[....]
}

The email service:

@Service
public class MyEmailService {
@Resource SpringTemplateEngine emailTemplateEngine;
[....]
public boolean sendHtmlEmail(...) {
    final Context ctx = new Context(locale);
    ctx.setVariable("someVariable", "someValue"); // Don't want to add myBean here
    final String body = this.emailTemplateEngine.process("myTemplate", ctx);
    [....]

The Component:

@Component
public class MyBean {
    public String doSomething() {
        return "Something done!";
    }
}

The template:

<span th:text="${@myBean.doSomething()}">

The error:

EL1057E:(pos 1): No bean resolver registered in the context to resolve access to bean 'myBean'

I'm using thymeleaf-2.1.4 and spring-4.1.6

EDIT:

plese note that I can't use the HttpServletRequest because I send most of the emails in an @Async method where the request can't be reliably used. That's the reason why I use Context and not WebContext (although I didn't know about SpringWebContext - I guess that if somebody made that class for accessing beans via a "beans" variable, then using the @myBean notation must be something impossible).

like image 261
xtian Avatar asked Jul 06 '15 11:07

xtian


People also ask

How do you access objects in Thymeleaf?

In Thymeleaf, these model attributes (or context variables in Thymeleaf jargon) can be accessed with the following syntax: ${attributeName} , where attributeName in our case is messages . This is a Spring EL expression.

Is Thymeleaf deprecated?

This was deprecated back in 2.0 (thymeleaf-layout-dialect/issues/95) in favour of decorate as the naming was misleading, and has now been deleted.

What is #{} in Thymeleaf?

#{} is used for message (i18n) expressions. Used to retrieve locale-specific messages from external sources.


2 Answers

It's confusing because we have three different context here:

  • The Spring ApplicationContext, which is not a WebApplicationContext, if we're sending a mail in a scheduled method.
  • The Thymeleaf org.thymeleaf.context.Context on which you set/add variables.
  • The SpEL evaluation context org.springframework.expression.EvaluationContext, used to evaluate SpEL expressions. For Thymeleaf that's the ThymeleafEvaluationContext.

If you don't have a Spring web context, all you need is to add the ThymeleafEvaluationContext with a reference to the application context as a variable to the Thymeleaf Context:

E.g. (in Kotlin)

@Service
class TemplateService(
        private val templateEngine: TemplateEngine,
        private val applicationContext: ApplicationContext
) {

    fun processTemplate(locale: Locale, template: String, vararg variables: Pair<String, Any?>): String {
        val context = Context(locale)

        // Set the Thymeleaf evaluation context to allow access to Spring beans with @beanName in SpEL expressions
        context.setVariable(ThymeleafEvaluationContext.THYMELEAF_EVALUATION_CONTEXT_CONTEXT_VARIABLE_NAME,
                ThymeleafEvaluationContext(applicationContext, null))

        // Set additional variables
        variables.forEach { (key, value) -> context.setVariable(key, value) }

        return templateEngine.process(template, context)
    }
}
like image 101
Dario Seidl Avatar answered Sep 28 '22 06:09

Dario Seidl


Found, you need to add a bean resolver in your SpringWebContext. Bean Resolver is created by ThymeleafEvaluationContext.

    Map<String, Object> mergedModel = new HashMap<>();
    ConversionService conversionService = (ConversionService) this.request
            .getAttribute(ConversionService.class.getName()); // might be null!
    ThymeleafEvaluationContext evaluationContext = new ThymeleafEvaluationContext(this.ac,
            conversionService);
    mergedModel.put(ThymeleafEvaluationContext.THYMELEAF_EVALUATION_CONTEXT_CONTEXT_VARIABLE_NAME,
            evaluationContext);
    SpringWebContext ctx = new SpringWebContext(this.request, this.response,
            this.request.getServletContext(), this.request.getLocale(), mergedModel, this.ac);
like image 45
Alexandre Petrillo Avatar answered Sep 28 '22 07:09

Alexandre Petrillo