前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >今儿咱说说消息那些事 | 从开发角度看应用架构17

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

作者头像
魏新宇
发布2018-08-16 15:09:12
9570
发布2018-08-16 15:09:12
举报

前言

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

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

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

本文分为系列文章,将会有多篇,初步预计将会有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方法,该方法检查消息类型并记录消息内容:

代码语言:javascript
复制
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的方法体,也就是:

代码语言:javascript
复制
public class QueueListener implements MessageListener {

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

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

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

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

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

代码语言:javascript
复制
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():

代码语言:javascript
复制
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:

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

代码语言:javascript
复制
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中读取:

代码语言:javascript
复制
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等技术认证。
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-08-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 大魏分享 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档