今儿咱说说消息那些事 | 从开发角度看应用架构17

前言

本文仅代表作者的个人观点;

本文的内容仅限于技术探讨,不能作为指导生产环境的素材;

本文素材是红帽公司产品技术和手册;

本文分为系列文章,将会有多篇,初步预计将会有26篇。

一、消息是干啥用的

用最直白的话说:消息是用来传递信息的。

在Java EE中,消息是在应用程序之间传递信息的。

那么,应用之间的相互访问,是否一定要通过消息?

不是。例如Java应用对应用数据的访问,通过JPA的标准,实现ORM,这种方式就不是消息。

那么,消息有啥好处?

消息的传输和接受异步的,它实现了应用之间松耦合。只要组件遵循相同的消息格式,就可以用许多不同的语言编写应用程序组件,然后通过消息进行组件之间的信息传递。

二、消息是咋传递的?

消息的传递有两种方式:

  1. 消息队列方式
  2. 订阅-发布方式

消息队列的消息传递方式是点对点的,也是是基于“拉”的方式。说白了就是:应用(消息的消费者),想获取到消息,那你就得自己(定期)去消息队列里找,看有没有。

在点对点模型中,队列使用者必须确认消息的成功处理,如果没能成功处理,需要将其重新放回要重试的队列。

而订阅-发布模式,是一对多的。我们拿定报纸举例(这个例子暴露了自己的年龄)会比较容易理解。

消息的提供者Provider,我们看成是报亭,卖报纸的。报纸有:人民日报、参考消息、环球时报、娱乐杂志,每一个杂志都是一个主题(Topic)。

而消费者,想读报纸,就去报亭那去定自己想看的主题的报纸,如娱乐杂志。

对于报亭而言,一类主题的报纸,会有很多人定。每天早上,当报纸到了报亭以后,报亭就会主动地将报纸发给订报的人。

订阅的模式有两种:持久订阅和非持久订阅。 使用持久订阅时,如果应用程序暂时断开与主题的连接,则会在应用程序断开连接时发送到主题的任何消息都会保存,并在下次持久订阅服务器重新连接时传递。而非持久订阅不会保存订户断开时收到的任何消息。

整体而言,消息传递的方式,使用消息队列的方式居多。而使用消息队列的Java应用,可以是普通的JavaBean,也可以是EJB。

三、应用的类型都有啥?

刚才我们提到,消息是用来实现应用之间的异步通讯的。

那么,应用,或者说Java应用的本质是啥?

Java应用的本质,实际上是类(class)。Java的应用又主要分为:Java SE和JavaEE。

Java SE通常用于开发独立程序、工具和实用程序。典型的qq、坦克大战游戏,都是JavaSE程序。这些程序在OS的JVM中(JDK> JRE> JVM):

Java EE规范是一组基于Java SE构建的API。它为运行多线程、事务、安全和可扩展的企业应用程序提供了运行时环境。需要注意的是,与Java SE不同,Java EE主要是API的一组标准规范,实现这些API的运行时环境通常称为应用程序服务器。也就是我们常说,传统意义上的中间件:app server。

也就说,JavaSE的程序,在windows中双击.exe可以运行,而JavaEE的程序不是这样玩的,它们是部署到App server上运行的。

Java EE中的对象大致有三类:POJO、JavaBean、EJB

POJO全称是Plain Ordinary Java Object / Pure Old Java Object,中文可以翻译成:普通Java类。

JavaBean 是一种JAVA语言写成的可重用组件。为写成JavaBean,类必须是具体的和公共的,并且具有无参数的构造器。JavaBeans 通过提供符合一致性设计模式的公共方法将内部域暴露称为属性。众所周知,属性名称符合这种模式,其他Java 类可以通过自省机制发现和操作这些JavaBean 属性。

JavaBean可分为两种:一种是有用户界面(UI,User Interface)的JavaBean;还有一种是没有用户界面,主要负责处理事务(如数据运算,操纵数据库)的JavaBean。JSP通常访问的是后一种JavaBean。

企业Java Bean(EJB)是一种Java EE组件,通常用于在企业应用程序中封装业务逻辑。EJB与Java SE中的简单Java bean不同,开发人员必须明确地实现多线程、并发、事务和安全等概念,应用程序服务器在运行时提供了这些功能,使开发人员可以专注于编写应用程序的业务逻辑。

EJB是把你编写的软件中,那些需要执行制定的任务的类,不放到客户端软件上了,而是给他打成包放到一个服务器上了"。EJB 就是将那些"类"放到一个服务器上,用C/S 形式的软件客户端对服务器上的"类"进行调用。

EJB 是运行在独立服务器上的组件,客户端是通过网络对EJB 对象进行调用的。在Java中,能够实现远程对象调用的技术是RMI,而EJB 技术基础正是RMI。通过RMI 技术,J2EE将EJB 组件创建为远程对象,客户端就可以通过网络调用EJB 对象了。

EJB主要有几类:

  • 会话 Bean(Session Bean),有分为有状态的和无状态的。
  • 消息驱动Bean(MessageDriven Bean)。

当然,我们是可以将一个POJO转化为EJB的。具体的方法参照本文的实验三。

四、JavaBean和EJB使用消息队列的区别

基于消息的EJB,我们称之为为MDB:Message Driven Bean。MDB既可以使用消息队列方式,也可以使用订阅-发布模式。

首先,JavaBean和EJB使用消息队列的时候,都是基于JMS (Java Message ServiceAPI实现的。

与Java bean不同,MDB通过依赖注入其他Bean(类),是实现其他接口的方法。MDB的所有通信都通过JMS进行。每个MDB都配置为使用受管理对象侦听特定JMS目标。

EJB Container(JBoss EAP)负责管理MDB的生命周期。应用程序服务器定义了一个MDB池,它允许并发处理消息。并发消息处理提供了消息吞吐量的实质性改进。服务器在启动时自动在池中创建MDB。当MDB正在侦听的目标收到新消息时,EJB Container会自动在其中一个预先创建的MDB实例上调用onMessage方法。所有MDB必须实现MessageListener接口的onMessage方法。 MDB完成处理后,MDB实例将返回到池中以供重用。使用MDB池可以提高应用程序性能,因为当目标接收消息时,MDB类已经实例化并准备好立即处理消息。

MDB是异步和多线程的。出于这些原因,MDB是一种更强大的Java EE应用程序解决方案,需要异步使用来自目标的消息。

五、MDB的查看队列消息侦听器接口

所有MDB都必须实现MessageListener接口。 此接口的唯一方法是onMessage,该方法将JMS消息作为参数并具有void返回类型。

以下MessageListener示例显示了一个onMessage方法,该方法检查消息类型并记录消息内容:

public class QueueListener implements MessageListener {

	private final static Logger LOGGER = Logger.getLogger(this.class.getName());

	public void onMessage(Message rcvMessage) {
		TextMessage msg = null;

		try {
			if (rcvMessage instanceof TextMessage) {
				msg = (TextMessage) rcvMessage;
				LOGGER.info("Received Message from helloWorldQueue ===> " + msg.getText());
			} else {
				LOGGER.warn("Incorrect Message Type!");
			}
		} catch (JMSException e) {
			throw new RuntimeException(e);
		}
	}
...

我们来分析一下这段代码:

MessageListener是个接口,它里面有个onMessage方法。

我们要使用onMessage方法,就必须要通过一个类(QueueListener)实现MessageListener接口中onMessage的方法体,也就是:

public class QueueListener implements MessageListener {

然后MessageListener,这个类到底做了什么事情?

它有个判断:将从消息队列获取到的信息先做类型判断,是否是字符串,是的话,LOGGER.info(记录的日志)会显示从队列获取的信息;如果不是,将会提示类型不对。

激活MDB,其实就是将它注册到EJB容器,并配置MDB用于确定要侦听的目标的受管对象。 也就说说,一个调用了onMessage方法的JavaBean,才可能是一个MDB(通过这个方法从队列接受消息)。

要激活MDB并将其与目标关联,还需要使用@MessageDriven注释。

下表总结了一些其他可用的激活配置属性:

The JNDI name of the queue or topic. This property is mandatory.

六、实验一:MDB:使用JMS创建消息传递应用程序

在本实验中,我将创建一个待办事项的应用:每次在待办事项列表应用程序中更新项目时,您将使用消息生成器将消息发送到队列。

够构建一个JMS应用程序,该应用程序使用JMS生成器将消息放入队列,并使用消息驱动Bean来侦听同一队列并将消息记录到特殊的文件中。

首先,用JBDS导入一个已经存在的maven项目:

查看分配给JBoss配置文件中为TodoListQueue定义的受管对象的JNDI名称。

使用首选文本编辑器,在/opt/eap/standalone/configuration/standalone-full.xml中打开EAP配置文件:

导航到urn:jboss:domain:messaging-activemq:1.0子系统:

我们可以看到TudoListQueue,这个队列是我们实验中要使用到的。

请注意,TodoListQueue的JNDI条目为java:jboss / jms / queue / TodoListQueue。

接下来,创建一个名为JMSClient的新的无状态EJB类,它提供一个名为sendMessage(String msg)的公共方法,以使用JMS消息生成器将消息发送到TodoListQueue。

在JBDS左侧窗格的Project Explorer选项卡中,单击messaging-lab→Java Resources→src / main / java→com.redhat.training.todo.messaging,右键单击com.redhat.training.messaging并选择 New→Class。

在“名称”字段中,输入JMSClient。 单击完成。

编辑新创建的JMSClient类,添加@Stateless注释以将其标记为可用于注入的EJB(可被别的class注入)。

接下来,更新JMSClient EJB以注入默认JMSContext,还为TodoListQueue注入受管对象,然后使用该上下文创建JMSProducer以将消息发送到队列。

注入默认JMSContext以访问默认连接工厂。

使用@Resource注释注入Todo List Queue受管对象。

实现sendMessage(String msg)方法,使用JMSProducer接口在队列上放置新消息,通过将其堆栈跟踪打印到控制台来处理任何异常。

更新ItemService以注入JMSClient EJB。 添加对ItemService类中的update()方法的调用,以使用注入的JMSClient实例在每次更新项目时发送JMS消息。

通过在JBDS左侧窗格的Project Explorer选项卡中展开messaging-lab项打开ItemService类,然后单击messaging-lab→Java Resources→src / main / java→com.redhat.training.todo.service展开 它。 双击ItemService.java文件。

导入JMSClient并注入默认实例。

在update方法中,使用以下消息添加对sendMessage方法的调用:ID为“+ item.getId()+”的项目已更新为状态Done =“+ item.isDone():

public void update(Item item) {	jmsClient.sendMessage("Item with ID:" + item.getId() + " was updated with status Done="+ item.isDone());
	em.merge(item);
    }

将QueueListener类更新为MDB,该bean侦听TodoListQueue并使用writeMessageToFile方法将消息输出到特殊日志文件。

通过展开JBDS左窗格中Project Explorer选项卡中的messaging-lab项打开QueueListener类,然后单击messaging-lab→Java Resources→src / main / java→com.redhat.training.todo.messaging展开 它。 双击QueueListener.java文件。

使QueueListener类实现MessageListener接口,并实现onMessage()方法:

实现对消息类型的简单检查,以确保它是TextMessage的实例,并使用提供的writeMessageToFile(String message)方法将结果记录到自定义日志文件中。 一定要使用try-catch块来处理任何JMSExceptions:

在代码中,也定义了日志文件的位置:

public void onMessage(Message rcvMessage) {		TextMessage msg = null;
		try {
			if (rcvMessage instanceof TextMessage) {
			msg = (TextMessage) rcvMessage;
				System.out.println("Received Message from TodoListQueue ===> " + msg.getText());
				writeMessageToFile(msg.getText());
			} else {
				System.out.println("Message of wrong type: " + rcvMessage.getClass().getName());
			}
		} catch (JMSException e) {
			e.printStackTrace();
		}
	}
}

使用@MessageDriven批注配置MDB,设置必要的@ActivationConfigProperty实例以从TodoListQueue中读取:

import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;@MessageDriven(name = "QueueListener", activationConfig = {
    @ActivationConfigProperty(propertyName = "destinationLookup", propertyValue = "queue/TodoListQueue"),
    @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue")})public class QueueListener implements MessageListener {
...

接下来,启动EAP,并编译和运行应用:

浏览器访问应用:

然后查看日志,有更新的记录:

七、实验二:Java使用消息队列:创建一个JMS Client

在本实验中,我编写一个JMS客户端,该客户端使用位于JBoss EAP中嵌入式Artemis代理上的JMS API和队列来发送和接收JMS消息。

我使用JMS API和JBoss EAP提供的受管对象来构建MessageProducer实例,并使用MessageConsumer接口来发送和接收来自队列的消息。

首先通过JDBS导入一个maven项目:

查看JBoss的配置文件:standalone-full.xml,可以看到消息队列子系统:

请注意,helloWorldQueue具有java的JNDI条目:jboss / jms / queue / helloWorldQueue。

配置JMS上下文和目标。

通过展开JBDS左窗格中Project Explorer选项卡中的jms-client项打开JMSClient类,然后单击jms-client→Java Resources→src / main / java→com.redhat.training.messaging并展开它。 双击JMSClient.java文件。

使用@Inject批注注入默认的JMSContext,它提供与在本地JBoss服务器上运行的嵌入式Artemis代理的连接。

@Resource注释直接注入目标对象,例如Queue或Topic,以及仅使用服务器配置中定义的JNDI名称的ConnectionFactory对象

使用@Resource注释注入helloWorldQueue目标:

确保mappedName属性已正确设置为队列的JNDI名称。

创建一个将消息放入helloWorldQueue的JMS生成器。

使用JMSContext接口提供的createProducer方法在sendMessage方法中构建MessageProducer的实例:

使用JMSContext接口创建TextMessage,以将msg参数的值映射到JMS消息的正文中:

使用生产者将消息发送到目的地:

创建一个从helloWorldQueue读取消息的JMS使用者。

使用JMSContext接口提供的createConsumer方法在getMessage方法中为helloWorldQueue目标构建MessageConsumer实例:

尝试从队列中读取消息,无需等待没有可用消息。 使用MessageConsumer接口提供的receiveNoWait方法并将结果转换为实例TextMessage:

使用close方法完成所有操作后关闭使用者:

启动EAP,编译并部署应用:

输入david

从消息队列获取消息:

八、实验三:POJO到EJB的转换

通过JBDS导入一个已经存在的maven项目:

查看源码:Item.java类。这个类在应用程序中建模一个todo项目。它有三个属性:一个id,一个描述和一个表示任务是否完成的布尔属性。

如下图箭头所示:

查看:ItemRepository.java

该类模拟内存数据库并存储待办事项列表。它具有添加项目,查看单个项目和查看所有项目列表的方法。 注意到这个类用@ApplicationScoped注解,这意味着只要应用程序在应用程序服务器上部署并运行,该类的对象就保持在作用域(活动)中。

注意到seedTodoList()方法已用@PostConstruct注解。一旦该类被初始化,这个方法用三个项目填充待办事项列表。

ItemService.java类,它是一个简单的POJO类,它包含添加待办事项的方法,查看待办事项和列出所有待办事项。

注意到这个类注入ItemRepository类并调用它的方法来添加,查看和列出所有todo项目。

接下来,我们把POJO转化为无状态的 EJB.

使用@Stateless注释标注ItemService类以将此POJO转换为EJB。

查看ItemResourceRESTService类,它为前端用户界面提供了REST端点(基于AngularJS)。该文件位于rest文件夹中。

注意到这个类需要使用ItemService EJB来调用EJB的方法,并向前端层提供JSON响应。

将ItemService EJB注入到ItemResourceRESTService类中。

将@EJB注释添加到ItemService声明中。

接下来,启动EAP:

通过运行以下命令来构建和部署EJB到JBoss EAP:

查看EAP日志:

访问应用:

我们添加条目,然后观察日志:

查坎EAP日志:

整体调用逻辑:

魏新宇

  • "大魏分享"运营者、红帽资深解决方案架构师
  • 专注开源云计算、容器及自动化运维在金融行业的推广
  • 拥有MBA、ITIL V3、Cobit5、C-STAR、TOGAF9.1(鉴定级)等管理认证。
  • 拥有红帽RHCE/RHCA、VMware VCP-DCV、VCP-DT、VCP-Network、VCP-Cloud、AIX、HPUX等技术认证。

原文发布于微信公众号 - 大魏分享(david-share)

原文发表时间:2018-08-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大魏分享(微信公众号:david-share)

设计一个应用集成的路由:构建以API为中心的敏捷集成系列-第五篇

Message 消息: Unit of transport containing 消息传递的内容包括

1102
来自专栏安富莱嵌入式技术分享

【RL-TCPnet网络教程】第26章 RL-TCPnet之DHCP应用

本章节为大家讲解RL-TCPnet的DHCP应用,学习本章节前,务必要优先学习第25章的DHCP基础知识。有了这些基础知识之后,再搞本章节会有事半功倍的效果。

991
来自专栏大魏分享(微信公众号:david-share)

实战:将POJO类转换为EJB | 从开发角度看应用架构6

1125
来自专栏Android 研究

Android跨进程通信IPC之13——Binder总结

我们知道在Linux系统中,进程间的通信方式有socket,named pipe,message queue, signal,sharememory等。这几种通...

1744
来自专栏信安之路

Bypass 360主机卫士SQL注入防御(多姿势)

在服务器客户端领域,曾经出现过一款 360 主机卫士,目前已停止更新和维护,官网都打不开了,但服务器中依然经常可以看到它的身影。

2570
来自专栏小灰灰

熔断Hystrix使用尝鲜

熔断Hystrix使用尝鲜 当服务有较多外部依赖时,如果其中某个服务的不可用,导致整个集群会受到影响(比如超时,导致大量的请求被阻塞,从而导致外部请求无法进来)...

3529
来自专栏Ryan Miao

使用dropwizard(3)-加入DI-dagger2

前言 习惯了Spring全家桶,对spring的容器爱不释手。使用dropwizard,看起来确实很轻,然而,真正使用的时候不得不面临一个问题。我们不可能一个...

2727
来自专栏Java学习网

Java开发技术之Spring依赖注入知识学习

不管是构造器、Setter方法还是其他的方法,Spring都会尝试满足方法参数上所声明的依赖。假如有且只有一个bean匹配依赖需求的话,那么这个bean将会被装...

1082
来自专栏猿天地

高性能NIO框架Netty入门篇

Netty介绍 Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服...

38510
来自专栏纯洁的微笑

springboot(二):web综合开发

上篇文章介绍了Spring boot初级教程:spring boot(一):入门篇,方便大家快速入门、了解实践Spring boot特性;本篇文章接着上篇内容继...

4066

扫码关注云+社区

领取腾讯云代金券