SpringCloud 数据流教程(一)
一、云和大数据
数字世界由大约 440 亿字节的数据组成。一个 zettabyte 是 100 万 Pb,或 10 亿 TB,或 1 万亿 GB。2019 年,谷歌处理了大约 370 万次查询,YouTube 记录了 450 万次观看视频,脸书每 60 秒注册 100 万次登录。想象一下计算机处理所有这些请求、数据接收和数据操作的能力。常识告诉我们,大型 IT 公司使用大量硬件来保存数据。需要整合大量存储以防止容量限制。
IT 公司如何应对数据过载、成本上升或技能缺口等挑战?近年来,大型 IT 公司投入巨资开发战略,使用企业数据仓库(EDW)作为中央数据系统,从不同来源报告、提取、转换和加载(ETL)流程。如今,用户和设备(恒温器、灯泡、安全摄像头、咖啡机、门铃、座椅传感器等。)摄取数据。
戴尔、英特尔和 Cloudera 等公司携手打造硬件和存储解决方案,帮助其他公司发展壮大,变得更快、更具可扩展性。
一点数据科学
当我们谈论数据科学时,一个拥有博士学位的科学家团队浮现在脑海中。他们可能挣大钱,他们不休息,因为公司依赖他们。一个数据科学家的实际教育经历是怎样的?
几年前,计算杂志披露,随着 Hadoop、Kafka、Hive、Pig、Cassandra、D3 和 Tableau 等工具的加入,Spark 和 Scala 在希望应用数据科学的公司中迅速发展。
Python 已经成为机器学习技术的主要编程语言之一,与 R、Scala 和 Java 齐名。
机器学习通常在商业、数学、计算机科学和通信领域发挥作用。数据科学家使用数据进行预测、分类、推荐、模式检测和分组、异常检测、识别、可操作的洞察、自动化流程、决策、评分和排名、细分、优化和预测。太多了!
图 1-1。
数据科学
我们需要合适的工具、平台、基础设施和软件工程知识来创新和创造。机器学习应该依赖于一种感觉舒适、易学的编程语言(比如 Python)。平台应该有合适的引擎来处理数据。基础设施应该是可靠的、安全的和冗余的。开发技术应该创造出令人敬畏的企业解决方案,不仅使公司受益,而且使全世界的用户受益。
云
在过去的十年里,许多公司已经进入了所谓的云,或者他们是云本地人,或者他们是在云计算时代;但这到底意味着什么呢?有几家公司表示,他们一直都在云中,因为他们的所有服务都在公司之外,由第三方管理,而且如果发生停机,他们的响应速度会更快。但这准确吗?还是云意味着通过互联网启用服务器、网络、存储、开发工具和应用的架构计算?
在我看来,我们可以通过公共云环境访问互联网,用户可以通过 ?? 互联网连接随时随地“接入”数据和应用。我认为云是一种新的测量服务,采用随用随付的模式,您只需为您正在使用的任何服务器、网络、存储、带宽、应用或更多服务付费——非常类似于电力或水务公司根据消耗量收费。
我还将云视为一种按需自助服务 *。*您可以请求这些服务中的任何一项,只需点击几下鼠标,它们就会很快被提供。
我可以将云视为一种多租户模型,其中应用、网络或服务器的单个实例由多个用户共享。这被称为共享资源池。一旦您使用完它,它将返回到池中等待另一个用户请求它。
我可以将云视为一个弹性平台,其中的资源可以根据需要快速伸缩(见图 1-2 )。
图 1-2。
云计算
云技术和基础设施
我认为今天的云技术意味着公司可以快速扩展和适应。他们可以加速创新,更有效地推动业务灵活性,满怀信心地简化运营,并降低成本以更好地与其他公司竞争。这导致公司持续增长。如今,在技术方法上更具战略性的公司在财务上做得更好,但这些公司如何看待新的云技术?
亚马逊(按需计算的先驱)、谷歌和微软等大型 IT 公司都提供云技术。这些公司获得了丰厚的报酬,为公司提供云基础架构,提供弹性、托管服务、按需计算和存储、网络等。
实施云基础架构需要存储、服务器或虚拟机。还需要托管服务、混合运营以及数据安全和管理。这些服务允许公司将他们的数据用于所有这些新的机器学习工具,并将新的人工智能算法应用于系统分析,以帮助欺诈检测,帮助决策,这是数据处理的几个不断增长的功能(见图 1-3 )。
图 1-3。
云基础设施
合适的工具
在我 20 年的经验中,我见过大公司使用工具和技术来帮助他们以正确的方式使用收集的数据,并遵循数据操作的最佳实践和标准。由于所有新的需求和服务需求增加的方式,公司雇用知道如何使用诸如 JMS、RabbitMQ、Kinesis、Kafka、NATs、ZeroMQ、ActiveMQ、Google PubSub 等工具的人。我们看到随着这些技术出现了更多的消息模式,例如事件驱动或数据驱动模式(见图 1-4 )。这些模式并不新鲜,但直到现在才受到重视。
图 1-4。
数据驱动型企业
像 Apache Hadoop 这样的技术跨集群分布大型数据集。Apache Flume 是一个简单灵活的数据流架构,也是一个收集、聚合和移动大量日志数据的服务。Apache Sqoop 是一个批处理工具,用于在 Apache Hadoop 和结构化数据存储(如关系数据库)之间传输批量数据;它解决了你需要做的一些数据争论。
新一波编程语言可以处理大量数据。这些语言包括 R、Python 和 Scala 等语言,以及一系列用于机器学习和专家系统的库和框架,如 MadLib(见图 1-5 和 1-6 )。
图 1-6。
数据流
图 1-5。
数据流
消息传递代理的新协议每天都会出现。我们应该学习所有这些新技术吗?或者我们应该雇佣具备所有这些技能的人吗?我认为我们至少应该有一种技术来处理通信。好了,我们做到了:SpringCloudStream 和编制器,SpringCloud 数据流(见图 1-7 )。
我将讨论这两种技术。如果你是 Spring 开发者,你不需要学习任何新的消息传递 APIs 你可以使用你已经知道的东西——Java 和 Spring。如果您不熟悉 Spring,在接下来的两章中,我将快速浏览一下 Spring Boot、Spring Integration 和 Spring Batch,并向您展示如何使用它们。这三项技术是 SpringCloudStream 和 SpringCloud 数据流的核心(见图 1-7 )。
接下来,您将创建您的第一个流式应用,它可以连接而不考虑消息传递代理。没错;在多个流式应用之间设置哪个代理并不重要。SpringCloudStream 有这个能力。您将开发定制流并创建一个定制绑定器,允许您对消息传递代理隐藏任何 API。
最后,我谈谈 Spring CloudStream 及其组件,以及如何创建应用、流和任务并监控它们(见图 1-7 )。
图 1-7。
数据流:SpringCloud 数据流
摘要
在这一章中,我谈到了大数据和使用提供开箱即用解决方案的云基础架构来改善服务的新方法。每家公司都需要有知名度、速度、快速进入市场的能力,以及做出反应的时间。
在这短短的一章中,我想为这本书设定背景。在接下来的章节中,我将讨论帮助您使用大数据创建企业级解决方案的技术。
二、Spring Boot
构建云原生应用的一种方法是遵循十二因素应用指南( https://12factor.net ),这些指南有助于在任何云环境中运行应用。其中一些原则,如依赖声明(第二因素)、配置(第三因素)和端口绑定(第七因素),等等,都是 Spring Boot 支持的!Spring Boot 是一个微服务和云就绪框架。
为什么是 Spring Boot 而不仅仅是 Spring?或者是另一种技术,比如 NodeJS 或 Go 语言?Spring Boot 是一项无与伦比的技术,因为它由 Java 社区中最常用的框架提供支持,并允许您轻松创建企业级应用。其他语言需要您进行大量的手动设置和编码。Spring Boot 为你提供了它。即使像 NodeJS 这样的技术有数百个库,但在我看来,它在企业级别上比不上 Spring。别误会我。我并不是说其他技术不好或者没用,但是如果你想构建一个快速、细粒度的企业应用,只有 Spring Boot 提供了最少的配置和代码。让我们看看为什么 Spring Boot 很重要,以及它如何帮助您创建云原生应用。
什么是 Spring 框架,什么是 Spring Boot?
Spring Boot 是下一代的 Spring 应用。它是一种固执己见的运行时技术,公开了创建企业级 Spring 应用的最佳实践。
Spring 框架
让我们稍微倒退一下,谈谈 Spring 框架。使用 Spring 框架,您可以创建细粒度的企业应用,但您需要知道它如何工作,最重要的是,如何配置它。配置是 Spring 框架的关键元素之一。您可以将定制实现、DB 连接和对外部服务的调用解耦,从而使 Spring Framework 更具可扩展性、更易于维护和运行。在某种程度上,你需要知道所有适用于 Spring 应用的最佳实践。让我们从一个简单的 Spring 应用开始,它演示了 Spring 框架是如何工作的。
目录应用
假设您需要创建一个 Spring 应用来保存人们的联系信息,比如姓名、电子邮件和电话号码。这是一个基本的目录应用,它在任何数据库引擎中公开了一个具有持久性的 REST API,并且可以部署在任何兼容的 J2EE 服务器中。下面是创建这样一个应用的步骤。
-
安装一个像 Maven 或者 Gradle 这样的构建工具,编译构建源代码的目录结构。如果你有 Java 背景,你应该知道你需要一个 WEB-INF 目录结构。
-
创建
web.xml和application-context.xml文件。web.xml文件有org.springframework.web.servlet.DispatcherServlet类,它充当基于 Spring 的 web 应用的前端控制器。 -
添加一个指向
application-context.xml文件的监听器类,在这里声明所有的 Spring beans 或应用所需的任何其他配置。如果省略侦听器部分,您需要将 Spring beans 声明文件命名为与DispatcherServlet相同的名称。 -
在
application-context.xml文件,中添加几个 Spring beans 部分来覆盖每个细节。如果使用 JDBC,需要添加一个数据源、init SQL 脚本和一个事务管理器。如果您正在使用 JPA,您需要添加一个 JPA 声明(一个persistence.xml文件,您可以在其中配置您的类和主要单元)和一个实体管理器来处理会话并与事务管理器通信。 -
因为这是一个 web app,所以需要在
application-context.xml文件中添加一些关于 HTTP 转换器的 Spring beans 部分,这些转换器公开 JSON 视图和 MVC 驱动的注释来使用@RestController和@RequestMapping(或者@GetMapping、@PostMapping、@DeleteMapping 等。)以及其他 Spring MVC 注释。 -
如果您使用 JPA(最简单的持久化方法),用
@EnableJpaRepositories注释指定存储库和类的位置。 -
要运行该应用,请以 WAR 格式打包您的应用。您需要安装一个符合 J2EE 标准的应用服务器,然后测试它。
如果你是一个有经验的 Spring 开发者,你知道我在说什么。如果你是一个新手,那么你需要学习所有的语法。这不是太难,但你需要花一些时间在这上面。或者也许有另一种方法。当然有。您可以使用基于注释的配置或 JavaConfig 类来设置 Spring beans,也可以混合使用两者。最后,你需要学习一些 Spring 注释来帮助你配置这个应用。你可以在这本书的网站上查看源代码(ch02/directory-jpa)。
让我们回顾一下这个应用的一些代码。记住,你需要创建一个 Java web 结构(见图 2-1 )。
图 2-1。
基于 Java 网络的目录结构
图 2-1 显示了一个基于 Java 网络的目录结构。可以删除index.jsp文件,打开web.xml文件,全部替换为清单 2-1 所示的内容。
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/application-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
Listing 2-1.web.xml
清单 2-1 向您展示了如何添加一个 Spring servlet ( DispatcherServlet,一个前端控制器模式),它是处理来自用户的任何请求的主 servlet。
接下来,让我们通过添加清单 2-2 中的内容来创建application-context.xml文件。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/jdbc https://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/data/jpa https://www.springframework.org/schema/data/jpa/spring-jpa.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<mvc:annotation-driven />
<tx:annotation-driven />
<jpa:repositories base-package="com.apress.spring.directory.repository" entity-manager-factory-ref="localContainerEntityManagerFactoryBean" />
<context:component-scan base-package="com.apress.spring.directory" />
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager">
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="objectMapper"/>
</bean>
<bean class="org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter">
<property name="objectMapper" ref="xmlMapper"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<bean id="objectMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
<property name="indentOutput" value="true"/>
<property name="modulesToInstall" value="com.fasterxml.jackson.module.paramnames.ParameterNamesModule"/>
</bean>
<bean id="xmlMapper" parent="objectMapper">
<property name="createXmlMapper" value="yes"/>
</bean>
<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="mediaTypes">
<value>
json=application/json
xml=application/xml
</value>
</property>
</bean>
<bean id="localContainerEntityManagerFactoryBean"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
</property>
<property name="jpaProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">create-drop</prop>
<prop key="hibernate.dialect">org.hibernate.dialect.H2Dialect</prop>
</props>
</property>
</bean>
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.h2.Driver" />
<property name="url" value="jdbc:h2:mem:testdb" />
<property name="username" value="sa" />
<property name="password" value="" />
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="localContainerEntityManagerFactoryBean" />
</bean>
<bean id="persistenceExceptionTranslationPostProcessor" class=
"org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
<jdbc:embedded-database type="H2" >
<jdbc:script location="classpath:META-INF/sql/schema.sql"/>
<jdbc:script location="classpath:META-INF/sql/data.sql"/>
</jdbc:embedded-database>
</beans>
Listing 2-2.application-context.xml
清单 2-2 显示了application-context.xml文件,在该文件中,您为 Spring 容器添加了所有必要的配置,所有的类都是在这个容器中初始化和连接的。
在回顾每一个标记和它的声明方式之前,看看你是否能猜出每一个标记是做什么的,以及为什么它是这样配置的。看看声明之间的命名和引用。
如果你是 Spring 新手,推荐你看看 Apress 出版的 Pro Spring 系列。这些书解释了配置 Spring 的这种声明形式的每个方面。
接下来,分别添加以下类:Person、PersonRepository和PersonController(参见清单 2-3 、 2-4 和 2-5 )。
package com.apress.spring.directory.domain;
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
public class Person {
@Id
private String email;
private String name;
private String phone;
public Person() {
}
public Person(String email, String name, String phone) {
this.email = email;
this.name = name;
this.phone = phone;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
Listing 2-3.com.apress.spring.directory.domain.Person.java
清单 2-3 展示了使用所有 JPA (Java Persistence API)注释的Person类,所以很容易使用,没有更多直接的 JDBC。
package com.apress.spring.directory.repository;
import com.apress.spring.directory.domain.Person;
import org.springframework.data.repository.CrudRepository;
public interface PersonRepository extends CrudRepository<Person,String> {
}
Listing 2-4.com.apress.spring.directory.repository.PersonRepository.java
清单 2-4 显示了从另一个CrudRepository接口扩展而来的PersonRepository接口。在这里,它使用 Spring Data 和 Spring Data JPA 的所有功能来创建一个基于实体类及其主键(在本例中是一个String类型)的存储库模式。换句话说,不需要创建任何 CRUD 实现——让 Spring Data 和 Spring Data JPA 来处理。
package com.apress.spring.directory.controller;
import com.apress.spring.directory.domain.Person;
import com.apress.spring.directory.repository.PersonRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
@Controller
public class PersonController {
private Logger log = LoggerFactory.getLogger(PersonController.class);
private PersonRepository personRepository;
public PersonController(PersonRepository personRepository) {
this.personRepository = personRepository;
}
@RequestMapping(value = "/people",
method = RequestMethod.GET,
produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
@ResponseBody
public Iterable<Person> getPeople() {
log.info("Accessing all Directory people...");
return personRepository.findAll();
}
@RequestMapping(value = "/people",
method = RequestMethod.POST,
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = {MediaType.APPLICATION_JSON_VALUE})
@ResponseBody
public ResponseEntity<?> create(UriComponentsBuilder uriComponentsBuilder, @RequestBody Person person) {
personRepository.save(person);
UriComponents uriComponents =
uriComponentsBuilder.path("/people/{id}").buildAndExpand(person.getEmail());
return ResponseEntity.created(uriComponents.toUri()).build();
}
@RequestMapping(value = "/people/search",
method = RequestMethod.GET,
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = {MediaType.APPLICATION_JSON_VALUE})
@ResponseBody
public ResponseEntity<?> findByEmail(@RequestParam String email) {
log.info("Looking for {}", email);
return ResponseEntity.ok(personRepository.findById(email).orElse(null));
}
@RequestMapping(value = "/people/{email:.+}",
method = RequestMethod.DELETE)
@ResponseBody
public ResponseEntity<?> deleteByEmail(@PathVariable String email) {
log.info("About to delete {}", email);
personRepository.deleteById(email);
return ResponseEntity.accepted().build();
}
}
Listing 2-5.com.apress.spring.directory.controller.PersonController.java
清单 2-5 显示了任何用户请求/响应的PersonController类的实现。在 Spring 中实现一个 web 控制器有很多方法,比如使用@RestController。避免在每个方法中写@ResponseBody,或者使用类似@GetMapping, @PostMapping,和@DeleteMapping的专用注释来代替@RequestMapping。
接下来,创建初始化数据库的 SQL 文件(参见清单 2-6 和 2-7 )。
CREATE TABLE person (
email VARCHAR(100) NOT NULL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
phone VARCHAR(20) NOT NULL,
);
Listing 2-6.META-INF/sql/schema.sql
清单 2-6 是一个简单的模式,只包含一个表。
INSERT INTO person (email,name,phone) VALUES('mark@email.com','Mark','1-800-APRESS');
INSERT INTO person (email,name,phone) VALUES('steve@email.com','Steve','1-800-APRESS');
INSERT INTO person (email,name,phone) VALUES('dan@email.com','Dan','1-800-APRESS');
Listing 2-7.META-INF/sql/data.sql
清单 2-7 显示了应用启动时要插入的一些记录。接下来因为这个 app 用的是 JPA,所以需要提供一个persistence.xml文件。还有另一个选择——您可以向application-context.xml添加一个 bean 声明,并声明 JPA 引擎工作所需的持久性单元(参见清单 2-8 )。
<persistence xmlns:="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.2"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<!-- Define persistence unit -->
<persistence-unit name="directory">
</persistence-unit>
</persistence>
Listing 2-8.META-INF/persistence.xml
清单 2-8 显示了声明持久性单元所需的 JPA 文件。您可以在localContainerEntityManagerFactoryBean bean 声明中将其声明为一个属性(persistenceUnitName属性)。
接下来是最重要的文件之一。这个应用是使用 Maven 作为构建工具创建的。让我们在项目的根目录下创建一个pom.xml文件(参见清单 2-9 )。
<project xmlns:="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.apress.spring</groupId>
<artifactId>directory-jpa</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>directory-web-project Maven Webapp</name>
<url>http://maven.apache.org</url>
<properties>
<java.version>1.8</java.version>
<servlet.version>3.1.0</servlet.version>
<spring-framework.version>5.2.2.RELEASE</spring-framework.version>
<spring-data.jpa>2.2.3.RELEASE</spring-data.jpa>
<slf4j.version>1.7.25</slf4j.version>
<logback.version>1.2.3</logback.version>
<h2>1.4.199</h2>
<jackson>2.10.1</jackson>
</properties>
<dependencies>
<!-- Spring Core/MVC/Web -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>${spring-data.jpa}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<!-- Other Web dependencies -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet.version}</version>
<scope>provided</scope>
</dependency>
<!-- JPA -->
<dependency>
<groupId>jakarta.activation</groupId>
<artifactId>jakarta.activation-api</artifactId>
<version>1.2.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
<version>2.2.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>jakarta.transaction</groupId>
<artifactId>jakarta.transaction-api</artifactId>
<version>1.3.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.4.9.Final</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<artifactId>jboss-transaction-api_1.2_spec</artifactId>
<groupId>org.jboss.spec.javax.transaction</groupId>
</exclusion>
<exclusion>
<artifactId>javax.activation-api</artifactId>
<groupId>javax.activation</groupId>
</exclusion>
<exclusion>
<artifactId>javax.persistence-api</artifactId>
<groupId>javax.persistence</groupId>
</exclusion>
<exclusion>
<artifactId>jaxb-api</artifactId>
<groupId>javax.xml.bind</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- JSON/XML -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-parameter-names</artifactId>
<version>${jackson}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>${jackson}</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>${h2}</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<finalName>directory-jpa</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Listing 2-9.pom.xml
清单 2-9 显示了 pom.xml 文件,其中声明了所有的依赖项。如果你来自 J2EE 背景,你可能会发现这很难,因为你需要找到一种与他人相处融洽的依赖关系。这可能需要一点时间。
接下来,您需要打包应用。安装 Maven (
mvn package
该命令打包应用并生成target/directory-jpa.war文件。要运行应用,您需要一个可以运行 J2EE 应用的应用服务器;最常见的是 Tomcat。从 https://tomcat.apache.org/download-90.cgi 下载版本 9,然后解压并将directory-jpa.war部署/复制到webapps/ Tomcat 文件夹中。要启动 Tomcat 服务器,使用bin/文件夹中的脚本。看一下脚本。要启动 Tomcat,通常需要执行名为startup.sh(对于 Unix 用户)或startup.bat(对于 Windows 用户)的脚本。
您可以使用cUrl命令行或任何其他 GUI 应用来测试您的应用,比如 Postman ( www.getpostman.com ),来执行所有的请求。例如,要查看目录中列出的所有人,请执行以下命令。
$ curl http://localhost:8080/directory-jpa/people -H "Content-Type: application/json"
您应该得到以下输出。
[ {
"email" : "mark@email.com",
"name" : "Mark",
"phone" : "1-800-APRESS"
}, {
"email" : "steve@email.com",
"name" : "Steve",
"phone" : "1-800-APRESS"
}, {
"email" : "dan@email.com",
"name" : "Dan",
"phone" : "1-800-APRESS"
} ]
正如你所看到的,这里有很多步骤,我遗漏了你需要添加到应用中的部分业务逻辑。一个训练有素的 Spring 开发人员可能要花三个小时来交付这个应用,而且他们一半以上的时间都花在了配置应用上。如何加快这一配置过程?在配置上花费太多时间会导致错误和不良行为。
Note
请记住,您可以访问本书的配套源代码。可以从 Apress ( www.apress.com )下载。本例的文件夹在ch02/directory-jpa中。
Spring Boot
Spring Boot 来救援了!Spring 工程师意识到 Spring 开发人员对每个 Spring 应用都遵循相同的步骤,所以他们想出了一个更好的方法来进行配置和添加最佳实践。他们创造了 Spring Boot,这是一个固执己见的引擎,它为你设定了最佳实践,让你可以专注于代码。
Spring Boot 提供了许多功能,这些功能将在后面讨论。现在,让我们回到目录应用。
Spring Boot 的目录应用
让我们从使用 Spring Initializr 创建结构开始。打开浏览器,指向 https://start.spring.io (见图 2-2 和 2-3 )。
图 2-2。
图 2-2 是 Spring Initializr 的截图。将以下数据添加到字段中。
-
组:
com.apress.boot -
神器:
directory -
依赖项:Spring Web、Spring Data JPA、Rest 存储库和 H2 数据库
您可以保留其他选项的默认值。单击生成按钮。在任何可以运行 Java 应用的 IDE 中打开 ZIP 文件。我推荐 IntelliJ 社区版( www.jetbrains.com/idea/download/ )或者微软的可视代码配合适的插件运行 Java 和 Spring 的应用( https://code.visualstudio.com/download )。
花点时间分析一下结构。调用清单 2-3 ( Person.java)和清单 2-4 ( PersonRepository.java)中的类以及清单 2-7 ( data.sql)中的文件。您可以将Person和PersonRepository类添加到com.apress.boot.directory文件夹/包中,将data.sql文件添加到资源文件夹中(参见图 2-3 )。
图 2-3。
智能:Spring Boot 目录应用结构
图 2-3 显示了 Spring Boot 应用的结构和类的位置。注意这里有额外的文件,包括mvnw*脚本和一个隐藏的.mvn文件夹。这是 Maven 的一个瘦包装器,意味着你不需要安装 Maven。您可以从命令行运行它。
你完了!你不需要做任何其他事情。使用 IDE 或命令行运行应用。在运行应用之前,请确保停止 Tomcat。然后,您可以从终端运行该应用,方法是转到directory文件夹并运行以下命令。
$ ./mvnw spring-boot:run
上述命令仅在 Unix 系统上运行。如果您使用 Windows 操作系统,则执行以下操作来运行您的应用。
> mvnw.bat spring-boot:run
然后你可以如下执行一个cUrl命令。
$ curl http://localhost:8080/persons -H "Content-Type: application/json
您会得到相同的结果—一个人员列表。一个有经验的 Spring 或 Spring Boot 开发者可以在 5 分钟内创建这个应用!一个 Spring Boot 的新手通常需要 15 分钟来创建它。(看,根本没有配置)。Spring Boot 是一个自以为是的运行时,它会发现你的类路径中有什么,并基于此设置一些默认值以避免任何其他配置,如DispatcherServlet设置、自定义HttpMessageConverters、DB 初始化器和控制器。它使用 HATEOAS(作为应用状态引擎的超媒体)协议来生成响应,并添加了所有的 HTTP 方法实现。如果您想添加一个新人,使用cUrl执行以下命令。
$ curl -XPOST http://localhost:8080/persons -H "Content-Type: application/json" -d '{"email":"mike@email.com","name":"Mike","phone":"1-800-APRESS"}'
Spring Boot 是如何变魔术的?如果你想更多地了解 Spring Boot,以及这种配置是如何毫不费力地完成的,我推荐阅读我的一些其他书籍,如 Pro Spring Boot 2 (Apress,2019)。
您可以使用 Spring 和 Spring Boot 的强大功能轻松创建现成的应用。
超越目录应用示例
通常,书籍以一个必做的 HelloWorld 示例或一个简单的应用(如目录应用)开始。现在,让我们通过创建一个使用来自 Twitter feed 的流处理的微服务来见证 Spring Boot 的力量。在这一节中,我给出了构建这个应用的一步一步的说明。
首先,在 https://developer.twitter.com 登录 Twitter 开发者计划。您需要获得四个密钥:消费者 API 密钥、消费者 API 秘密密钥、访问令牌密钥和访问令牌秘密密钥(参见图 2-4 )。
图 2-4。
https://developer.twitter.com/ 钥匙和令牌
接下来打开一个浏览器,指向 https://start.spring.io (见图 2-5 )。使用以下信息。
图 2-5。
https://start.spring.io 推特 app
-
组:
com.apress.boot -
神器:
twitter -
依赖性:无
点击生成按钮下载一个 ZIP 文件。解压缩文件并在任何 IDE 中打开项目。创建几个类,你应该有最终的应用,如图 2-6 所示。
图 2-6。
Twitter 应用
接下来,打开pom.xml文件并添加清单 2-10 中所示的依赖项。
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-twitter</artifactId>
<version>1.1.0.RELEASE</version>
</dependency>
Listing 2-10.pom.xml spring-social-twitter dependency
接下来,创建TwitterProperties类(参见清单 2-11 )。
package com.apress.boot.twitter;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "twitter")
public class TwitterProperties {
private String consumerKey;
private String consumerSecret;
private String accessToken;
private String accessTokenSecret;
public String getConsumerKey() {
return consumerKey;
}
public void setConsumerKey(String consumerKey) {
this.consumerKey = consumerKey;
}
public String getConsumerSecret() {
return consumerSecret;
}
public void setConsumerSecret(String consumerSecret) {
this.consumerSecret = consumerSecret;
}
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public String getAccessTokenSecret() {
return accessTokenSecret;
}
public void setAccessTokenSecret(String accessTokenSecret) {
this.accessTokenSecret = accessTokenSecret;
}
}
Listing 2-11.com.apress.boot.twitter.TwitterProperties.java
清单 2-11 显示了保存 Twitter API 工作所需密钥的TwitterProperties类。所有这些属性都在application.properties文件中声明,它有twitter前缀。
接下来,创建TwitterConfig类(参见清单 2-12 )。
package com.apress.boot.twitter;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.social.twitter.api.impl.TwitterTemplate;
@Configuration
@EnableConfigurationProperties(TwitterProperties.class)
public class TwitterConfig {
private TwitterProperties twitterProperties;
public TwitterConfig(TwitterProperties twitterProperties){
this.twitterProperties = twitterProperties;
}
@Bean
TwitterTemplate twitterTemplate(){
return new TwitterTemplate(twitterProperties.getConsumerKey(),
twitterProperties.getConsumerSecret(), twitterProperties.getAccessToken(), twitterProperties.getAccessTokenSecret());
}
}
Listing 2-12.com.apress.boot.twitter.TwitterContig.java
清单 2-12 显示了TwitterConfig类,该类具有创建TwitterTemplate实例的初始化配置。该实例处理所需的连接和密钥交换。这是一种非常简单的方法,可以登录 Twitter 并进行一些操作,比如阅读或创建推文。
接下来,创建TwitterStart类(参见清单 2-13 )。
package com.apress.boot.twitter;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.social.twitter.api.*;
import java.util.Collections;
@Configuration
public class TwitterStart {
@Bean
CommandLineRunner start(Twitter twitter){
return args -> {
twitter.streamingOperations().sample(Collections.singletonList(new StreamListener() {
@Override
public void onTweet(Tweet tweet) {
tweet.getEntities().getHashTags().forEach(hashTagEntity -> {
System.out.println(String.format("#%s",hashTagEntity.getText()));
});
}
@Override
public void onDelete(StreamDeleteEvent streamDeleteEvent) { }
@Override
public void onLimit(int i) { }
@Override
public void onWarning(StreamWarningEvent streamWarningEvent) { }
}));
};
}
}
Listing 2-13.com.apress.boot.twitter.TwitterStart.java
清单 2-13 显示了TwitterStart类。一旦 Spring Boot 完成配置和设置,这个类就执行start方法。这里一个重要的细节是StreamListener的用法,它监听 Twitter 上公开的每一个标签。这个程序唯一做的事情就是打印标签。
接下来,将密钥添加到application.properties文件中(参见清单 2-14 )。
# Twitter Properties
twitter.consumerKey=YbJisGFe9Jo3lAFE30wYR08To
twitter.consumerSecret=Slm9EJYOTFunnw5YWm13Px3HH6jQGDt2NJp8N4DyjhmIv2HtZK
twitter.accessToken=64771614-9RjhlWVy5h6PKhvQbCjm7rt2BcB66ZVEJwZ7DAPCN
twitter.accessTokenSecret=oRluin2ZMgNOKHUP0JSJc6HMEjul2QC6aeSAV4DBKW8uz
Listing 2-14.application.properties
请记住,这些键来自您的 Twitter 开发人员门户应用。现在,您可以使用 Maven 包装器运行应用。
$ ./mvnw spring-boot:run
你应该看到一堆实时发送到 Twitter 的标签!
如您所见,创建一个快速、细粒度的企业应用非常容易。这就是 Spring Boot 的魅力。
Spring Boot 特色
Spring Boot 的特色如此之多,以至于需要一整本书来描述它们。但是,我可以在本节中描述其中的一些。
-
SpringApplication类提供了一种启动 Spring 应用的便捷方式。它在主类中。 -
Spring Boot 允许您创建没有任何 XML 配置的应用。它不做代码生成。
-
Spring Boot 通过
SpringApplicationBuildersingleton 类提供了一个流畅的构建器 API,允许你创建具有多个应用上下文的层次结构。这个特性与 Spring 框架有关。我将在接下来的章节中解释这个特性,但是如果您是 Spring 和 Spring Boot 的新手,您只需要知道您可以扩展 Spring Boot 来获得对您的应用的更多控制。 -
Spring Boot 提供了更多配置 Spring 应用事件和监听器的方法。这将在接下来的章节中解释。
-
Spring Boot 是一种固执己见的技术。该特性试图创建正确类型的应用,既可以是 web 应用(通过嵌入 Tomcat、Netty、Undertow 或 Jetty 容器),也可以是单个应用。
-
通过
org.springframework.boot.ApplicationArguments接口,Spring Boot 允许访问任何应用参数。当您使用参数运行应用时,这是一个非常有用的特性。 -
Spring Boot 允许您在应用启动后执行代码。您唯一需要做的就是实现
CommandLineRunner接口和run(String ...args)方法。一个特殊的例子是在启动时初始化数据库中的一些记录,或者在应用启动前检查一些服务以查看它们是否正在运行。 -
Spring Boot 允许您通过使用
application.properties或application.yml文件来具体化配置。在接下来的章节中会有更多的介绍。 -
您可以添加与管理相关的功能,通常通过 JMX,在
application.properties或application.yml文件中启用spring.application.admin.enabled属性。 -
Spring Boot 允许您拥有配置文件,帮助您的应用在不同的环境中运行。
-
Spring Boot 允许您非常简单地配置和使用日志记录。
-
Spring Boot 通过使用 starter poms 提供了一种配置和管理依赖项的简单方法。换句话说,如果你想创建一个 web 应用,你只需要在你的 Maven
pom.xml或build.gradle文件中包含spring-boot-starter-web依赖项。 -
Spring Boot 通过使用 Spring Boot 致动器提供开箱即用的非功能性要求,该致动器具有新的测微计平台无关框架,允许您对您的应用进行仪器化。
-
Spring Boot 提供了
@Enable<feature>注释,帮助您启用、包含、配置和使用数据库(例如,SQL 和 NoSQL)、Spring Integration 和云,以及缓存、调度、消息、批处理等等。
我将在每一章中讨论其他特性。
摘要
在这一章中,我向你展示了什么是 Spring Boot 以及你可以用它做什么。这是 Spring Cloud Stream 和 Spring Cloud Data Flow 为其组件使用的主要技术。我还向您展示了 Spring Boot 为开发人员提供的一些特性的例子。
在下一章中,我将向您展示创建 Spring CloudStream 的另一个技术基础。也不用担心所有的新术语;它们很快就会让你明白。
三、Spring Integration
如果你搜索单词 communication 的意思,你会发现它来自拉丁语 communicare,,意思是“分享”今天, communication 这个词已经变得越来越强大,不仅用于那些想要分享思想、想法和问题的人,而且也用于技术领域。企业应用需要使用消息作为常规通信协议,在组件或外部系统之间共享数据。
在这一章中,你将了解到 Spring 框架最好的项目之一——Spring Integration!它提供了一种简单的、开箱即用的方式来使用企业集成模式。
通过使用轻量级消息传递,Spring Integration 支持通过声明性适配器与外部系统集成。换句话说,您可以连接 JMS (IBM MQ 和 Tibco,以及其他代理)、AMQP、套接字或 UDP 消息,然后处理、转换并交付到新系统中,如 REST API 端点、NoSQL 持久性或 RabbitMQ 消息传递,最多只需几行代码。
集成模式
Spring Integration 支持企业集成模式 ( www.eaipatterns.com )。本章只介绍了几个模式来帮助你理解它们是如何工作的,以及 Spring Integration 是如何促进它们的使用的,因为它是 Spring CloudStream 的一个重要部分。剧透预警:Spring CloudStream 基于 Spring Integration!
企业集成模式(EIP)符合几种模式(见图 3-1);请记住,设计模式是对反复出现的问题的解决方案。
图 3-1。
企业集成模式
您可以看到每个模式在特定业务逻辑/规则中所扮演的角色的清晰划分。最重要的部分是消息和消息传输。
信息发送
为什么信息传递如此重要?当你创建一个解决方案时,你脑海中有一个基本的场景(见图 3-2 )。这是需要发送到输入的数据,然后是处理,最后是输出到最终阶段(打印)或发送到另一个系统。
图 3-2。
输入-处理-输出
如果你考虑在你的解决方案中调用一个特定的逻辑,你是在调用一个方法(输入),传递一些基于数据的参数(数据)。你执行一些逻辑(过程),然后返回(输出)一个结果(数据)。
现在从信息传递的角度考虑一下。您通过本地进程或向远程服务器发送消息。您的消息可以被处理、增强或触发另一个事件,然后您得到一个结果,可能是您发送的同一条消息,或者是告诉您一切正常的消息,或者可能有一个错误。所有这些通过本地或远程系统的数据都需要通过通道进行传输。渠道在传递信息的方式中扮演着重要的角色。
正如你所看到的,信息在交流中起着重要的作用。
Spring Integration
通过使用在本地或远程系统之间通信的通道或其他通道来应用消息传递。它为您提供了集成消息传递组件以创建可伸缩解决方案的参考,并展示了一些现有的消息传递技术(JMS、TIBCO、SOAP、MSMQ、NET 等)。)以及如何整合它们。如果你想更多地了解 EIP,我推荐 Gregor Hohpe 和 Bobby Woolf(Addison-Wesley Professional,2003)的企业集成模式:设计、构建和部署消息传递解决方案。这本书提供了每种模式的全面解释。
在这一节中,我将向您展示 Spring Integration 如何工作以及如何使用它。通过向您展示一个小型集成应用的例子,您可以更好地了解 Spring Integration 是怎么一回事。
电影应用规范要求
让我们创建一个包含两部分的电影应用。
假设您在一个文本文件(CSV 格式)中获得了一些关于新电影的信息(标题、演员、年份),您需要将这些信息发送到一个只接受 JSON 格式的外部和远程系统。这个远程系统有一个 REST API 服务,它接受您的集合(/v1/movies)。收到信息后,它将信息保存到数据库(MySQL)中,并向管理员用户发送电子邮件(参见图 3-3 了解大致情况)。
图 3-3。
电影集成应用
电影应用第一部分
以下是电影应用的规格/要求。
-
获取 CSV 文件格式的新电影信息
-
将 CSV 内容转换成 JSON 格式
-
需要并行执行以下操作
-
将电影信息保存到文件系统路径中(文件名需要有一个
.processed后缀;例如action-movies.txt > action-movies.txt.processed -
通过调用 REST API 将 JSON 格式的电影信息发送到外部服务器
-
电影应用:第二部分(外部)
以下是应用外部系统部分的规格/要求。
-
以 REST API 服务为特色:
/v1/movies获取 JSON 格式的电影集的信息 -
将新的电影信息存储到 MySQL 中
-
存储电影信息后发送电子邮件
这个例子非常简单,但是足以向您展示 Spring Integration 是如何工作的。
创建电影应用:第一部分
让我们从创建应用的框架开始。转到 https://start.spring.io 并创建一个项目。将其导出到任何 IDE 中(参见图 3-4 )。或者,使用 STS IDE ( https://spring.io/tools ),单击文件➤新➤ Spring Starter 项目,然后按照向导进行操作。
图 3-4。
https://start.spring.io 的 Spring
图 3-4 显示了 Spring Initializr 主页。您可以添加以下值。
-
组:
com.apress.integration -
神器:
movie -
依赖性:Spring Integration
点击生成项目按钮。解压缩项目,并使用任何 IDE 导入它。
检查您的项目结构,确保您有清单 3-1 中所示的pom.xml文件。我还添加了一个依赖项。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.apress.integration</groupId>
<artifactId>movie</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>movie</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<!-- JSON -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Spring Integration - File -->
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-file</artifactId>
</dependency>
<!-- Spring Integration - Http -->
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-http</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Listing 3-1.The pom.xml File for the Movie App Project
列表 3-1 是你应该有的最终pom.xml that。您会看到将 CSV 行转换成 object/JSON 所需的spring-boot-starter-integrationJSON(Jackson-databind)依赖项、处理文件所需的spring-integration-file依赖项以及将 JSON 发送到外部系统所需的spring-integration-http依赖项。
制作 Spring Boot 应用的最佳特性之一是能够以不同的方式配置 Spring 上下文。您可以通过 XML beans 文件使用声明式编程,也可以通过用@Configuration注释您的类并使用@Bean注释声明您的 bean 来使用 JavaConfig 类。根据您的应用,您可以避免任何配置。Spring Boot 有一个自以为是的运行时,所以它试图找出你正在运行的应用的类型,通过使用它的自动配置和查看类路径,它尽最大努力创建正确的应用类型。
在接下来的小节中,我将向您展示如何使用声明性 XML 创建 Spring Integration 应用。我认为这是学习这种特殊技术的最好方法,因为它比使用 JavaConfig 类更具可读性(在我看来);此外,通过使用声明性 XML,您可以利用 IDE 的强大功能来生成集成图,以可视化企业集成模式是如何工作的。
电影应用:声明性 XML
让我们从使用 JavaConfig 类开始。首先,用清单 3-2 中的内容添加一个名为MovieConfiguration.java的新类。
package com.apress.integration.movie;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
@Configuration
@ImportResource("META-INF/spring/movie-app-integration.xml")
public class MovieConfiguration {
}
Listing 3-2.src/main/java/com/apress/integration/movie/MovieConfiguration.java
@ ImportResource注释导入 XML 文件。这些文件是 Spring 配置的一部分。当使用声明式 XML 时,我认为 Spring Integration 是配置它的最佳方式。别担心。我将在下一节解释如何创建 XML 文件。
通过声明性 XML 的 Spring Integration
接下来,您需要在src/main/resources/META-INF/spring/movie-app-integration.xml中创建一个文件。您还需要创建目录结构(参见清单 3-3 )。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int-file="http://www.springframework.org/schema/integration/file"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-http="http://www.springframework.org/schema/integration/http"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration/file http://www.springframework.org/schema/integration/file/spring-integration-file.xsd
http://www.springframework.org/schema/integration/http http://www.springframework.org/schema/integration/http/spring-integration-http.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd">
<!-- Spring Integration -->
<int-file:inbound-channel-adapter channel="input" directory="target/input" filename-pattern="*.txt">
<int:poller fixed-rate="500"/>
</int-file:inbound-channel-adapter>
<!-- Spring Integration: Direct Channel -->
<int:channel id="input"/>
<!-- Spring Integration: Service Activator -->
<int:service-activator id="movieProcessor" input-channel="input" ref="movieEndpoint" />
</beans>
Listing 3-3.src/main/resources/META-INF/spring/movie-app-integration.xml
清单 3-3 显示了第一部分的 Spring 配置文件。要使用 Spring 配置,需要在 XML 中导入几个名称空间。以下是重要的名称空间。
-
xmlns:int -
xmlns:int-file -
xmlns:int-http
这些名称空间包含几个标签描述。使用最多的标签之一是<int:channel />。这个标签创建了一个通道作为生产者和消费者之间的通信点,生产者通过这个通道发送消息,消费者接收消息。使用通道的好处之一是,它确保消息到达目的地,并将生产者和消费者分离开来。与图 3-2 相同的过程如图 3-5 所示。
图 3-5。
有通道的输入-处理-输出
那么,新的表述告诉了我们什么?
-
输入和输出的符号定义了进行点对点或发布-订阅模式通信的通道。该示例使用直接通道,这是一种点对点模式。
-
流程符号定义了一个服务激活器,它是接收到的消息的处理器。通过输入通道接收消息,如果需要,服务激活器可以通过输出通道返回消息。
Spring Integration 处理基于org.springframework.messaging.Message类的内部消息格式。该接口定义如下。
public interface Message<T> {
/**
* Return the message payload.
*/
T getPayload();
/**
* Return message headers for the message (never {@code null} but may be empty).
*/
MessageHeaders getHeaders();
}
注意Message是一个接口,有办法通过MessageBuilder -fluent API 创建。
MessageBuilder.withPayload("Hello World").setHeader("myheader",
"Hi").setHeader("myotherheader","there").build();
这个例子使用了两个通道。
图 3-6a 和 3-6b。
inbound-channel-adapter通道在左边,outbound-channel-adapter通道在右边。
-
一个是名为
inbound-channel-adapter的特殊通道,用图 3-6a 中的符号表示。 -
第二个通道命名为
outbound-channel-adapter,用图 3-6b 中的符号表示。
两个频道与常规频道相同;唯一的区别是这些通道可以轮询源调用。
回到例子,让我们看看每一部分。
-
<int-file:inbound-channel-adapter />。该通道每隔几秒钟轮询(监控)一次文件系统路径。以下是它的属性。-
directory="target/input"表示频道正在监控目录中的任何新文件。 -
filename-pattern="*.txt"只寻找文件。txt 扩展名。 -
channel="input"表示一旦找到文件(. txt 文件),其内容将被发送到输入通道。 -
<int:poller fixed-rate="500"/>是一个内部标签,表示每 500 毫秒监控一次。
-
-
<int:channel id="input"/>。这个标签创建了一个准备接收文件内容的通道。通常,id 是最匹配的通道的名称,而不考虑其用途(输入或输出)。 -
<int:service-activator/>。这个标签是文件内容的处理器。以下是它的属性。-
input-channel="input"监听来自输入通道的任何消息。 -
ref="movieEndpoint"使用名为 movieEndpoint 的 bean 来处理来自输入通道的消息。
-
如果你使用的是 Spring 工具套件,简称 STS IDE(https://spring.io/tools,可以看到这个例子的 Spring Integration 图(见图 3-7 )。您可以用 IntelliJ 做同样的事情,但是您需要使用付费版本和 Spring Integration 模式插件。
图 3-7。
STS IDE 中的 Spring Integration 图
Spring Integration 开发
接下来,让我们创建Service Activator类,它接收文本文件的内容。用清单 3-4 中的内容创建 MovieEndpoint.java 类。
package com.apress.spring.integration.movie;
import java.io.File;
import java.io.FileInputStream;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.integration.annotation.MessageEndpoint
;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.util.StreamUtils;
@MessageEndpoint
public class MovieEndpoint {
private static final Logger log = LoggerFactory.getLogger(MovieEndpoint.class);
@ServiceActivator
public void process(File input, @Headers Map<String,Object> headers) throws Exception {
FileInputStream in = new FileInputStream(input);
String movies = new String(StreamUtils.copyToByteArray(in));
in.close();
log.info("Received: \n" + movies);
}
}
Listing 3-4.com.apress.integration.movie.MovieEndpoint.java
清单 3-4 显示了MovieEndpoint.java服务激活器。让我们来看看每一部分。
-
@MessageEndpoint是一个注释,它告诉 Spring Integration 该类必须被视为任何其他输出通道访问的端点。 -
@ServiceActivator是对一个方法的注释,告诉 Spring Integration 该方法是消息到达和处理的入口点。以下是它的参数。-
File是java.io.File类。<int-file:inbound-channel-adapter />标签自动发送 java.io.File,它填写Message接口中的所有头。 -
@Headers是一个具有org.springframework.messaging.MessageHeaders类头的注释。其中两个标题是id和timestamp。
-
-
process方法是从<int:channel channel="input"/>开始监听的切入点。
接下来,您需要创建一个包含电影的小文件来测试您的第一个 Spring Integration 应用——类似于下面的代码片段(就像电影标题、演员、年份一样简单)。
The Matrix, Keanu Reeves, 1999
Memento, Guy Pearce, 2000
The Silence of the Lambs, Jodie Foster, 1991
The Prestige, Christian Bale, 2006
Disturbia, Shia LaBeouf, 2007
将文件放入target/input(在项目的根目录下)。当您使用 Maven 时,它会自动生成target文件夹。您需要创建输入文件夹并将文件放在那里。
现在该运行应用了。打开一个新的终端,从根目录的项目中,执行下面的命令行。
$ ./mvn spring-boot:run
您应该会看到以下输出。
INFO 5395 --- [ask-scheduler-1] o.s.i.file.FileReadingMessageSource : Created message: [GenericMessage [payload=target/input/movies-batch1.txt, headers={timestamp=1437355862115, id=4fe905ee-8829-e3f2-df42-f5a7512635cd}]]
INFO 5395 --- [ask-scheduler-1] c.a.spring.integration.MovieEndpoint : Movies Received:
The Matrix, Keanu Reeves, 1999
Memento, Guy Pearce, 2000
The Silence of the Lambs, Jodie Foster, 1991
The Prestige, Christian Bale, 2006
Disturbia, Shia LaBeouf, 2007
成功了。如果您想创建一个行为来监控目录中的文件,您需要考虑可重用性和可扩展性。这种与文件系统的集成已经存在于 Spring Integration 中。
您需要将文件(CSV)的内容转换成 JSON 格式。我们来看看清单 3-5 。
package com.apress.integration.movie;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Component
public class MovieService {
private static final Logger log = LoggerFactory.getLogger(MovieService.class);
public String format(String contents){
log.info("Formatting to Json...");
String json = "{}";
ObjectMapper mapper = new ObjectMapper();
try {
json = mapper.writeValueAsString(parse(contents));
log.info("\n" + json);
} catch (IOException e) {
e.printStackTrace();
}
return json;
}
private List<Movie> parse(String contents){
List<Movie> movies = new ArrayList<Movie>();
String[] record = null;
for(String line: contents.split(System.getProperty("line.separator"))){
record = Arrays.asList(line.split(",")).stream().map( c -> c.trim()).toArray( size -> new String[size]);
movies.add(new Movie(record[0],record[1],Integer.valueOf(record[2])));
}
return movies;
}
}
Listing 3-5.com.apress.integration.movie.MovieService.java
清单 3-5 显示了用@Component,注释了MovieService类,使得这个类对 Spring 容器可见,这样您就可以在您的服务激活器中使用它。让我们看看每种方法。
-
format(String contents)获取文件的内容并使用ObjectMapper类(来自 Jackson 库)通过调用私有的parse方法将它(来自电影列表)转换成 JSON 格式。 -
parse(String contents)是获取文件内容的方法。它通过剥离每一行并(用逗号)分割成实际值来进行解析。它创建一个添加到数组列表中的Movie对象。这个数组列表就是结果。这个方法使用 Java 8 streams 符号来避免值中有任何空格。清单 3-5 引入了一个
Movie类,那么我们来看看这个类(参见清单 3-6 )。
package com.apress.integration.movie;
public class Movie {
private String title;
private String actor;
private int year;
public Movie(){}
public Movie(String title, String actor, int year){
this.title = title;
this.actor = actor;
this.year = year;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getActor() {
return actor;
}
public void setActor(String actor) {
this.actor = actor;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public String toString(){
StringBuilder builder = new StringBuilder();
builder.append("Movie(title: ");
builder.append(title);
builder.append(", actor: ");
builder.append(actor);
builder.append(", year: ");
builder.append(year);
builder.append(")");
return builder.toString();
}
}
Listing 3-6.com.apress.integration.movie.Movie.java
清单 3-6 是一个基本的电影 POJO,包含这些基本字段:标题、演员和年份。让我们在服务激活器中使用这个服务(参见清单 3-7 )。
package com.apress.integration.movie;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.util.StreamUtils;
import java.io.File;
import java.io.FileInputStream;
import java.util.Map;
@MessageEndpoint
public class MovieEndpoint {
private static final Logger log = LoggerFactory.getLogger(MovieEndpoint.class);
@Autowired
private MovieService service;
@ServiceActivator
public void process(File input, @Headers Map<String,Object> headers) throws Exception {
FileInputStream in = new FileInputStream(input);
String movies = service.format(new String(StreamUtils.copyToByteArray(in)));
in.close();
log.info("Movies Received: \n" + movies);
}
}
Listing 3-7.com.apress.integration.movie.MovieEndpoint.java (version 2) Using MovieService
清单 3-7 是MovieEndpoint类的版本 2,它使用了MovieService。它使用了允许MovieService被注入并准备使用的@Autowired注释。如果您使用 Maven 或 STS IDE 运行应用,您应该会看到下面的输出。
INFO 5677 --- [ask-scheduler-1] com.apress.integration.movie.MovieService : Formatting to Json...
INFO 5677 --- [ask-scheduler-1] com.apress.integration.movie.MovieService :
[{"title":"The Matrix","actor":"Keanu Reeves","year":1999},{"title":"Memento","actor":"Guy Pearce","year":2000},{"title":"The Silence of the Lambs","actor":"Jodie Foster","year":1991},{"title":"The Prestige","actor":"Christian Bale","year":2006},{"title":"Disturbia","actor":"Shia LaBeouf","year":2007}]
这个输出是使用MovieService的结果;它以 JSON 格式生成,这是该应用的要求之一。
下一个需求是将内容发送到一个target/output/<file>.txt.processed格式的文件中。您可以在 service activator 中创建所有的逻辑,这样就大功告成了。但是对于使用 REST API 将文件发送到远程服务的需求呢?如果你考虑一下,你可以在同一个过程方法中在Service Activator类中做这两个实现。但这需要时间,对吗?多亏了 Spring Integration,这个逻辑已经作为标签存在了!让我们从将处理过的文件发送到扩展名为.processed的输出目录开始。
您需要修改movie-app-integration.xml文件,看起来像清单 3-8 。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int-file="http://www.springframework.org/schema/integration/file"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-http="http://www.springframework.org/schema/integration/http"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration/file http://www.springframework.org/schema/integration/file/spring-integration-file.xsd
http://www.springframework.org/schema/integration/http http://www.springframework.org/schema/integration/http/spring-integration-http.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd">
<!-- Spring Integration -->
<int-file:inbound-channel-adapter channel="input" directory="target/input" filename-pattern="*.txt">
<int:poller fixed-rate="500"/>
</int-file:inbound-channel-adapter>
<!-- Spring Integration: Direct Channel -->
<int:channel id="input"/>
<!-- Spring Integration: Service Activator -->
<int:service-activator id="movieProcessor" input-channel="input" ref="movieEndpoint" output-channel="output"/>
<!-- Spring Integration: Direct Channel -->
<int:channel id="output"/>
<!-- Spring Integration: File -->
<int-file:outbound-channel-adapter channel="output" directory="target/output" filename-generator-expression="headers['name'] + '.processed'" />
</beans>
Listing 3-8.movie-app-integration.xml (version 2)
清单 3-8 是movie-app-integration.xml文件的版本 2。让我们看看有什么新的。
-
<int:service-activator />有一个新属性:output-channel="output".这个属性标识消息发出的通道;在这种情况下,频道的名称是"output"。 -
<int:channel id="output"/>创建新频道。这是从服务激活器发送消息的通道。 -
<int-file:outbound-channel-adapter />获取内容(JSON 格式)并在指定目录下创建文件。使用了以下属性。-
channel="output"设置收听输入内容的频道。 -
directory="target/output"指定文件在目录中的位置。 -
filename-generator-expression="headers['name'] + '.processed'"通过检查邮件头并添加后缀来生成文件名。
-
您需要再次修改服务激活器,因为它现在需要返回一些东西。在这种情况下,<int-file:outbound-channel-adapter/>标签给你一个提示。您需要发送回一个Message实例,至少包含新文件名的头(参见清单 3-9 )。
package com.apress.integration.movie;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.messaging.Message;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.StreamUtils;
import java.io.File;
import java.io.FileInputStream;
import java.util.Map;
@MessageEndpoint
public class MovieEndpoint {
private static final Logger log = LoggerFactory.getLogger(MovieEndpoint.class);
@Autowired
private MovieService service;
@ServiceActivator
public Message<String> process(File input, @Headers Map<String,Object> headers) throws Exception {
FileInputStream in = new FileInputStream(input);
String movies = service.format(new String(StreamUtils.copyToByteArray(in)));
in.close();
log.info("Sending the JSON content to a file...");
return MessageBuilder.withPayload(movies).setHeader("name",input.getName()).setHeader("Content-Type","application/json").build();
}
}
Listing 3-9.com.apress.integration.movie.MovieEndpoint.java (version 3): Returning a Message Instance
清单 3-9 是MovieService的版本 3,从服务激活器返回一个值;在本例中,是一个org.springframework.messaging.Message<String>接口的实例。此外,它还添加了包含名称和Content-Type的头。这最后一个头对于 HTTP 请求(稍后出现)很有用。org.springframework.messaging.support.MessageBuilder实用程序类构建消息。这个类提供了一个非常漂亮流畅的 API。
如果有 STS IDE,可以看到修改配置 XML 文件后的图形结果,如图 3-8 所示。
图 3-8。
Spring 积分图(第 2 版)
运行应用后,您应该得到和以前一样的输出,但是如果您看一下target/output目录,您会发现一个新的文件名,movies.txt.processed(如果您将文件命名为:movies.txt)。内容是 JSON 格式的。
下一个需求是将这个文件发送到外部 REST API 服务。清单 3-10 是movie-app-integration.xml的版本 3。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int-file="http://www.springframework.org/schema/integration/file"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-http="http://www.springframework.org/schema/integration/http"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration/file http://www.springframework.org/schema/integration/file/spring-integration-file.xsd
http://www.springframework.org/schema/integration/http http://www.springframework.org/schema/integration/http/spring-integration-http.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd">
<!-- Spring Integration -->
<int-file:inbound-channel-adapter channel="input" directory="target/input" filename-pattern="*.txt">
<int:poller fixed-rate="500"/>
</int-file:inbound-channel-adapter>
<!-- Spring Integration: Direct Channel -->
<int:channel id="input"/>
<!-- Spring Integration: Service Activator -->
<int:service-activator input-channel="input" ref="movieEndpoint" output-channel="output"/>
<!-- Spring Integration: Direct Channel -->
<int:channel id="output"/>
<!-- Spring Integration: Router -->
<int:recipient-list-router input-channel="output">
<int:recipient channel="toFile" />
<int:recipient channel="toHttp"/>
</int:recipient-list-router>
<!-- Spring Integration: Direct Channels -->
<int:channel id="toFile"/>
<int:channel id="toHttp"/>
<!-- Spring Integration: File and Http -->
<int-file:outbound-channel-adapter channel="toFile" directory="target/output" filename-generator-expression="headers['name'] + '.processed'" />
<int-http:outbound-channel-adapter channel="toHttp" url="http://localhost:8080/v1/movies" http-method="POST"/>
</beans>
Listing 3-10.movie-app-integration.xml (version 3)
清单 3-10 是movie-app-integration.xml文件的版本 3。让我们看看有什么新的。
-
<int:recipient-list-router />是一个暴露路由器的新标签。这个例子使用output通道将内容发送到toFile和toHttp通道。 -
<int:recipient/>声明路由器要使用的通道。 -
<int:channel/>表示有两个新的直接通道(点对点):toFile和toHttp。 -
<int-http:outbound-channel-adapter/>向远程服务发出请求的新标签——您需要指向的 REST API(/v1/movies)。
基于清单 3-10 ,你的曲线图应该类似于图 3-9 。
图 3-9。
Spring 积分图
运行应用会导致一些错误,因为它正在寻找http://localhost:8080/v1/movies端点,而且还没有准备好;这是集成的下一部分。正如您所看到的,尝试实现需求的最后一部分非常耗时,但是 Spring Integration 已经拥有了这些可重用的模块(EIP 实现)。
创建电影应用:第二部分(外部)
这个应用是面向创建一个 REST API,做数据库插入,并发送电子邮件。您可以使用相同的 URL 来生成项目( http://start.spring.io )或者使用 STS IDE 来生成相同的模板。
以下是该项目的新字段值(见图 3-10 )。
图 3-10。
spring Initializr athttps://start . spring . io
-
组:
com.apress.integration -
神器:
movie-web -
依赖:Spring Web,Spring Integration,Java 邮件发送器,JDBC API,H2 数据库
生成项目后,将其解压缩并导入到您喜欢的 IDE 中。您应该拥有清单 3-11 中所示的pom.xml。(如有必要,请修改以匹配清单 3-11 。)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.apress.integration</groupId>
<artifactId>movie-web</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>movie-web</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Integration -->
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mail</artifactId>
</dependency>
<!-- JDBC -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Http -->
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-http</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Listing 3-11.pom.xml
清单 3-11 是您将要使用的pom.xml。看一看 Spring Integration 和 JDBC 部分。你需要加上spring-integration-jdbc、spring-integration-mail、spring-integration-http,和h2。前两个依赖项添加了名称空间,以便在解决方案的这一部分使用特殊的标记。
Spring Boot MVC 电影应用第二部分
让我们看看使用 Spring MVC 组件有多容易。在本节中,您将创建com.apress.integration.movieweb.MovieController.java类并重用Movie类(参见清单 3-6 )。MovieController类必须具有清单 3-12 中所示的内容。
package com.apress.integration.movieweb;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MovieController {
private static final Logger log = LoggerFactory.getLogger(MovieController.class);
@RequestMapping(method=RequestMethod.POST,value="/v1/movie")
public ResponseEntity<String> movie(@RequestBody Movie body){
log.info("Movie: " + body);
return new ResponseEntity<String>(HttpStatus.ACCEPTED);
}
@RequestMapping(method=RequestMethod.POST,value="/v1/movies")
public ResponseEntity<String> movies(@RequestBody Movie[] body){
for (Movie movie: body){
log.info("Movie: " + movie);
}
return new ResponseEntity<String>(HttpStatus.ACCEPTED);
}
}
Listing 3-12.com.apress.integration.movieweb.MovieController.java
清单 3-12 显示了MovieController类。由于其注释,此类被视为 web 控制器。Spring Boot 注册这个控制器和任何暴露的 URL 映射;在这种情况下,“/v1/movie”和“/v1/movies”(一个是单数,一个是复数)。我们来看看细节。
-
@RestController。该注释是 Spring Boot 的标记,因此它被注册为入口休息点。 -
@RequestMapping。该注释声明了 REST API、接受传入请求的方法以及接受这些请求的路径。这个注释必须放在作为请求处理程序的方法中。您也可以使用@GetMapping注释,这是一种处理 GET HTTP 请求的简单方法。 -
@RequestBody。两个处理程序中都使用了这个注释,但是看看参数——一个是单个实例,另一个是一组Movie实例。每次有请求时,Spring MVC 负责自动将每个 JSON 请求转换成正确的实例;在这种情况下,Movie的实例。(Movie类与第一部分中的相同)。 -
ResponseEntity<String>。每个处理程序返回一个 ResponseEntity 状态;在本例中,是一个带有 HTTP 状态代码202的字符串Accepted。
现在,您可以通过执行以下命令在终端中运行 web 应用。
$ ./mvnw spring-boot:run
您可以做一个小测试,验证您的 REST API 正在运行。例如,您可以打开一个终端并使用cURL命令,如下面的代码片段所示,并获得相同的输出。
$ curl -i -H "Content-Type:application/json" -X POST -d '[{"title":"The Matrix","actor":"Keanu Reeves","year":1999},{"title":"Memento","actor":"Guy Pearce","year":2000}]' http://localhost:8080/v1/movies
HTTP/1.1 202 Accepted
Server: Apache-Coyote/1.1
Content-Length: 0
您将获得 202 Accepted 显示,并且在运行 web 应用的日志中,您将看到类似于以下输出的内容。
INFO 8052 --- [.16-8080-exec-3] c.apress.integration.movieweb.MovieWebController : Movie: Movie(title: The Matrix, actor: Keanu Reeves, year: 1999)
INFO 8052 --- [.16-8080-exec-3] c.apress.integration.movieweb.MovieWebController : Movie: Movie(title: Memento, actor: Guy Pearce, year: 2000)
好了,您刚刚创建了 REST API 服务,但是您遗漏了其他需求。将电影保存到 JDBC,并发送有关新保存的电影的电子邮件。在 rest 控制器的 handler 方法中,您可以添加执行 JDBC 和发送电子邮件的逻辑,但主要目的是为了了解 Spring Integration 的强大功能。
您可以运行电影应用(来自第一部分)来看看它是如何交流的。您应该会在MovieWeb控制台日志中看到所有打印出来的电影。
通过声明性 XML 的 Spring Integration:第二部分
接下来,让我们创建清单 3-13 中所示的 Spring bean 上下文文件。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-http="http://www.springframework.org/schema/integration/http"
xmlns:int-mail="http://www.springframework.org/schema/integration/mail"
xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:int-jdbc="http://www.springframework.org/schema/integration/jdbc"
xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/integration/jdbc http://www.springframework.org/schema/integration/jdbc/spring-integration-jdbc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration/http http://www.springframework.org/schema/integration/http/spring-integration-http.xsd
http://www.springframework.org/schema/integration/mail http://www.springframework.org/schema/integration/mail/spring-integration-mail.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd">
<!-- Spring Integration -->
<!-- Spring Integration: Http -->
<int-http:inbound-channel-adapter id="movie"
supported-methods="POST" channel="input" path="/v2/movie"
request-payload-type="com.apress.integration.movieweb.Movie"
status-code-expression="T(org.springframework.http.HttpStatus).ACCEPTED"
/>
<int-http:inbound-channel-adapter id="movies"
supported-methods="POST" channel="input" path="/v2/movies"
request-payload-type="com.apress.integration.movieweb.Movie[]"
status-code-expression="T(org.springframework.http.HttpStatus).ACCEPTED"
/>
<!-- Spring Integration: Execution Channel -->
<int:channel id="input"/>
<!-- Spring Integration Service Activator. -->
<int:service-activator input-channel="input" ref="movieEndpoint" />
</beans>
Listing 3-13.src/main/resources/META-INF/spring/movie-webapp-integration.xml
清单 3-13 是要使用的 XML 配置文件。这个文件应该创建在src/main/resources/META-INF/spring目录下。
Note
所有源代码都在 Apress 网站上(参见 www.apress.com 的源代码/下载选项卡)。可以复制/粘贴。
看看下面的名称空间。即使您现在不会使用它们,但很快就会用到。
-
xmlns:int-mail展示了一些对电子邮件操作有用的标签。 -
xmlns:jdbc对 JDBC 有用。
让我们回顾一下清单 3-13 ,看看有什么新内容。
-
<int-http:inbound-channel-adapter/>。即使您已经创建了MovieController类,这个标签也会创建相同的行为。它使用版本 2 创建了两个端点:"/v2/movie"和"/v2/movies"。这些属性如下。-
supported-methods="POST"告知端点仅接受 POST 请求。 -
channel="input"指定请求消息发送到哪里。 -
path="/v2/movie"是请求映射路径。定义了两个:一个用于单个电影,另一个用于电影集合。 -
request-payload-type="com.apress.integration.movieweb.Movie"类似于声明@RequestBody的注释。Spring MVC 自动将 JSON 格式转换成Movie对象。对于电影集合,您声明了Movie[]数组。 -
status-code-expression="T(org.springframework.http.HttpStatus).ACCEPTED"显示了类似于ResponseEntity<String>类型的响应类型。
-
-
<int:channel id="input"/>创建一个生产和消费一个Message类型的通道。 -
<int:service-activator input-channel="input" ref="movieEndpoint" />公开类的名称,在该类中,您将该端点的处理程序声明为服务激活器。
您可以使用 XML 配置文件以编程方式创建相同的 REST API 端点(如MovieWebController类),也可以不使用任何代码(声明性的)。
这个 XML 配置文件的结果图如图 3-11 所示。
图 3-11。
Spring 积分图第二部分(第 1 版)
Spring Integration 开发:第二部分
清单 3-14 是一个服务激活器组件,即MovieEndpoint类。
package com.apress.integration.movieweb;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.messaging.handler.annotation.Headers;
import java.util.Map;
@MessageEndpoint
public class MovieEndpoint {
private static final Logger log = LoggerFactory.getLogger(MovieEndpoint.class);
@ServiceActivator
public void processMovie(Movie movie, @Headers Map<String,Object> headers) throws Exception {
log.info("Movie: " + movie);
}
@ServiceActivator
public void processMovies(Movie[] movies, @Headers Map<String,Object> headers) throws Exception {
for (Movie movie: movies){
log.info("Movie: " + movie);
}
}
}
Listing 3-14.com.apress.integration.movieweb.MovieEndpoint.java
清单 3-14 显示了消息端点。该类公开了两个服务激活器处理程序。当消息到达输入通道时,Spring Integration 决定选择哪一个。
接下来,创建MovieConfiguration类,因为您需要包含带有@ImportResource注释的 XML 文件(参见清单 3-15 )。
package com.apress.integration.movieweb;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
@Configuration
@ImportResource("META-INF/spring/movie-webapp-integration.xml")
public class MovieConfiguration {
}
Listing 3-15.MovieWebApplication (version 2)
现在,您可以运行应用了。现在您应该有四个 REST 端点——两个由MovieController类(/v1/movie和/v1/movies)处理请求,另外两个由 Spring Integration 及其<int-http:inbound-channel-adapter/>标记(/v2/movie和/v2/movies)处理请求。
可以用 cURL 测试一下。
$ curl -i -H "Content-Type:application/json" -X POST -d '[{"title":"The Matrix","actor":"Keanu Reeves","year":1999},{"title":"Memento","actor":"Guy Pearce","year":2000}]' http://localhost:8080/v2/movies
HTTP/1.1 202 Accepted
Server: Apache-Coyote/1.1
Content-Length: 0
再次注意,您使用的是 API 的版本 2。
接下来,继续第二部分的需求,有必要添加一种方法来将电影存储在数据库中,然后,在这之后,发送电子邮件。一种方法是重用服务激活器处理程序,并创建一个公共函数,将电影存储在数据库中。为此,您可以使用常规的 JDBC 代码、Spring 提供的 JDBCTemplate 或 Hibernate with JPA。
还是用 Spring Integration 吧!它有一个组件可以直接插入到数据库中(参见清单 3-16 )。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-http="http://www.springframework.org/schema/integration/http"
xmlns:int-mail="http://www.springframework.org/schema/integration/mail"
xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:int-jdbc="http://www.springframework.org/schema/integration/jdbc"
xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/integration/jdbc http://www.springframework.org/schema/integration/jdbc/spring-integration-jdbc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration/http http://www.springframework.org/schema/integration/http/spring-integration-http.xsd
http://www.springframework.org/schema/integration/mail http://www.springframework.org/schema/integration/mail/spring-integration-mail.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd">
<!-- Spring Integration -->
<!-- Spring Integration: Http -->
<int-http:inbound-channel-adapter id="movie"
supported-methods="POST" channel="input" path="/v2/movie"
request-payload-type="com.apress.integration.movieweb.Movie"
status-code-expression="T(org.springframework.http.HttpStatus).ACCEPTED"
/>
<int-http:inbound-channel-adapter id="movies"
supported-methods="POST" channel="input" path="/v2/movies"
request-payload-type="com.apress.integration.movieweb.Movie[]"
status-code-expression="T(org.springframework.http.HttpStatus).ACCEPTED"
/>
<!-- Spring Integration: Execution Channel -->
<int:channel id="input"/>
<!-- Spring Integration: JDBC -->
<int-jdbc:outbound-channel-adapter
query="insert into movies (title, actor, year) values ( :payload.title, :payload.actor, :payload.year)"
data-source="dataSource" channel="input" />
</beans>
Listing 3-16.movie-web-app-integration.xml version 2
清单 3-16 显示了movie-webapp-integration.xml的版本 2。注意不再有服务激活器,这意味着你可以直接从http:inbound标签将电影传入数据库。
<int-jdbc:outbound-channel-adapter />允许您连接到任何 JDBC 兼容的数据库。(NoSQL 也有一个!)以下是属性。-
query设置 SQL 语句;这个例子非常简单,但是你也可以进行更新和删除。 -
data-source是数据源的名称,通常,它是一个 bean,包含连接到数据库所需的所有信息,如用户名、密码、URL 等等。 -
channel是标签获取信息的地方。
-
你应该有一个如图 3-12 所示的积分图。
图 3-12。
积分图
图 3-11 与图 3-12 有何不同?您只需移除服务激活并插入 JDBC 组件。
在尝试运行 web 应用之前,必须确保声明了 SQL 驱动程序,因为其中一个依赖项是 H2。您可以添加一个不同的引擎,但是您需要确保您的数据库引擎已经启动并且正在运行。
因为您使用的是 H2,所以 Spring Boot 使用嵌入式数据库默认设置。它有一个内存数据库。如果您使用任何其他数据库引擎,请相应地修改application.properties中的属性。
您可以将清单 3-17 添加到 application.properties 文件中,以查看数据是否被写入 H2 数据库引擎。
# H2 Web Console
spring.h2.console.enabled=true
# External DataSource - MySQL
#spring.datasource.url=jdbc:mysql://localhost/testdb
#spring.datasource.username=scdf
#spring.datasource.password=scdf
#spring.datasource.driver-class-name=com.mysql.jdbc.Driver
Listing 3-17.src/main/resources/application.properties
清单 3-17 显示了application.properties文件的内容。我注释了一些DataSource属性,这样您就可以看到您能够使用 MySQL 或任何其他 DB 引擎。添加spring.datasource属性会创建dataSource bean(您在<int-jdbc:outbound-channel-adapter/>标签的数据源中设置的值)。请记住添加正确的数据库访问凭据,如果使用不同的数据库引擎,请更改驱动程序类。现在,只使用 H2。
接下来,您需要创建一个模式来帮助 JDBC 适配器发送数据。Spring Boot 可以自动生成表和数据库,但是你需要使用不同的引擎,比如 Hibernate/JPA。这个例子使用了 JDBC 驱动程序(没有 JPA),所以您需要提供一个包含表创建的src/main/resources/schema.sql文件(参见清单 3-18 )。
create table IF NOT EXISTS movies(
id int not null auto_increment,
title varchar(250),
actor varchar(100),
year smallint,
primary key(id));
Listing 3-18.src/main/resources/schema.sql
清单 3-18 是一个非常基本的表,其中声明了主要字段。
现在,您可以运行应用,并使用 cURL 函数,如下一个代码片段所示。
$ curl -i -H "Content-Type:application/json" -X POST -d '{"title":"The Matrix","actor":"Keanu Reeves","year":1990}' http://localhost:8080/v2/movie
HTTP/1.1 202 Accepted
Server: Apache-Coyote/1.1
Content-Length: 0
打开浏览器,指向http://localhost:8080/h2-console/打开 H2 控制台(参见图 3-13 )。
图 3-13。
您应该能够使用 URL jdbc:h2:mem:testdb、用户名sa和一个空密码进行连接。单击连接。图 3-14 显示了一个已定义的电影表,其中包括您发布的记录。
图 3-14。
H2 控制台
你注意到了吗,curl命令是使用/v2/movie路径执行的,并且只发布了一部电影。怎么能一个电话就把电影集贴出来插上呢?有许多方法可以实现这一点。例如,使用一个处理集合的服务激活器(就像你在清单 3-14 中看到的)通过使用另一个连接到<int-jdbc:outbout-channel-adapter/>标签的直接通道发送每部电影。考虑一下,如果您需要停止插入,然后恢复或安排特定的日期和时间,会发生什么情况。使用 Spring Integration 有点复杂,对吗?好的方面是有办法做这种任务,但那是你的功课。别担心。给你个提示——春批!
下一个要求是一旦电影/电影存储发送电子邮件。让我们从movie-webapp-integration.xml的最终版本开始(见清单 3-19 )。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-http="http://www.springframework.org/schema/integration/http"
xmlns:int-mail="http://www.springframework.org/schema/integration/mail"
xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:int-jdbc="http://www.springframework.org/schema/integration/jdbc"
xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/integration/jdbc http://www.springframework.org/schema/integration/jdbc/spring-integration-jdbc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration/http http://www.springframework.org/schema/integration/http/spring-integration-http.xsd
http://www.springframework.org/schema/integration/mail http://www.springframework.org/schema/integration/mail/spring-integration-mail.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd">
<!-- Spring Integration -->
<!-- Spring Integration: Http -->
<int-http:inbound-channel-adapter id="movie"
supported-methods="POST" channel="publisher" path="/v2/movie"
request-payload-type="com.apress.integration.movieweb.Movie"
status-code-expression="T(org.springframework.http.HttpStatus).ACCEPTED"
/>
<int-http:inbound-channel-adapter id="movies"
supported-methods="POST" channel="publisher" path="/v2/movies"
request-payload-type="com.apress.integration.movieweb.Movie[]"
status-code-expression="T(org.springframework.http.HttpStatus).ACCEPTED"
/>
<!-- Publish/Subscribe Channel -->
<int:publish-subscribe-channel id="publisher" />
<!-- Spring Integration: JDBC -->
<int-jdbc:outbound-channel-adapter id="jdbc"
query="insert into movies (title, actor, year) values ( :payload.title, :payload.actor, :payload.year)"
data-source="dataSource" channel="publisher" order="1" />
<!-- Spring Integration: Mail -->
<int-mail:outbound-channel-adapter channel="mail" mail-sender="mailSender" />
<int:service-activator input-channel="publisher" output-channel="mail" ref="movieMailEndpoint" order="2" />
<int:channel id="mail">
<int:dispatcher task-executor="taskExecutor"/>
</int:channel>
<!-- More definitions in the next section ... -->
</beans>
Listing 3-19.src/main/resources/META-INF/spring/movie-webapp-integration.xml (version 3)
清单 3-19 显示的是movie-webapp-integration.xml的最终版本。什么是新的?
-
使用发布-订阅模式创建一个频道。它按照指定的顺序将消息发送给所有订阅者(这是通过订阅者添加
order属性来完成的)。 -
<int-jdbc:outbound-channel-adapter/>存储电影,现在是publisher频道的订户。让我们看看属性和它们的值。-
channel="publisher"是发布者频道的订阅者。 -
order="1"先收到消息。
-
-
<int:service-activator/>是一个新的服务激活器,它创建了要交付给mail通道的MailMessage实例。以下是它的属性。-
ref="movieMailEndpoint"是 bean 引用类。 -
order="2"表示服务激活器接收到存储后的消息。 -
output-channel="mail"是发送MailMessage实例的通道的名称。
-
-
<int-channel>是接收MailMessage实例的新通道(执行者通道)。这个通道通过任务执行器执行,这意味着它可以是一个异步调用。以下是它的属性。-
<int:dispatcher/>声明使异步调用成为可能的类。 -
task-executor="taskExecutor"是任务执行器类的 bean 实现的名称。
-
-
<int-mail:outbound-channel-adapter/>是一个新标签,它根据mailSender属性中给出的参数发送电子邮件(在application.properties文件中)。mail-sender="mailSender"是豆子的名字。Spring 容器基于查看email.<props>的application.properties文件生成这个 bean。
最终版本的 Spring Integration 图如图 3-15 所示。
图 3-15。
Spring 积分图
清单 3-19 显示了您对两个 beans 的依赖:taskExecutor和emailSender。将以下 bean 声明添加到movie-webapp-integration.xml(参见清单 3-20 )。
<!-- Mail Properties -->
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
<property name="host" value="${email.host}" />
<property name="port" value="${email.port}" />
<property name="username" value="${email.account.name}" />
<property name="password" value="${email.account.password}" />
<property name="javaMailProperties">
<props>
<prop key="mail.smtp.starttls.enable">true</prop>
<prop key="mail.smtp.auth">true</prop>
</props>
</property>
</bean>
<!-- Helpers -->
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="5" />
<property name="maxPoolSize" value="10" />
<property name="queueCapacity" value="25" />
</bean>
<!-- JSON Converter -->
<bean id="jsonConverter"
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="supportedMediaTypes" value="application/json" />
</bean>
Listing 3-20.src/main/resources/META-INF/spring/movie-webapp-integration.xml (extra declaration dependencies)
清单 3-20 显示了需要添加到movie-web app-integration . XML文件中的mailSender bean 定义和taskExecutor定义。taskExecutor bean 定义了ThreadPoolTaskExecutor类,作为发送电子邮件的异步处理程序,所以在服务器响应之前,您不需要这样做。
拥有连接到您选择的任何 SMTP 提供者的所有必要信息,并且它提供了一种将值公开为属性的方法。这些值在src/main/resources/application.properties文件中(见清单 3-21 )。
# H2 Web Console
spring.h2.console.enabled=true
# External DataSource - MySQL
#spring.datasource.url=jdbc:mysql://localhost/springxd
#spring.datasource.username=springxd
#spring.datasource.password=springxd
#spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#spring.datasource.initialize=true
email.account.name=myuser@mydomain.com
email.account.password=mypassword
email.host=smtp.gmail.com
email.port=587
Listing 3-21.src/main/resources/application.properties file (final version)
清单 3-21 中显示的属性使用 Gmail SMTP 服务提供商。清单 3-22 ( movie-webapp-integration.xml)定义了服务激活器中movieWebMailEndpoint的引用。清单 3-22 展示了这个类。
package com.apress.integration.movieweb;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.mail.MailMessage;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.messaging.handler.annotation.Headers;
import java.util.Date;
import java.util.Map;
@MessageEndpoint
public class MovieMailEndpoint {
private static final Logger log = LoggerFactory.getLogger(MovieMailEndpoint.class);
@ServiceActivator
public MailMessage process(Movie movie, @Headers Map<String,Object> headers) throws Exception {
log.info("Movie: " + movie);
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setTo("myuser@mydomain.com");
mailMessage.setSubject("A new Movie is in Town");
mailMessage.setSentDate(new Date(0));
mailMessage.setText(movie.toString());
return mailMessage;
}
}
Listing 3-22.com.apress.spring.integration.MovieWebMailEndpoint.java
清单 3-22 显示了Message端点和服务激活器处理器。这个特殊的处理程序返回<int-mail:outbound-channel-adapter/>标签所需的MailMessage实例。这是您添加电子邮件的收件人、主题和文本的地方。处理程序正在使用Movie对象和Message实例上的头。
您可以运行最后一个测试来查看电影是否已存储,然后将邮件放入收件箱。确保设置了正确的属性,并且不会有任何问题。请记住,本章中的所有代码都可以从 Apress 网站下载。
现在是运行这两部分的时候了。您可以使用 Maven(在单独的窗口终端中),或者您可以选择运行您最喜欢的 IDE 来查看完整的流程!
Note
如果您正在运行 STS 并且想要运行这两个项目,您需要在菜单中禁用 Live Bean 支持特性:运行➤运行配置,选择配置,并取消选中 Live Bean 支持特性。
恭喜你!您刚刚创建了一个集成两个系统的 Spring Integration 应用。最后你在里面没用多少代码。Spring Integration 完成了大部分繁重的工作。尽管这是一个简单的例子,但您知道如何集成 JMS、RabbitMQ 或 SOAP 等系统,或者如何集成脸书或 Twitter 等社交媒体平台。
摘要
本章向您介绍了 Spring Integration。您看到了 Spring Integration 组件以及它如何依赖于消息、通道和端点。您看到了一些集成模式,以及 Spring Integration 如何促进了它们的使用。
您已经通过一个例子了解了如何使用 Spring Integration。此外,您看到了用几乎零代码创建应用并将其与其他组件或本地/外部系统集成是多么容易。
为什么 Spring Integration 很重要?Spring CloudStream 基于 Spring Integration,这使得它非常可靠、可伸缩且易于扩展。如果你想了解更多关于 Spring Integration 的知识,Apress 有非常好的标题。