Адаптеры в Spring Integration: работа с почтой и базой

Во введении мы уже использовали Gateway, в этой статье будем использовать адаптеры. Мы напишем приложение, которое будет получать данные Animal из почтового ящика через Imap Inbound Adapter и добавлять их в базу данных через Jpa Outbound Adapter.

Еще будет вспомогательный поток для отправки данных в почту (чтобы было, что брать из ящика) с Smtp 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.

Адаптеры в Spring Integration: работа с почтой и базой: 2 комментария

    1. заменить на локальную
      в 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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *