Отправляйте электронные письма с динамическим HTML-контентом, используя шаблонизатор FreeMarker

Вступление

В программировании события - это действия, которые обрабатываются программным обеспечением. События могут запускаться по-разному, например, когда пользователь нажимает кнопку или сама система может вызвать событие. Суть в том, что приложение должно отслеживать такие действия или конкретное событие, а затем реагировать на них и каким-то образом обрабатывать их.

В Spring Framework одним из центральных интерфейсов является ApplicationContext, и он имеет возможность публиковать события для зарегистрированных слушателей. Spring Framework также предоставляет абстракцию для отправки электронных писем, которая отвечает за низкоуровневую обработку ресурсов, а Spring Boot имеет стартовый модуль и автоконфигурацию для него.

В этой статье мы создадим пример, который будет использовать возможность Spring публиковать и прослушивать события, отправлять электронное письмо с динамическим HTML-содержимым с помощью Apache FreeMarker Template Engine каждый раз, когда клиент бронирует столик в ресторане.

Зависимости

Spring Boot предоставляет стартовые модули для Java Mail Sender и FreeMarker, наш первый шаг - добавить их в наш gradle.build или pom.xml файл или использовать Spring Initializer для быстрой генерации проекта.

implementation 'org.springframework.boot:spring-boot-starter-freemarker'
implementation 'org.springframework.boot:spring-boot-starter-mail'

Конфигурация

Поскольку мы используем spring-boot-starter-mail, Spring Boot автоматически настроит JavaMailSender bean и сделает его доступным для внедрения. Мы можем настроить его дальше, используя элементы конфигурации, доступные в пространстве имен spring.mail.

spring.mail.username= #email which will be used to send emails from 
spring.mail.password= #password
spring.mail.port=587
spring.mail.host=smtp.gmail.com
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true

В этом примере проекта я буду использовать Gmail в качестве сервера SMTP (Simple Mail Transfer Protocol), поэтому я настроил его, указав свое имя пользователя / пароль и данные сервера Gmail. При желании вы можете использовать Outlook.

Обратите внимание: если в вашем аккаунте Google включена двухфакторная аутентификация, вы получите ошибку аутентификации. В этом случае вам нужно будет сгенерировать пароль приложения и использовать его вместо обычного пароля в файле application.properties, чтобы ваше приложение получило доступ к вашей учетной записи Gmail.

Весенние события

Цель нашего проекта - отправить электронное письмо с подтверждением и деталями бронирования клиенту, который сделал заказ. Таким образом, при каждом создании резервирования приложение должно будет прослушивать это событие, а затем отвечать на него, отправляя электронное письмо.

Давайте создадим простое ReservationCreated событие. Object source, который затем передается в super(), является фактическим объектом, на котором произошло событие, в основном источником события.

Обратите внимание, что вам не нужно расширять ApplicationEvent, вы также можете публиковать объекты как событие, за исключением того, что, когда публикуемый нами объект не является ApplicationEvent, он будет автоматически заключен в PayloadApplicationEvent к весне.

Теперь нам нужен слушатель, который будет отслеживать появление событий:

Позже мы реализуем некоторую логику в прослушивателе событий onReservationCreated для обработки фактического события.

JavaMailSender и Apache FreeMarker

Spring Boot предоставит нам bean-компонент JavaMailSender и bean-компонент Apache FreeMarker Configuration, который инкапсулирует параметры конфигурации FreeMarker и используется в качестве службы загрузки шаблонов.

Давайте создадим EmailService, где мы загрузим шаблон, подготовим и отправим электронное письмо. FreeMarker имеет processTemplateIntoString метод, который обрабатывает указанный шаблон с данной моделью. Затем мы можем использовать setText(String text, boolean html) для установки обработанного шаблона.

Я использовал beefree.io для загрузки HTML-шаблона электронной почты и поместил его в папку templates в структуре проекта с именем reservation-confirmation.ftl, а изображения, использованные в шаблоне, находятся в папке static/images. В шаблоне мы можем ссылаться на модель, используя ${reservation.numberOfPeople} или ${reservation.name} в соответствующих местах.

Собираем все вместе

Имея EmailService на месте, давайте теперь реализуем onRegistrationCreated прослушиватель для использования EmailService и отправки электронной почты.

Мы извлекаем reservation, исходный объект, на котором произошло событие, создаем модель, на которую будет указана ссылка в шаблоне FreeMarker, упаковываем все это и отправляем электронное письмо.

Обратите внимание, что наш метод прослушивателя событий также имеет аннотацию @Async, поскольку я хочу, чтобы обработка событий происходила в другом потоке. Помните, чтобы использовать @Async, мы должны @EnableAsync в классе @SpringBootApplication.

Осталось только опубликовать мероприятие. Для этого мы вводим ApplicationEventPublisher и публикуем ReservationCreatedEvent на Reservation исходном объекте после его сохранения в базе данных.

Давай проверим.

Если вы получили SunCertPathBuilderException: unable to find valid certification path to requested target, обязательно отключите антивирус во время тестирования приложения. И не используйте случайный адрес электронной почты в запросе, поскольку он фактически отправит электронное письмо с вашего адреса электронной почты, указанного в файле application.properties как адрес fromEmail.

Бонусный совет

Еще одна замечательная вещь, которую предоставляет Spring, - это возможность привязать слушателя к транзакции, используя аннотацию @TransactionalEventListener(phase= TransactionPhase.AFTER_COMMIT) вместо @EventListener. Поскольку в нашем примере событие запускается путем сохранения данных резервирования в базе данных, если эта транзакция по какой-либо причине откатывается или неуспешна, мы не хотим публиковать событие. Подробнее о транзакциях весной читайте в моей предыдущей статье.



В этом сценарии мы можем привязать прослушиватель событий к конкретному TransactionPhase и публиковать событие только при успешной фиксации. Ниже приведены дополнительные константы Enum, которые можно передать @TransactionalEventListener

  • AFTER_COMMIT - запускать событие после успешной фиксации.
  • AFTER_COMPLETION - запускать событие после завершения транзакции
  • AFTER_ROLLBACK - запускать событие, если транзакция откатилась
  • BEFORE_COMMIT - запускать событие перед фиксацией транзакции.

Исходный код этого примера проекта доступен на GitHub.

Подпишитесь на информационный бюллетень, чтобы получать будущие статьи прямо на свой почтовый ящик.

Читать дальше: