本文是我们名为“ Spring Integration for EAI ”的学院课程的一部分。
在本课程中,向您介绍了企业应用程序集成模式以及Spring Integration如何解决它们。 接下来,您将深入研究Spring Integration的基础知识,例如通道,转换器和适配器。 在这里查看 !
目录
-
1.简介 2.系统概述 3.埃及剧院服务 4. Pantages剧院服务 5.用户界面 6.记录每个用户请求 7.丢弃无效的条目 8.选择要请求的剧院 9.请求埃及剧院 10.请求潘太及斯剧院 11.处理错误
-
-
11.1。 将消息存储到数据库 11.2。 向负责人发送电子邮件
12.关闭应用程序 13.看一看完整的流程 14.技术版本
1.简介
本教程将详细介绍一个应用程序的完整示例,该应用程序使用Spring Integration提供的多个组件来为其用户提供服务。 该服务由一个系统提示用户选择不同的剧院组成。 选择后,系统将向所选剧院的外部系统发出请求,并返回其可用电影的列表。 每个电影院通过不同的API提供服务; 我们将在解释每个外部系统的部分(第三部分和第四部分)中看到这一点。
2.系统概述
以下活动图显示了系统的高级视图。
- 用户界面 :在图的左侧,我们开始了流程; 请求用户条目。 系统显示的该进入请求以及对用户的系统响应都是与流集成的示例。
- Web服务调用 :根据用户的选择,系统将从另一个外部系统检索影片列表。 埃及剧院通过HTTP公开其服务,而潘塔基斯剧院则通过SOAP公开其服务。
- 错误处理 :如果在流程中出现错误,可能是由于意外异常或Web服务不可用,系统会将信息发送到另外两个外部系统:noSQL数据库( MongoDB )和电子邮件地址。
下一部分将更深入地介绍该系统的每个部分。
3.埃及剧院服务
埃及剧院系统通过HTTP公开他的服务。 在本节中,我们将快速浏览该应用程序。 这是一个包含RESTful Web服务的Spring 4 MVC应用程序。
控制器将请求检索剧院中放映的所有可用电影:
@RestController
@RequestMapping(value="/films")
public class FilmController {
FilmService filmService;
@Autowired
public FilmController(FilmService service) {
this.filmService = service;
}
@RequestMapping(method=RequestMethod.GET)
public Film[] getFilms() {
return filmService.getFilms();
}
}
API很简单,并且在此示例中,服务将返回一些虚拟值,因为本节的重点是仅提供有关调用外部系统的更多细节:
@Service("filmService")
public class FilmServiceImpl implements FilmService {
@Override
public Film[] getFilms() {
Film film1 = new Film(1, "Bladerunner", "10am");
Film film2 = new Film(2, "Gran Torino", "12pm");
return new Film[]{film1, film2};
}
}
Spring配置基于注释:
web.xml文件配置Web应用程序:
contextConfigLocation
classpath:xpadro/spring/mvc/config/root-context.xml
org.springframework.web.context.ContextLoaderListener
springServlet
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:xpadro/spring/mvc/config/app-context.xml
springServlet
/spring/*
因此,系统将处理发送到http://localhost:8080/rest-films/spring/films
请求,其中rest-films
是应用程序的上下文路径。
4. Pantages剧院服务
Pantages Theatre服务通过SOAP公开其服务。 像埃及剧院一样,它包含在Web应用程序中,但在这种情况下,它是通过Spring Web Services实现的 。
该终结filmRequest
使用名称空间http://www.xpadro.spring.samples.com/films
服务于filmRequest
请求。 响应是从电影服务收到的结果中构建的:
@Endpoint
public class FilmEndpoint {
@Autowired
private FilmService filmService;
@PayloadRoot(localPart="filmRequest", namespace="http://www.xpadro.spring.samples.com/films")
public @ResponsePayload FilmResponse getFilms() {
return buildResponse();
}
private FilmResponse buildResponse() {
FilmResponse response = new FilmResponse();
for (Film film : filmService.getFilms()) {
response.getFilm().add(film);
}
return response;
}
}
电影服务也是虚拟服务,它将返回一些默认值:
@Service
public class FilmServiceImpl implements FilmService {
@Override
public List getFilms() {
List films = new ArrayList<>();
Film film = new Film();
film.setId(new BigInteger(("1")));
film.setName("The Good, the Bad and the Uggly");
film.setShowtime("6pm");
films.add(film);
film = new Film();
film.setId(new BigInteger(("2")));
film.setName("The Empire strikes back");
film.setShowtime("8pm");
films.add(film);
return films;
}
}
Spring配置如下所示:
最后, web.xml
文件:
contextConfigLocation
classpath:xpadro/spring/ws/config/root-config.xml
org.springframework.web.context.ContextLoaderListener
Films Servlet
org.springframework.ws.transport.http.MessageDispatcherServlet
contextConfigLocation
classpath:xpadro/spring/ws/config/servlet-config.xml
1
Films Servlet
/films/*
根据此配置,Pantages Theatre应用程序将处理发送到http://localhost:8080/ws-films/films
请求,其中ws-films
是应用程序的上下文路径。
5.用户界面
一旦我们了解了将与Spring Integration应用程序进行通信的外部系统是什么,让我们继续看一下如何构建此应用程序。
独立应用程序从引导Spring上下文的主要方法开始,该方法包含我们所有的集成组件。 接下来,它提示用户输入他的选择:
public class TheaterApp {
private static Logger logger = LoggerFactory.getLogger("mainLogger");
static AbstractApplicationContext context;
public static void main(String[] args) {
context = new ClassPathXmlApplicationContext("classpath:xpadro/spring/integration/config/int-config.xml");
context.registerShutdownHook();
logger.info("\\nSelect your option (1-Egyptian Theater / 2-Pantages Theater / 0-quit):\\n");
}
public static void shutdown() {
logger.info("Shutting down...");
context.close();
}
}
该应用程序还实现了一种关闭方法,该方法将由用户调用。 我们将在后面的部分中更详细地介绍这一点。
好的,现在用户选择将如何进入消息传递系统? 这是与流集成起作用的地方。 消息传递系统的系统条目是使用入站通道适配器实现的,该适配器将从stdin( System.in
)中读取。
通过使用轮询器,入站通道适配器将尝试每秒从System.read中读取并将结果放入systemEntry
通道。
现在,我们收到了一条带有用户条目作为其有效负载的Spring Integration消息。 我们可以使用端点来转换数据并将其发送到所需的系统。 示例:控制台将提示用户在不同的剧院之间进行选择:
2014-04-11 13:04:32,959|AbstractEndpoint|started org.springframework.integration.config.ConsumerEndpointFactoryBean#7
Select your option (1-Egyptian Theater / 2-Pantages Theater / 0-quit):
6.记录每个用户请求
系统要做的第一件事是记录用户选择。 这是通过使用丝锥完成的 。 此窃听是拦截器的实现,该拦截器将拦截通过通道(在我们的情况下为systemEntry
通道)传播的消息。 它不会改变流量; 该消息将继续到达其目的地,但有线分流器还将其发送到另一个通道,通常用于监视。
在我们的应用程序中,该消息还将发送到日志记录通道适配器。
日志记录通道适配器由LoggingChannelAdapterParser
实现。 它基本上创建了一个LoggingHandler
,它将使用Apache Commons Logging库记录消息的有效负载。 如果要记录完整消息而不是仅记录其有效负载,则可以将log-full-message
属性添加到log-full-message
记录通道适配器。
示例:日志显示用户所做的选择
Select your option (1-Egyptian Theater / 2-Pantages Theater / 0-quit):
1
2014-04-11 13:06:07,110|LoggingHandler|User selection: 1
7.丢弃无效的条目
看一眼用户提示,我们看到系统接受三个有效条目:
logger.info("\\nSelect your option (1-Egyptian Theater / 2-Pantages Theater / 0-quit):\\n")
无效条目的处理非常简单; 系统将过滤无效条目,以防止它们在流程中向前移动。 这些丢弃的消息然后将被发送到discards
通道。
订阅invalidEntries
丢弃通道,还有另一个流通道适配器,在这种情况下,是出站适配器:
该适配器的功能是写入stdout( System.out
),因此用户将收到用户在控制台上输入了无效请求的信息。
要记住的一件事是我们没有创建invalidEntries
通道。 适配器将通过匿名临时直接通道连接到过滤器。
示例:控制台显示用户输入了无效的选择。
Select your option (1-Egyptian Theater / 2-Pantages Theater / 0-quit):
8
2014-04-11 13:07:41,808|LoggingHandler|User selection: 8
Invalid entry: 8
8.选择要请求的剧院
成功通过上一个过滤器的有效条目将被发送到路由器:
该路由器负责确定用户需要哪个外部系统的信息。 它还将检测用户何时要关闭该应用程序:
@Component("cinemaRedirector")
public class CinemaRedirector {
private static final String CINEMA_EGYPTIAN_CHANNEL = "egyptianRequestChannel";
private static final String CINEMA_PANTAGES_CHANNEL = "pantagesRequestChannel";
private static final String QUIT_REQUEST_CHANNEL = "quitRequestChannel";
@Router
public String redirectMessage(Message msg) {
String payload = msg.getPayload();
if ("1".equals(payload)) {
return CINEMA_EGYPTIAN_CHANNEL;
}
else if ("2".equals(payload)) {
return CINEMA_PANTAGES_CHANNEL;
}
return QUIT_REQUEST_CHANNEL;
}
}
因此,这里的流程分为三个不同的通道,每个剧院的请求和完成流程的请求。
9.请求埃及剧院
为了与埃及剧院系统进行通信,我们需要发送一个HTTP请求。 我们通过使用HTTP出站网关来完成此任务。
该网关配置了几个属性:
-
expected-response-type
:Web服务返回的返回类型将是一个字符串,其中包含带有电影列表的JSON。 -
http-method
:我们正在发出GET请求。 -
charset
:用于将有效载荷转换为字节的字符集。
收到响应后,我们将使用转换器将返回的JSON转换为Java对象。 在我们的例子中,我们将响应转换为Film
数组:
接下来,服务激活器将遍历数组并构建更适合用户的String。
实现如下所示:
@Component("restResponseHandler")
public class RestResponseHandler {
private static final String NEW_LINE = "\\n";
@ServiceActivator
public String handle(Message msg) {
Film[] films = msg.getPayload();
StringBuilder response = new StringBuilder(NEW_LINE);
if (films.length > 0) {
response.append("Returned films:" + NEW_LINE);
}
else {
response.append("No films returned" + NEW_LINE);
}
for (Film f:films) {
response.append(f.getName()).append(NEW_LINE);
}
return response.toString();
}
}
最后,我们将通过在控制台上打印响应显示给用户。 我们正在使用与用于向用户显示其无效条目的通道适配器相同的通道适配器。 适配器将写入System.out:
下一个代码段显示了完整的请求。 由于我不想为这些端点之间的每次交互创建消息通道,因此我使用了消息处理程序链。 当我们在序列中有多个端点时,这种类型的端点简化了所需的XML配置。 通过使用消息处理程序链,其所有端点都通过匿名直接通道连接。
示例:向用户显示了埃及剧院的电影列表。
1
2014-04-11 14:26:20,981|LoggingHandler|User selection: 1
Returned films:
Bladerunner
Gran Torino
10.请求潘太及斯剧院
我们将需要一个Web服务网关来与Pantages Theatre系统进行交互,但是首先,我们必须构建一个filmRequest
类型的请求对象,以便由埃及Web服务端点进行服务。
我们正在使用转换器将消息更改为电影Web服务请求:
实现:
@Component("soapRequestTransformer")
public class SoapRequestTransformer {
@Transformer
public Message> createRequestMessage(Message msg) {
return MessageBuilder.withPayload(new FilmRequest()).copyHeadersIfAbsent(msg.getHeaders()).build();
}
}
我们获得了请求对象,因此我们现在可以使用Web服务网关来调用Web服务:
如上一教程中所述,将需要编组器来转换请求和响应。 对于此任务,我们将使用oxm名称空间:
当我们收到Web服务响应时,它将采用FilmResponse的形式。 序列中的下一个端点将调整响应并返回一个String,以便在下一阶段向用户显示:
实现:
@Component("soapResponseHandler")
public class SoapResponseHandler {
private static final String NEW_LINE = "\\n";
@ServiceActivator
public String handle(Message msg) {
FilmResponse response = msg.getPayload();
StringBuilder resp = new StringBuilder(NEW_LINE);
if (response.getFilm().size() > 0) {
resp.append("Returned films:" + NEW_LINE);
}
else {
resp.append("No films returned" + NEW_LINE);
}
for (Film f : response.getFilm()) {
resp.append(f.getName()).append(NEW_LINE);
}
return resp.toString();
}
}
与埃及请求一样,该请求也以另一个流出站通道适配器结束:
由于我们还有另一个端点序列,因此我们使用消息处理程序链来最大程度地减少所需的配置量:
示例:向用户显示了Pantages Theatre电影列表。
2
2014-04-11 14:27:54,796|LoggingHandler|User selection: 2
Returned films:
The Good, the Bad and the Uggly
The Empire strikes back
11.处理错误
如果出现任何问题,我们需要以某种方式进行注册。 发生这种情况时,应用程序将执行以下两项操作:
- 将消息存储到数据库。
- 发送电子邮件到指定地址。
Spring Integration消息通道名为errorChannel
将接收消息处理程序抛出的MessagingException
类型的错误。 这个特殊频道是一个发布-订阅频道,这意味着它可以有多个订阅者。 我们的应用程序订阅了两个端点,以便执行先前指定的两个操作。
将消息存储到数据库
以下服务激活器已预订到错误通道,因此它将收到MessagingException:
这个激活器将要做的是解决将请求发送到哪个剧院,然后它将构建一个FailedMessage
对象,其中包含我们要记录的信息:
@Component("mongodbRequestHandler")
public class MongodbRequestHandler {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private TheaterResolver theaterResolver;
public FailedMessage handle(MessagingException exc) {
logger.error("Request failed. Storing to the database");
String theater = theaterResolver.resolve(exc);
FailedMessage failedMsg = new FailedMessage(new Date(), exc.getMessage(), theater);
return failedMsg;
}
}
失败的消息结构包含基本信息:
public class FailedMessage implements Serializable {
private static final long serialVersionUID = 4411815706008283621L;
private final Date requestDate;
private final String messsage;
private final String theater;
public FailedMessage(Date requestDate, String message, String theater) {
this.requestDate = requestDate;
this.messsage = message;
this.theater = theater;
}
public Date getRequestDate() {
return new Date(requestDate.getTime());
}
public String getMessage() {
return this.messsage;
}
public String getTheater() {
return this.theater;
}
}
构建消息后,我们将使用mongodb出站通道适配器将其存储到数据库中:
这部分流程的完整代码如下所示:
mongodb-config.xml
文件包含特定于MongoDB配置的信息:
示例:以下屏幕快照显示了两个失败请求后的集合,每个剧院类型一个:
向负责人发送电子邮件
订阅错误通道的另一个端点是邮件请求处理程序。
此处理程序负责创建MailMessage以便将其发送到邮件网关:
@Component("mailRequestHandler")
public class MailRequestHandler {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private TheaterResolver theaterResolver;
@ServiceActivator
public MailMessage handle(MessagingException exc) {
logger.error("Request failed. Sending mail");
MailMessage mailMsg = new SimpleMailMessage();
mailMsg.setFrom("Theater.System");
mailMsg.setTo("[email protected]");
mailMsg.setSubject("theater request failed");
String theater = theaterResolver.resolve(exc);
StringBuilder textMessage = new StringBuilder("Invocation to ").append(theater).append(" failed\\n\\n")
.append("Error message was: ").append(exc.getMessage());
mailMsg.setText(textMessage.toString());
return mailMsg;
}
}
剧院解析器检查用户选择,并返回所请求剧院的名称。
完整的配置如下:
mail-config.xml
包含Spring邮件发件人的配置:
true
true
javax.net.ssl.SSLSocketFactory
示例:邮件发送到gmail帐户:
12.关闭应用程序
出现提示时,用户可以通过输入零来决定完成应用程序的执行。 当他决定这样做时,路由器会将消息重定向到quitRequestChannel
通道(请参阅第8节)。 订阅此频道,我们已经配置了消息处理程序链:
服务激活器将创建一个ShutdownEvent
并将其返回,以便由消息处理程序链的下一个端点进行处理:
@Component("shutdownActivator")
public class ShutdownActivator {
@ServiceActivator
public ShutdownEvent createEvent(Message msg) {
return new ShutdownEvent(this);
}
}
ShutdownEvent
是ApplicationEvent
的实例。
public class ShutdownEvent extends ApplicationEvent {
private static final long serialVersionUID = -198696884593684436L;
public ShutdownEvent(Object source) {
super(source);
}
public ShutdownEvent(Object source, String message) {
super(source);
}
public String toString() {
return "Shutdown event";
}
}
事件出站通道适配器将发布为ApplicationEvent
,将发送到订阅该通道的任何消息。 这样,它们将由在应用程序上下文中注册的任何ApplicationListener
实例处理。 但是,如果消息的有效负载是ApplicationEvent
的实例,则它将按原样传递。 如前面的代码所示,我们的ShutdownEvent
是ApplicationEvent
一个实例。
侦听此类事件,我们已经注册了一个侦听器:
@Component("shutdownListener")
public class ShutdownListener implements ApplicationListener {
@Override
public void onApplicationEvent(ShutdownEvent event) {
TheaterApp.shutdown();
}
}
侦听器用于关闭应用程序。 如果您记得第五节中的关闭方法, Theater
应用程序将关闭应用程序上下文。
13.看一看完整的流程
为了简化起见,我将所有集成元素都放在同一个文件中,但是我们可以考虑将其拆分为较小的文件:
14.技术版本
对于此应用程序,我使用了Spring框架的3.2.8发行版和Spring Integration的最新3.0.2发行版。
完整的依赖项列表如下所示:
3.2.8.RELEASE
3.0.2.RELEASE
1.7.5
2.3.0
1.4.1
org.springframework
spring-context
${spring-version}
org.springframework.integration
spring-integration-core
${spring-integration-version}
org.springframework.integration
spring-integration-jms
${spring-integration-version}
org.springframework.integration
spring-integration-stream
${spring-integration-version}
org.springframework.integration
spring-integration-event
${spring-integration-version}
org.springframework.integration
spring-integration-http
${spring-integration-version}
org.springframework.integration
spring-integration-ws
${spring-integration-version}
org.springframework.integration
spring-integration-mongodb
${spring-integration-version}
org.springframework.integration
spring-integration-mail
${spring-integration-version}
javax.mail
mail
${javax-mail-version}
com.fasterxml.jackson.core
jackson-core
${jackson-version}
com.fasterxml.jackson.core
jackson-databind
${jackson-version}
org.slf4j
slf4j-api
${slf4j-version}
org.slf4j
slf4j-log4j12
${slf4j-version}
翻译自: https://www.javacodegeeks.com/2015/09/spring-integration-full-example.html