Search code examples
javaspring-bootemailspring-thymeleaf

Why is my Spring @Bean not used by my Service?


I'm trying to send an email with a given template but, when I call "templateEngine.process" it doesn't give any exception but it doesn't process the template, only returns a string with the template's name

My POM dependencies:

<dependencies>
    <!-- Spring Boot -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-mail</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-mail</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>

    <!-- Spring Doc -->
    <dependency>
        <groupId>org.springdoc</groupId>
        <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
        <version>2.1.0</version>
    </dependency>

    <!-- Eureka -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>

    <!-- LOMBOK -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.26</version>
    </dependency>
</dependencies>

My Config:

@Configuration
public class EmailConfiguration {

    @Bean
    public SpringTemplateEngine springTemplateEngine() {
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.addTemplateResolver(htmlTemplateResolver());
        return templateEngine;
    }

    @Bean
    public ClassLoaderTemplateResolver htmlTemplateResolver(){
        ClassLoaderTemplateResolver emailTemplateResolver = new ClassLoaderTemplateResolver();
        emailTemplateResolver.setPrefix("classpath:/templates/");
        emailTemplateResolver.setSuffix(".html");
        emailTemplateResolver.setTemplateMode(TemplateMode.HTML);
        emailTemplateResolver.setCharacterEncoding(StandardCharsets.UTF_8.name());
        return emailTemplateResolver;
    }
}

My Controller:

    @PostMapping("/send")
    public boolean sendEmail(@RequestParam("lang") String language, @RequestBody EmailRequestDTO payload) throws NotifierEmailException {
        emailService.sendSimpleMailMessage(payload, language);
        return true;
    }

My Service:

@Service
public class EmailService {

    private static final String ENCODING = "UTF-8";

    private final JavaMailSender emailSender;

    public final TemplateEngine template = new SpringTemplateEngine();

    @Autowired
    public EmailService(JavaMailSender emailSender) {
        this.emailSender = emailSender;
    }

    public void sendSimpleMailMessage(EmailRequestDTO emailRequestDTO, String language) throws NotifierEmailException {
        try {
            var locale = Locale.forLanguageTag(language);
            // Prepare the evaluation context
            final Context thymeleafContext = new Context(locale);
            emailRequestDTO.getAttributes().forEach(thymeleafContext::setVariable);

            // Prepare message using a Spring helper
            final MimeMessage mimeMessage = emailSender.createMimeMessage();
            final MimeMessageHelper message = new MimeMessageHelper(mimeMessage, ENCODING);
            message.setSubject(emailRequestDTO.getSubject());
            message.setFrom(emailRequestDTO.getEmailFrom());
            message.setTo(emailRequestDTO.getEmailTo());

            // Create the HTML body using Thymeleaf
            /* This line here returns a string with the template name that I passed! */
            final String htmlContent = template.process("activationEmail.html", thymeleafContext);
            message.setText(htmlContent, true);

            // Send mail
            emailSender.send(mimeMessage);
        } catch (Exception exception) {
            throw new NotifierEmailException(HttpStatus.INTERNAL_SERVER_ERROR, exception.getMessage());
        }
    }
}

Didn't include the application.properties cause I'm not using it for thymeleaf.

My template location enter image description here

The log when I executed (I'm hiding some data here):

DATA
354  Go ahead z15-20020aa785cf000000b006daa809584csm3243217pfn.182 - gsmtp
Date: Sat, 27 Jan 2024 19:50:01 -0300 (BRT)
From: [HIDDEN]
To: [HIDDEN]
Message-ID: <851717819.0.1706395805863@DESKTOP-MVK6CCP>
Subject: Activation
MIME-Version: 1.0
Content-Type: text/html;charset=UTF-8
Content-Transfer-Encoding: 7bit

activationEmail.html
.
250 2.0.0 OK  1706395825 z15-20020aa785cf000000b006daa809584csm3243217pfn.182 - gsmtp
DEBUG SMTP: message successfully delivered to mail server
QUIT

Email received: enter image description here


Solution

  • While you are creating a Bean of type SpringTemplateEngine in your configuration class, your @Service doesn't use this bean:

    Service
    public class EmailService {
    
        private static final String ENCODING = "UTF-8";
    
        private final JavaMailSender emailSender;
    
        public final TemplateEngine template = new SpringTemplateEngine();
    
        @Autowired
        public EmailService(JavaMailSender emailSender) {
            this.emailSender = emailSender;
        }
    

    Your template field is initialised with a newly created SpringTemplateEngine instance, and your constructor only takes a JavaMailSender instance.

    You need to add a TemplateEngine parameter to your constructor and remove the initialisation of the template field.

    So your EamilService looks like:

    ...
        private final TemplateEngine template;
    
        @Autowired
        public EmailService(JavaMailSender emailSender, TemplateEngine templateEngine) {
            this.emailSender = emailSender;
            this.templateEngine = templateEngine;
        }
    ...
    

    As the Javadoc for TemplateEngine says, a TemplateEngine with no ItemplateResolver configured will use a StringTemplateResolver which explains the behaviour you saw.