# 馈源适配器

## 提要适配器

Spring 集成通过提要适配器为聚合提供支持。该实现基于[罗马框架](https://rometools.github.io/rome/)。

你需要在项目中包含此依赖项：

Maven

```
<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-feed</artifactId>
    <version>5.5.9</version>
</dependency>
```

Gradle

```
compile "org.springframework.integration:spring-integration-feed:5.5.9"
```

Web 辛迪加是一种发布材料的方式，例如新闻报道，新闻稿，博客文章和其他项目，通常可以在网站上获得，但也可以在提要格式（例如 RSS 或 Atom）中获得。

Spring 集成通过其“提要”适配器为 Web 聚合提供了支持，并为其提供了方便的基于名称空间的配置。要配置“Feed”名称空间，在 XML 配置文件的头中包含以下元素：

```
xmlns:int-feed="http://www.springframework.org/schema/integration/feed"
xsi:schemaLocation="http://www.springframework.org/schema/integration/feed
	https://www.springframework.org/schema/integration/feed/spring-integration-feed.xsd"
```

### feed 入站通道适配器

你真正需要为检索提要提供支持的唯一适配器是入站通道适配器。它允许你订阅特定的 URL。下面的示例展示了一种可能的配置：

```
<int-feed:inbound-channel-adapter id="feedAdapter"
        channel="feedChannel"
        url="https://feeds.bbci.co.uk/news/rss.xml">
    <int:poller fixed-rate="10000" max-messages-per-poll="100" />
</int-feed:inbound-channel-adapter>
```

在前面的配置中，我们订阅了由`url`属性标识的 URL。

在检索新闻项目时，将其转换为消息，并将其发送到由`channel`属性标识的通道。每个消息的有效负载是`com.sun.syndication.feed.synd.SyndEntry`实例。每一个都封装了关于新闻项目的各种数据（内容、日期、作者和其他细节）。

入站提要通道适配器是一个轮询消费者。这意味着你必须提供一个 Poller 配置。然而，关于提要，你必须了解的一件重要事情是，它的内部工作方式与大多数其他轮询消费者略有不同。启动入站提要适配器时，它执行第一次轮询，并接收`com.sun.syndication.feed.synd.SyndEntryFeed`实例。该对象包含多个`SyndEntry`对象。每个条目存储在本地条目队列中，并基于`max-messages-per-poll`属性中的值释放，这样每个消息都包含一个条目。如果在从条目队列检索条目的过程中，队列已为空，则适配器尝试更新提要，从而在队列中填充更多条目（`SyndEntry`实例）（如果有可用的话）。否则，下一次对提要进行轮询的尝试将由 Poller 的触发器决定（在前面的配置中，每十秒钟进行一次轮询）。

### 重复条目

对提要进行轮询可能会导致条目已经被处理（“我已经阅读了该新闻项目，为什么要再次向我显示它？”） Spring 集成提供了一种方便的机制，以消除对重复条目的担心。每个提要条目都有一个“发布日期”字段。每次生成和发送新的`Message`时， Spring 集成都会在`MetadataStore`策略的实例中存储最新发布日期的值（参见[元数据存储](./meta-data-store.html#metadata-store)）。

|   |用于持久化最新发布日期的键是提要入站通道适配器组件的（必需的）`id`属性的值加上适配器配置中的`feedUrl`（如果有的话）。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

### 其他选项

从版本 5.0 开始，已废弃的`com.rometools.fetcher.FeedFetcher`选项已被删除，并提供了用于`org.springframework.core.io.Resource`的重载`FeedEntryMessageSource`构造函数。当提要源不是 HTTP 端点，而是任何其他资源（例如 FTP 上的本地或远程）时，这很有用。在`FeedEntryMessageSource`逻辑中，这样的资源（或提供`URL`）由`SyndFeedInput`解析到用于前面提到的处理的`SyndFeed`对象。还可以将自定义的`SyndFeedInput`（例如，使用`allowDoctypes`选项）实例注入`FeedEntryMessageSource`。

|   |如果到提要的连接需要一些定制，例如连接和读取超时，则必须使用带有其`customizeConnection(HttpURLConnection)`覆盖功能的`org.springframework.core.io.UrlResource`扩展，而不是普通的`URL`注入到<br/>例如：<br/><br/>```<br/>@Bean<br/>@InboundChannelAdapter("feedChannel")<br/>FeedEntryMessageSource feedEntrySource() {<br/>    UrlResource urlResource =<br/>	    new UrlResource(url) {<br/><br/>	        @Override<br/>	        protected void customizeConnection(HttpURLConnection connection) throws IOException {<br/>	            super.customizeConnection(connection);<br/>	            connection.setConnectTimeout(10000);<br/>	            connection.setReadTimeout(5000);<br/>	        }<br/>	    };<br/>    return new FeedEntryMessageSource(urlResource, "myKey");<br/>}<br/>```|
|---| |

### Java DSL 配置

下面的 Spring 引导应用程序展示了如何使用 Java DSL 配置入站适配器的示例：

```
@SpringBootApplication
public class FeedJavaApplication {

    public static void main(String[] args) {
        new SpringApplicationBuilder(FeedJavaApplication.class)
            .web(false)
            .run(args);
    }

    @Value("org/springframework/integration/feed/sample.rss")
    private Resource feedResource;

    @Bean
    public MetadataStore metadataStore() {
        PropertiesPersistingMetadataStore metadataStore = new PropertiesPersistingMetadataStore();
        metadataStore.setBaseDirectory(tempFolder.getRoot().getAbsolutePath());
        return metadataStore;
    }

    @Bean
    public IntegrationFlow feedFlow() {
        return IntegrationFlows
                .from(Feed.inboundAdapter(this.feedResource, "feedTest")
                                .metadataStore(metadataStore()),
                        e -> e.poller(p -> p.fixedDelay(100)))
                .channel(c -> c.queue("entries"))
                .get();
    }

}
```