异步消息简介

在起那面几张介绍的像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);
        }
    }
}

发送方截图:

微信异步通知 java_xml

接收方:

微信异步通知 java_spring_02