Статьи

Spring Integration 4.0: полный пример без XML

1. Введение

Spring Integration 4.0 наконец-то здесь , и этот выпуск поставляется с очень приятными функциями. В этой статье рассматривается возможность настройки потока интеграции без использования XML вообще. Те, кто не любит XML, смогут разработать интеграционное приложение, используя только JavaConfig.

Эта статья состоит из следующих разделов:

  1. Введение.
  2. Обзор потока.
  3. Весенняя комплектация.
  4. Деталь конечных точек.
  5. Тестирование всего потока.
  6. Вывод.
  • Исходный код можно найти на github .
  • Исходный код веб-службы, вызванной в этом примере, можно найти в репозитории spring-samples на github .

2. Обзор потока

В примере приложения показано, как настроить несколько конечных точек обмена сообщениями и интеграции. Пользователь запрашивает курс, указав идентификатор курса. Поток вызовет веб-сервис и вернет ответ пользователю. Кроме того, некоторые типы курсов будут сохранены в базе данных.

Поток выглядит следующим образом:

  • Шлюз интеграции (служба курса) служит входом в систему обмена сообщениями.
  • Преобразователь создает сообщение запроса от указанного пользователем идентификатора курса.
  • Исходящий шлюз веб-службы отправляет запрос веб-службе и ожидает ответа.
  • Активатор службы подписан на канал ответа, чтобы вернуть имя курса пользователю.
  • Фильтр также подписан на канал ответа. Этот фильтр отправляет некоторые типы курсов на адаптер канала mongodb для сохранения ответа в базе данных.

Следующая диаграмма лучше показывает, как структурирован поток:

ИНТ-v4-полный flow_v2

3. Пружинная конфигурация

Как обсуждалось в разделе введения, вся конфигурация определяется с помощью JavaConfig. Эта конфигурация разделена на три файла: инфраструктура, веб-сервис и конфигурация базы данных. Давайте проверим это:

3.1 Конфигурация инфраструктуры

Этот файл конфигурации содержит только определение каналов сообщений. Конечные точки обмена сообщениями (преобразователь, фильтр и т. Д.) Настроены с аннотациями.

