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).
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.
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.
#{} is used for message (i18n) expressions. Used to retrieve locale-specific messages from external sources.
It's confusing because we have three different context here:
ApplicationContext
, which is not a WebApplicationContext
, if we're sending a mail in a scheduled method.org.thymeleaf.context.Context
on which you set/add variables.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)
}
}
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);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With