Spring Integration 4.0: 一个完全无需 XML 的示例

Spring Integration 4.0: 一个完全无需 XML 的示例

英文原文:Spring Integration 4.0: A complete XML-free example

返回原文

1   Introduction

Spring Integration 4.0 is finally here, and this release comes with very nice features. The one covered in this article is the possibility to configure an integration flow without using XML at all. Those people that don’t like XML will be able to develop an integration application with just using JavaConfig.

This article is divided in the following sections:

  1. Introduction.

  2. An overview of the flow.

  3. Spring configuration.

  4. Detail of the endpoints.

  5. Testing the entire flow.

  6. Conclusion.


The source code can be found at github.

The source code of the web service invoked in this example can be found at the spring-samples repository at github.

译者信息

1   介绍

Spring Integration 4.0 终于 到来了, 而这一次发布带来了非常棒的特性. 本文中讲到的这个特性是完全不使用XML来配置一个集成流的可能性. 那些不喜欢XML的人们将可以只使用JavaConfig开发一个集成应用程序.

本文分成如下几个部分:

  1. 介绍.

  2. 流的概述.

  3. Spring 配置.

  4. 端点的详细信息.

  5. 测试整个流.

  6. 总结.


可以在 github 上找到本文相关的源代码.

在这个示例中被调用的web服务的源代码可以在 github 上的 srping-samples资源库中找到.

2   An overview of the flow

The example application shows how to configure several messaging and integration endpoints. The user asks for a course by specifying the course Id. The flow will invoke a web service and return the response to the user. Additionally, some type of courses will be stored to a database. 

The flow is as follows:

  • An integration gateway (course service) serves as the entry to the messaging system.

  • A transformer builds the request message from the user specified course Id.

  • A web service outbound gateway sends the request to a web service and waits for a response.

  • A service activator is subscribed to the response channel in order to return the course name to the user.

  • A filter is also subscribed to the response channel. This filter will send some types of courses to a mongodb channel adapter in order to store the response to a database.


The following diagram better shows how the flow is structured:
Spring Integration 4.0: 一个完全无需 XML 的示例_第1张图片

译者信息

2   流的概述

这个示例应用程序展示了如何配置一些消息和集成端点. 用户通过一个course的Id请求一个 course . 流将会调用一个web服务并向用户做出回应. 此外,一些类型的course将会被存储到一个数据库中. 

这个流如下所示:

  • 一个集成的 网关 (course 服务) 为消息系统提供词条的服务.

  • 一个 转换器 构建来自用户指定的course Id的请求消息.

  • 一个web服务的 出站网关 将请求发送到一个web服务并等待其回应.

  • 一个 服务执行器 被注册到回应通道,以向用户返回course的名称.

  • 一个 过滤器 也会被注册到回应通道. 这个过滤器会发送一些类型的course到一个mongodb 通道适配器 以将回应存储到一个数据库.


下图较好的展示了这个流是如何构造的:
Spring Integration 4.0: 一个完全无需 XML 的示例_第2张图片

3   Spring configuration

As discussed in the introduction section, the entire configuration is defined with JavaConfig. This configuration is split into three files: infrastructure, web service and database configuration. Let’s check it out:

3.1   Infrastructure configuration

This configuration file only contains the definition of message channels. The messaging endpoints (transformer, filter, etc...) are configured with annotations.

InfrastructureConfiguration.java

@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(@Qualifier("wsOutboundGateway") MessageHandler wsOutboundGateway) {
        DirectChannel channel = new DirectChannel();
        channel.subscribe(wsOutboundGateway);
        
        return channel;
    }
    
    @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(@Qualifier("mongodbAdapter") MessageHandler mongoOutboundAdapter) {
        DirectChannel channel = new DirectChannel();
        channel.subscribe(mongoOutboundAdapter);
        
        return channel;
    }
}

The @ComponentScan annotation searches for @Component annotated classes, which are our defined messaging endpoints; the filter, the transformer and the service activator.

The @IntegrationComponentScan annotation searches for specific integration annotations. In our example, it will scan the entry gateway which is annotated with @MessagingGateway.

The @EnableIntegration annotation enables integration configuration. For example, method level annotations like @Transformer or @Filter.

译者信息

3   Spring 配置

如在介绍一节所讨论的,整个配置都是用JavaConfig来配置. 这一配置被分成了三个文件:基础设施,web服务以及数据库配置. 让我们来一瞧究竟:

3.1   基础设施配置

这个配置文件值包含了消息通道的定义. 消息端点(转换器,过滤器等等..)是用了注解来配置.

InfrastructureConfiguration.java

@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(@Qualifier("wsOutboundGateway") MessageHandler wsOutboundGateway) {
        DirectChannel channel = new DirectChannel();
        channel.subscribe(wsOutboundGateway);
        
        return channel;
    }
    
    @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(@Qualifier("mongodbAdapter") MessageHandler mongoOutboundAdapter) {
        DirectChannel channel = new DirectChannel();
        channel.subscribe(mongoOutboundAdapter);
        
        return channel;
    }
}

@ComponentScan 注解会搜索使用了 @Component 注解的类, 这种类是我们定义的消息端点; 过滤器,转换器以及服务执行器.

@IntegrationComponentScan 注解搜索指定的集成注解. 在我们的示例中,它将会扫描到使用@MessagingGateway注解的词条网关.

@EnableIntegration 注解启动了集成配置. 例如,像@Transformer 或者 @Filter这种方法级别的注解.

3.2   Web service configuration

This configuration file configures the web service outbound gateway and its required marshaller.

WebServiceConfiguration.java

@Configuration
public class WebServiceConfiguration {
    
    @Bean
    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;
    }
}

The gateway allows us to define its output channel but not the input channel. I have done that by subscribing the gateway to the channel (see invocationChannel definition in the previous section).

3.3   Database configuration

This configuration file defines all necessary beans to set up mongoDB. It also defines the mongoDB outbound channel adapter.

MongoDBConfiguration.java

@Configuration
public class MongoDBConfiguration {
    
    @Bean
    public MongoDbFactory mongoDbFactory() throws Exception {
        return new SimpleMongoDbFactory(new MongoClient(), "si4Db");
    }
    
    @Bean
    public MessageHandler mongodbAdapter() throws Exception {
        MongoDbStoringMessageHandler adapter = new MongoDbStoringMessageHandler(mongoDbFactory());
        adapter.setCollectionNameExpression(new LiteralExpression("courses"));
        
        return adapter;
    }
}

Like the web service gateway, we can’t set the input channel to the adapter. I also have done that by subscribing it to the storeChannel.

译者信息

3.2   Web 服务配置

这一配置文件配置的web服务的出站请求网关及其所需要的编组.

WebServiceConfiguration.java

@Configuration
public class WebServiceConfiguration {
    
    @Bean
    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;
    }
}

网关可以让我们定义它的输出通道,而不是它的输入通道. 我可以将网关注册到通道来做到(见前面小节的invocationChannel 定义).

3.3   数据库配置

这一配置文件定义了所有用来设置 mongoDB 所必需的bean. 它也定义了 mongoDB 出站通道适配器.

MongoDBConfiguration.java

@Configuration
public class MongoDBConfiguration {
    
    @Bean
    public MongoDbFactory mongoDbFactory() throws Exception {
        return new SimpleMongoDbFactory(new MongoClient(), "si4Db");
    }
    
    @Bean
    public MessageHandler mongodbAdapter() throws Exception {
        MongoDbStoringMessageHandler adapter = new MongoDbStoringMessageHandler(mongoDbFactory());
        adapter.setCollectionNameExpression(new LiteralExpression("courses"));
        
        return adapter;
    }
}

像是web服务的网关,我们不能为适配器设置输出通道. 同样也可以通过将其注册到存储通道来做到.

4   Detail of the endpoints

The first endpoint of the flow is the integration gateway, which will put the argument (courseId) into the payload of a message and send it to the request channel.

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

The message containing the course id will reach the transformer. This endpoint will build the request object that the web service is expecting

@Component
public class CourseRequestBuilder {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    
    @Transformer(inputChannel="requestChannel", outputChannel="invocationChannel")
    public GetCourseRequest buildRequest(Message msg) {
        logger.info("Building request for course [{}]", msg.getPayload());
        GetCourseRequest request = new GetCourseRequest();
        request.setCourseId(msg.getPayload());
        
        return request;
    }
}

Subscribed to the response channel, which is the channel where the web service reply will be sent, there’s a service activator that will receive the response message and deliver the course name to the client:

@Component
public class CourseResponseHandler {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    
    @ServiceActivator(inputChannel="responseChannel")
    public String getResponse(Message msg) {
        GetCourseResponse course = msg.getPayload();
        logger.info("Course with ID [{}] received: {}", course.getCourseId(), course.getName());
        
        return course.getName();
    }
}

Also subscribed to the response channel, a filter will decide based on its type, if the course is required to be stored to a database:

@Component
public class StoredCoursesFilter {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    
    @Filter(inputChannel="responseChannel", outputChannel="storeChannel")
    public boolean filterCourse(Message 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;
    }
}

译者信息

4   端点的详细信息

流的第一个端点是集成网关,它将会把参数(courseId)放到一个消息的载荷中,并将其发送到请求通道.

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

包含courseId的消息将会抵达转换器. 这个端点将构建web服务所需要的请求对象

@Component
public class CourseRequestBuilder {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    
    @Transformer(inputChannel="requestChannel", outputChannel="invocationChannel")
    public GetCourseRequest buildRequest(Message msg) {
        logger.info("Building request for course [{}]", msg.getPayload());
        GetCourseRequest request = new GetCourseRequest();
        request.setCourseId(msg.getPayload());
        
        return request;
    }
}

注册到web服务回应将会被发送出去的回应通道,将会有一个服务执行器接受回应消息并向客户机传送course名称:

@Component
public class CourseResponseHandler {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    
    @ServiceActivator(inputChannel="responseChannel")
    public String getResponse(Message msg) {
        GetCourseResponse course = msg.getPayload();
        logger.info("Course with ID [{}] received: {}", course.getCourseId(), course.getName());
        
        return course.getName();
    }
}

同样如果course需要被存储到一个数据库,一个过滤器就会注册到回应通道, 基于它的类型来做出决定,:

@Component
public class StoredCoursesFilter {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    
    @Filter(inputChannel="responseChannel", outputChannel="storeChannel")
    public boolean filterCourse(Message 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   Testing the entire flow

The following client will send two requests; a BC type course request that will be stored to the database and a DF type course that will be finally filtered:

@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);
	}
}

This will result in the following console output:

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   Conclusion

We have learnt how to set up and test an application powered with Spring Integration using no XML configuration. Stay tuned, because Spring Integration Java DSL with Spring Integration extensions is on its way!

I'm publishing my new posts on Google plus and Twitter. Follow me if you want to be updated with new content.

译者信息

5   测试整个流

下面的客户机将会受到两个请求:一个BC类型的course请求将会被存储到数据库,还有一个DF类型的course将会最终被过滤掉:

@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);
	}
}

这将会导致如下的控制台输出:

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 扩展 的 Spring Integration Java DSL正在发展中!

你可能感兴趣的:(Spring Integration 4.0: 一个完全无需 XML 的示例)