InfrastructureConfiguration.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Configuration
@ComponentScan("xpadro.spring.integration.endpoint")    //@Component
@IntegrationComponentScan("xpadro.spring.integration.gateway"//@MessagingGateway
@EnableIntegration
@Import({MongoDBConfiguration.class, WebServiceConfiguration.class})
public class InfrastructureConfiguration {
     
    @Bean
    @Description("Entry to the messaging system through the gateway.")
    public MessageChannel requestChannel() {
        return new DirectChannel();
    }
     
    @Bean
    @Description("Sends request messages to the web service outbound gateway")
    public MessageChannel invocationChannel() {
        return new DirectChannel();
    }
     
    @Bean
    @Description("Sends web service responses to both the client and a database")
    public MessageChannel responseChannel() {
        return new PublishSubscribeChannel();
    }
     
    @Bean
    @Description("Stores non filtered messages to the database")
    public MessageChannel storeChannel() {
        return new DirectChannel();
    }
}

Аннотация @ComponentScan ищет аннотированные классы @Component, которые являются нашими определенными конечными точками обмена сообщениями; фильтр, трансформатор и сервисный активатор.

Аннотация @IntegrationComponentScan выполняет поиск конкретных примечаний по интеграции. В нашем примере он будет сканировать шлюз входа, аннотированный @MessagingGateway.

Аннотация @EnableIntegration позволяет настраивать интеграцию. Например, аннотации уровня метода, такие как @Transformer или @Filter.

3.2 Конфигурация веб-сервиса

Этот файл конфигурации настраивает исходящий шлюз веб-службы и его требуемый маршаллер.

WebServiceConfiguration.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
@Configuration
public class WebServiceConfiguration {
     
    @Bean
    @ServiceActivator(inputChannel = "invocationChannel")
    public MessageHandler wsOutboundGateway() {
        MarshallingWebServiceOutboundGateway gw = new MarshallingWebServiceOutboundGateway("http://localhost:8080/spring-ws-courses/courses", jaxb2Marshaller());
        gw.setOutputChannelName("responseChannel");
         
        return gw;
    }
     
    @Bean
    public Jaxb2Marshaller jaxb2Marshaller() {
        Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
        marshaller.setContextPath("xpadro.spring.integration.ws.types");
         
        return marshaller;
    }
}

Шлюз позволяет нам определять его выходной канал, но не входной канал. Нам нужно аннотировать адаптер с помощью @ServiceActivator, чтобы подписать его на канал вызова и избежать необходимости автоматического подключения его в определении компонента канала сообщений.

3.3 Конфигурация базы данных

Этот файл конфигурации определяет все необходимые bean-компоненты для настройки mongoDB . Он также определяет адаптер исходящего канала mongoDB.

MongoDBConfiguration.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
@Configuration
public class MongoDBConfiguration {
     
    @Bean
    public MongoDbFactory mongoDbFactory() throws Exception {
        return new SimpleMongoDbFactory(new MongoClient(), "si4Db");
    }
     
    @Bean
    @ServiceActivator(inputChannel = "storeChannel")
    public MessageHandler mongodbAdapter() throws Exception {
        MongoDbStoringMessageHandler adapter = new MongoDbStoringMessageHandler(mongoDbFactory());
        adapter.setCollectionNameExpression(new LiteralExpression("courses"));
         
        return adapter;
    }
}

Как и шлюз веб-службы, мы не можем установить входной канал для адаптера. Я также сделал это, указав входной канал в аннотации @ServiceActivator.

4. Детализация конечных точек

Первой конечной точкой потока является шлюз интеграции, который поместит аргумент (courseId) в полезную нагрузку сообщения и отправит его в канал запроса.

1
2
3
4
5
@MessagingGateway(name = "entryGateway", defaultRequestChannel = "requestChannel")
public interface CourseService {
     
    public String findCourse(String courseId);
}

Сообщение, содержащее идентификатор курса, достигнет преобразователя. Эта конечная точка создаст объект запроса, который ожидает веб-служба:

01
02
03
04
05
06
07
08
09
10
11
12
13
@Component
public class CourseRequestBuilder {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
     
    @Transformer(inputChannel="requestChannel", outputChannel="invocationChannel")
    public GetCourseRequest buildRequest(Message<String> msg) {
        logger.info("Building request for course [{}]", msg.getPayload());
        GetCourseRequest request = new GetCourseRequest();
        request.setCourseId(msg.getPayload());
         
        return request;
    }
}

Подписанный на канал ответа, который является каналом, на который будет отправляться ответ веб-службы, существует активатор службы, который получит ответное сообщение и доставит имя курса клиенту:

01
02
03
04
05
06
07
08
09
10
11
12
@Component
public class CourseResponseHandler {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
     
    @ServiceActivator(inputChannel="responseChannel")
    public String getResponse(Message<GetCourseResponse> msg) {
        GetCourseResponse course = msg.getPayload();
        logger.info("Course with ID [{}] received: {}", course.getCourseId(), course.getName());
         
        return course.getName();
    }
}

Также подписанный на канал ответа, фильтр будет определять на основе его типа, если курс должен быть сохранен в базе данных:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
@Component
public class StoredCoursesFilter {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
     
    @Filter(inputChannel="responseChannel", outputChannel="storeChannel")
    public boolean filterCourse(Message<GetCourseResponse> msg) {
        if (!msg.getPayload().getCourseId().startsWith("BC-")) {
            logger.info("Course [{}] filtered. Not a BF course", msg.getPayload().getCourseId());
            return false;
        }
         
        logger.info("Course [{}] validated. Storing to database", msg.getPayload().getCourseId());
        return true;
    }
}

5. Тестирование всего потока

Следующий клиент отправит два запроса; запрос курса типа BC, который будет сохранен в базе данных, и курс типа DF, который будет окончательно отфильтрован:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={InfrastructureConfiguration.class})
public class TestApp {
    @Autowired
    CourseService service;
     
    @Test
    public void testFlow() {
        String courseName = service.findCourse("BC-45");
        assertNotNull(courseName);
        assertEquals("Introduction to Java", courseName);
         
        courseName = service.findCourse("DF-21");
        assertNotNull(courseName);
        assertEquals("Functional Programming Principles in Scala", courseName);
    }
}

Это приведет к следующему выводу консоли:

01
02
03
04
05
06
07
08
09
10
11
CourseRequestBuilder|Building request for course [BC-45]
 
CourseResponseHandler|Course with ID [BC-45] received: Introduction to Java
 
StoredCoursesFilter|Course [BC-45] validated. Storing to database
 
CourseRequestBuilder|Building request for course [DF-21]
 
CourseResponseHandler|Course with ID [DF-21] received: Functional Programming Principles in Scala
 
StoredCoursesFilter|Course [DF-21] filtered. Not a BF course

6. Заключение

Мы узнали, как настроить и протестировать приложение на основе Spring Integration без использования XML-конфигурации. Оставайтесь с нами, потому что Spring Integration Java DSL с расширениями Spring Integration уже в пути!