异步消息简介
在起那面几张介绍的像RMI、Hessian、Burlap、HttpInvoker这些都是同步调用,他们的缺点就是在调用过程中引用程序将会阻塞,一直等到调用完成或者超时(如果设置了超时的话),那么如果这样的调用很频繁,或者收到网络延迟的影响,将会给用户带来不好的用户体验。而异步消息则是发送了消息之后,可以继续做其他的事情,不需要等待消息处理完成。异步消息主要是通过一个中间服务,发送的一方只需要把消息发送给这个中间服务,接收放会监听这个中间服务,如果有消息发送过来,就会处理,这样的好处是降低了发送和接收放的耦合,同时,就算接收方可能由于某种原因导致服务宕掉了,异步消息也能保证服务重启后能继续处理这些消息,因为他有个中间服务,还有一个好处就是可以创建多个接收方来缓解处理压力。
异步消息的两种模型
点对点模型:就是有多个接收者,但是一条消息只会给其中的一个接收者处理,点对点模型一般接收这的处理逻辑相同,以缓解处理压力。
发布-订阅模型:故名思意,发布者发布一条消息,每个接收者都能接收到消息并处理,发布-订阅模型一般每个接收这的处理逻辑都不同,用来处理不同的是,例如:公司新来一个员工,记录员工信息后可能会发送一条消息,其中一个接收这可能会给这个员工分配相应权限,另一个接收者可能会在薪资系统中记录该员工。
JMS简介
jms(Java Message Service)是一个Java标准,定义了使用消息代理的通用api,在jms出现之前,每个消息代理都有私有的api,这使得使用不同消息代理的代码很难通用,但借助jms,所有遵从规范的实现都使用相同的接口,这就类似于JDBC为数据库操作提供了通用的接口一样。
在spring中使用JMS
在使用之前我们需要一个消息代理,也就是前面提到的中间服务。在这里使用ActiveMQ,可以自行百度下载,下载玩直接解压就可以使用了,找到bin/activemq.bat,双击,服务就启动了。
配置发送方:
第一步:配置连接工厂:
<!--装配ActiveMQ的连接工厂-->
<bean name="connectionFactory" class="org.apache.activemq.spring.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61616"/>
</bean>
brokerURL属性用于设置代理监听的URL.
第二步:配置消息目的地
<!--目的地(点对点模型)-->
<bean class="org.apache.activemq.command.ActiveMQQueue">
<property name="physicalName" value="llg.queue"/>
</bean>
<!--目的地(发布-订阅模型)-->
<bean class="org.apache.activemq.command.ActiveMQTopic">
<property name="physicalName" value="llg.topic"/>
</bean>
在这里使用了可以选择使用不同的模型,physicalName属性指定消息目的地名称
第三步:配置JmsTemplate
<bean name="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="defaultDestinationName" value="llg.queue"/>
</bean>
connectionFactory属性用于配置连接工厂
defaultDestinationName用于设置默认的目的地
这样就配置好了,完整的配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="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">
<!--装配ActiveMQ的连接工厂-->
<bean name="connectionFactory" class="org.apache.activemq.spring.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61616"/>
</bean>
<!--目的地(点对点模型)-->
<bean class="org.apache.activemq.command.ActiveMQQueue">
<property name="physicalName" value="llg.queue"/>
</bean>
<!--目的地(发布-订阅模型)-->
<bean class="org.apache.activemq.command.ActiveMQTopic">
<property name="physicalName" value="llg.topic"/>
</bean>
<bean name="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="defaultDestinationName" value="llg.queue"/>
</bean>
</beans>
然后就可以使用jmsTemplate发送异步消息了,想这样:
package com.llg.tset;
import com.llg.bean.User;
import com.llg.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestSend {
@Autowired
private JmsTemplate jmsTemplate;
@Test
public void test(){
int i = 0;
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
User user = new User();
user.setName("zhangsan");
user.setId(i++);
System.out.println(user);
jmsTemplate.convertAndSend(user);
}
}
}
在这段代码中我每隔一秒发送一个User对象
User类:
package com.llg.bean;
import java.io.Serializable;
public class User implements Serializable {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
然后配置接收方:
接收方同样要配置连接工厂和目的地,如果在同一个应用中就不用重复配置了
只需要配置一个消息监听器,监听消息代理:
<bean name="messageHandler" class="com.llg.handler.MessageHandler"/>
<!--消息监听器-->
<jms:listener-container connection-factory="connectionFactory">
<jms:listener destination="llg.queue" ref="messageHandler" method="onMessage"/>
</jms:listener-container>
使用<jms:listener-container>配置消息监听容器,他会自动去找名为connectionFactory的连接工厂,每个监听器都使用这个工厂。
每个<jms:listener>定义了一个消息监听器,destination属性指定目的地,ref指定了处理bean,method指定了具体的方法
处理类:
package com.llg.handler;
import com.llg.bean.User;
public class MessageHandler {
public void onMessage(User user){
System.out.println("receive1-->"+user);
}
}
完整的spring配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:jms="http://www.springframework.org/schema/jms"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jms
http://www.springframework.org/schema/jms/spring-jms.xsd">
<!--装配ActiveMQ的连接工厂-->
<bean name="connectionFactory" class="org.apache.activemq.spring.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61616"/>
</bean>
<!--目的地(点对点模型)-->
<bean class="org.apache.activemq.command.ActiveMQQueue">
<property name="physicalName" value="llg.queue"/>
</bean>
<!--目的地(发布-订阅模型)-->
<bean class="org.apache.activemq.command.ActiveMQTopic">
<property name="physicalName" value="llg.topic"/>
</bean>
<bean name="messageHandler" class="com.llg.handler.MessageHandler"/>
<jms:listener-container connection-factory="connectionFactory">
<jms:listener destination="llg.queue" ref="messageHandler" method="onMessage"/>
</jms:listener-container>
</beans>
然后直接启动spring就可以监听消息了,像这样:
package com.llg.test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestReceive {
public static void main(String[] args) {
new ClassPathXmlApplicationContext("applicationContext.xml");
}
}
然后在启动刚刚发送给消息的代码:
package com.llg.tset;
import com.llg.bean.User;
import com.llg.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestClient {
@Autowired
private JmsTemplate jmsTemplate;
@Test
public void test(){
int i = 0;
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
User user = new User();
user.setName("zhangsan");
user.setId(i++);
System.out.println("send-->"+user);
jmsTemplate.convertAndSend(user);
}
}
}
发送方截图:
接收方: