Во введении мы уже использовали Gateway, в этой статье будем использовать адаптеры. Мы напишем приложение, которое будет получать данные Animal из почтового ящика через Imap Inbound Adapter и добавлять их в базу данных через Jpa Outbound Adapter.
Подготовка
Данные
Класс Animal выглядит так:
@Entity public class Animal { @Id @GeneratedValue private long id; private String name; //constructors/getters/setters }
Мы сделали его JPA-сущностью с помощью аннотации @Entity, так как наш пример демонстрирует именно Jpa Outbound Adapter (а есть еще Jdbc Outbound Adapter).
Каждое письмо будет содержать в теле ровно одно Animal, у которого имя сгенерировано случайно с помощью Math.random() с префиксом cat:
cat0.8561380482057016
Заголовок и отправитель письма для работы программы не важен, так что можно накидать письма вручную. Но в моем приложении есть вспомогательный поток с автогенерицией писем.
Maven-зависимости
Помимо spring-integration, не забудьте включить дополнительные библиотеки для работы с почтой и JPA:
<!--для Jpa Outbound Adapter--> <dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-jpa</artifactId> </dependency> <!--для Imap Inbound Adapter и Smtp Outbound Adapter--> <dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-mail</artifactId> </dependency> <!--используется Imap Inbound Adapter и Smtp Outbound Adapter для работы с почтой--> <dependency> <groupId>com.sun.mail</groupId> <artifactId>javax.mail</artifactId> <version>1.6.2</version> </dependency> <!--аналогично основная зависимость для работы с базой через JPA--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- в приложении используется реальная база postgresql--> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.2.8</version> </dependency>
Настройки почтового сервера
В приложении используется почтовый сервер mail.ru. Чтобы не устанавливать сертификат, в настройках при отправке письма (во вспомогательном потоке) будем задавать свойства:
p.put("mail.smtp.ssl.trust", "*"); p.put("mail.smtp.starttls.enable", "true");
а при получении:
p.put("mail.imaps.ssl.trust", "*");
Лучше завести новый пустой ящик, поскольку в нашем потоке нет фильтрации писем, и при получении посторонних писем возникнет исключение.
Основной поток
Создадим стандартный поток, который начинается с Inbound Adapter, а заканчивается Outbound Adapter. Конкретно наш будет выглядеть так:
Imap Inbound Adapter — Service Activator — Service Activator — Filter — Jpa Outbound Adapter
@Bean public IntegrationFlow mailListener() { /* внимание, надо закодировать имя и пароль с URLEncoder.encode(), чтоб не было лишних @ поэтому email@mail.ru превращается в email%40mail.ru*/ return IntegrationFlows.from(Mail.imapInboundAdapter( "imaps://" + URLEncoder.encode(this.email, Charset.defaultCharset()) + ":" + URLEncoder.encode(this.password, Charset.defaultCharset()) + "@imap.mail.ru:993/inbox" ).javaMailProperties(p -> { p.put("mail.debug", "false"); p.put("mail.imaps.ssl.trust", "*"); }), e -> e.poller(Pollers.fixedDelay(1000).maxMessagesPerPoll(1))) .<javax.mail.Message>handle((payload, headers) -> (payload)) .handle(converter, "animalFromEmail") .<Animal>filter(animal -> !animal.getName().startsWith("cat0.1")) .handle(Jpa.outboundAdapter(this.entityManagerFactory) .entityClass(Animal.class) .persistMode(PersistMode.PERSIST), e -> e.transactional(true)).get(); }
После входного адаптера мы берем из сообщения типа org.springframework.messaging.Message полезную нагрузку payload — получается письмо типа javax.mail.Message. Делает это первый Service Activator в методе handle().
Второй Service Activator (второй метод handle()) получает из письма Animal путем вызова метода конвертера:
@Service public class AnimalFromToEmailConverter { @Value("${mail.email}") private String email; // .... public Animal animalFromEmail(Message message){ String animalName=""; try { animalName = message.getContent().toString(); } catch (IOException | MessagingException e) { e.printStackTrace(); } return new Animal(animalName); } }
Далее идет Filter — просто для примера. Мы фильтруем сообщения и пропускаем только те, которые не начинается с «cat0.1»
Далее Animal идет в выходной адаптер в базу данных.
Рассмотрим подробнее адаптеры.
Imap Inbound Adapter
Вообще в SI есть два вида Inbound Channel Adapter:
- MessageProducers
- MessageSources
Первый тип (MessageProducer) сам генерирует сообщения, Poller (тот, кто регулярно проверяет) не требуется. К первому типу относятся относится JMS message-driven adapter, TCP inbound channel adapter, IMAP Idle adapter.
У нас Imap adapter, но не Idle. То есть у нас второй тип MessageSource. Нам Poller требуется, мы сами проверяем сообщения. Сообщения вытягиваются из почты раз в секунду по одному письму:
Pollers.fixedDelay(1000).maxMessagesPerPoll(1)
Jpa Outbound Adapter
В базу мы отправляем сущность Animal по одной штуке.
На этом круг закончен. Сообщения попадают из почты во входной адаптер, проходят череду конечных точек и идут через выходной адаптер в базу.
Вспомогательный поток с Smtp Outbound Adapter
Но, как уже говорилось, для заполнения почты сделан вспомогательный поток, отправляющий письма в наш ящик:
//вспомогательный поток - сгенерируем данные, отправим Animals в почтовый ящик. //но можно вручную отправить письма с текстом вида cat0.9542215123310624 @Bean public IntegrationFlow sendMailFlow() { return IntegrationFlows.from("sendMailChannel") .handle(Mail.outboundAdapter("smtp.mail.ru") .port(25) .credentials(this.email, this.password) .javaMailProperties(p -> { p.put("mail.debug", "true"); p.put("mail.smtp.ssl.trust", "*"); p.put("mail.smtp.starttls.enable", "true"); }), e -> e.id("sendMailEndpoint")) .get(); }
sendMailChannel — Smtp Outbound Adapter
Чтобы поток начал отправлять письма, надо отправить сообщения в канал sendMailChannel, именно из него поток выше берет сообщения.
Для отправки сообщений в канал сделан специальный сервис:
@Service public class EmailEmitterService { private final MessageChannel sendMailChannel; private final AnimalFromToEmailConverter converter; public EmailEmitterService(@Qualifier("sendMailChannel") MessageChannel sendMailChannel, AnimalFromToEmailConverter converter){ this.sendMailChannel=sendMailChannel; this.converter=converter; } public void sendEmails(int emailCount) { for (int i = 0; i < emailCount; i++) { Message message = MessageBuilder.withPayload(converter.createRandomAnimalEmail()).build(); sendMailChannel.send(message); } } }
Сервис использует тот самый конвертер выше, только другой его метод — который генерирует почтовые сообщения типа SimpleMailMessage:
@Service public class AnimalFromToEmailConverter { @Value("${mail.email}") private String email; public SimpleMailMessage createRandomAnimalEmail() { SimpleMailMessage mailMessage = new SimpleMailMessage(); mailMessage.setSubject("New animal"); //генеририруем случайные строки вида cat0.9542215123310624, cat0.2964173089983424 mailMessage.setText("cat" + Math.random()); //сами себе высылаем, чтобы не заводить два ящика mailMessage.setTo(this.email); mailMessage.setFrom(this.email); return mailMessage; } //... }
И вызывается сервис из метода main() нашего приложения:
public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(SpringIntegrationApplication.class, args); EmailEmitterService emitterService = (EmailEmitterService) context.getBean(EmailEmitterService.class); emitterService.sendEmails(2); }
Тут отправляется всего два письма, поскольку mail.ru довольно быстро начинает выдавать ошибку — превышение суточного лимита отправленных писем, и надо быть осторожнее.
Итоги
Исходный код примера доступен на GitHub.
а базейку для этого примера где можно подтянуть?
заменить на локальную
в applocation.yml
spring:
h2:
console:
enabled: true
datasource:
url: jdbc:h2:file:~/test
username: sa
password:
driverClassName: org.h2.Driver
и в pom.xml
com.h2database
h2
1.4.200