前两篇我们了解了activeMQ的概念和一般使用方法,但凡Java出彩的中间件和开源项目都绕不开spring这座大山,我们就来看看结合spring的实际应用,在这之前我想大家平常都会使用京东购物,每次付款成功后,系统app会先提示我们下单成功,很快再次提示订单已生成,这其中就是消息中间件的作用,当并发数高的时候直接对数据库操作,会对数据库造成过大的压力,所以先把下单信息放到中间件提示用户下单成功,插入数据库后再告诉客户订单生成,当然京东的架构不像我说的这么简单,我描述个大概。
那么让我们简单基于activeMQ实现一下添加书籍的功能,我之前写过一个简单的ssm项目,这里直接拿来用,链接在这十二步用ssm+bootstrsp搭建简单易上手的小型图书系统
新的项目结构图:
那么首先添加好pom文件的依赖:
<!-- xbean 如<amq:connectionFactory /> -->
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-spring</artifactId>
<version>3.16</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>4.3.6.RELEASE</version>
</dependency>
<!-- activemq -->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-core</artifactId>
<version>5.7.0</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-pool</artifactId>
<version>5.12.1</version>
</dependency>
第二步:构建activemq.xml文件,将spring和activemq结合起来
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:amq="http://activemq.apache.org/schema/core"
xmlns:jms="http://www.springframework.org/schema/jms"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
http://www.springframework.org/schema/jms
http://www.springframework.org/schema/jms/spring-jms-4.1.xsd
http://activemq.apache.org/schema/core
http://activemq.apache.org/schema/core/activemq-core-5.12.1.xsd"
>
<context:component-scan base-package="cn.com.activimq" />
<mvc:annotation-driven />
<!-- 这里配置activemq连接工厂,连接activemq -->
<bean id="activeMQConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61616"/>
</bean>
<!-- 配置JMS连接工长,结合spring自然是通过spring-jms的连接工厂来实现的 -->
<bean id="connectionFactory"
class="org.springframework.jms.connection.SingleConnectionFactory">
<constructor-arg ref="activeMQConnectionFactory" />
</bean>
<!-- 一个队列目的地,点对点;队列和主题模式之前已经讲过了 -->
<bean id ="queueDestination" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg value="spring-queue" />
</bean>
<!-- 主题目的地 -->
<bean id ="topicDestination" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg value="spring-topic" />
</bean>
<!-- JmsTemplate Definition -->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="connectionFactory" />
<property name="defaultDestination" ref="queueDestination" />
</bean>
<!-- 配置监听器,监听器的作用就是一有消息消费者可以自动接收处理 -->
<bean id="consumerMessageListener" class="cn.com.activimq.consumer.ConsumerMessageListener"></bean>
<!-- 配置消息容器,目的地destination随模式更换即可 -->
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"></property>
<property name="destination" ref="topicDestination"></property>
<property name="messageListener" ref="consumerMessageListener"></property>
</bean>
<bean id="producerServiceImpl" class="cn.com.activimq.producer.ProducerServiceImpl"></bean>
</beans>
当然我们不能忘记要在web.xml中将activemq.xml加载进去
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1" metadata-complete="true">
<!-- 配置DispatcherServlet -->
<servlet>
<servlet-name>seckill-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置springMVC需要加载的配置文件
spring-dao.xml,spring-service.xml,spring-web.xml
Mybatis - > spring -> springmvc
-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:Spring/spring-*.xml
classpath:Spring/activemq.xml
</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>seckill-dispatcher</servlet-name>
<!-- 默认匹配所有的请求 -->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
第三步:将我们之前写的生产者拉进来,一个ProducerService接口,一个ProducerServiceImpl实现
package cn.com.activimq.producer;
public interface ProducerService {
void sendMessage(String message);
}
package cn.com.activimq.producer;
import javax.annotation.Resource;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
public class ProducerServiceImpl implements ProducerService{
@Autowired
JmsTemplate jmsTemplate;
@Resource(name ="topicDestination")
Destination destination;
@Override
public void sendMessage(final String message) {
//使用jmsTemplate发送消息
jmsTemplate.send(destination,new MessageCreator(){
@Override
public Message createMessage(Session session) throws JMSException {
TextMessage textMessage = session.createTextMessage(message);
System.out.println("发送消息"+textMessage.getText());
return textMessage;
}
});
System.out.println("发送消息"+message);
}
}
第四步:在BookController中将添加图书的消息先存到activemq中去
package cn.com.controller;
import cn.com.activimq.producer.ProducerServiceImpl;
import cn.com.entity.Book;
import cn.com.service.BookService;
import java.util.List;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
/**
* ${DESCRIPTION}
*
* @author lightTrace
* @create 2017-10-26 21:57
**/
@Controller
@RequestMapping("/book")
public class BookController {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private BookService bookService;
@Resource
private ProducerServiceImpl producerServiceImpl;
@RequestMapping(value = "/list", method = RequestMethod.GET)
private String list(Model model) {
List<Book> list = bookService.getList(0, 1000);
model.addAttribute("list", list);
return "list";// WEB-INF/jsp/"list".jsp
}
@RequestMapping(value = "/addPage", method = RequestMethod.GET)
private String addPage(Model model) {
return "add";// WEB-INF/jsp/"list".jsp
}
@RequestMapping(value = "/detail/{bookId}", method = RequestMethod.GET)
private String detail(@PathVariable("bookId") Long bookId, Model model) {
Book book = bookService.getById(bookId);
model.addAttribute("book", book);
return "detail";
}
@RequestMapping(value = "/add", method = RequestMethod.POST, produces = "text/plain;charset=UTF-8")
private String add(Book book,Model model) {
Book hasBook = bookService.getById(book.getBookId());
String bookJsonString = JSON.toJSONString(book);
//将添加的book对象信息转换为json字符串传输到activemq中去
producerServiceImpl.sendMessage(bookJsonString);
return "list";
}
@RequestMapping(value = "/del/{bookId}", method = RequestMethod.GET)
@ResponseBody
private String deleteBookById(@PathVariable("bookId") Long id) {
int i = bookService.deleteBookById(id);
return i > 0 ? "success" : "error";
}
}
第五步:通过监听器将添加图书的信息通过BookService消费掉:
package cn.com.activimq.consumer;
import cn.com.entity.Book;
import cn.com.service.BookService;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
public class ConsumerMessageListener implements MessageListener{
@Autowired
private BookService bookService;
@Override
public void onMessage(Message message) {
TextMessage textMessage = (TextMessage) message;
try {
Book book = JSON.parseObject(textMessage.getText(), Book.class);
bookService.addBook(book);
System.out.println("接收消息"+textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
第六步:自然是添加一个添加图书的jsp页面,在添加图书信息那里
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<% String appPath = request.getContextPath(); %>
<html>
<head>
<title>添加图书</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 引入 Bootstrap -->
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<!-- HTML5 Shim 和 Respond.js 用于让 IE8 支持 HTML5元素和媒体查询 -->
<!-- 注意: 如果通过 file:// 引入 Respond.js 文件,则该文件无法起效果 -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
</head>
<body>
<div class="container">
<div class="row clearfix">
<div class="col-md-12 column">
<div class="page-header">
<h1>
<small>图书管理系统 - 添加图书</small>
</h1>
</div>
</div>
</div>
<div class="row clearfix">
<div class="col-md-12 column">
<ul class="nav nav-tabs">
<li><a href="<%=appPath%>/book/list">首页</a></li>
<li class="active"><a href="<%=appPath%>/book/detail/1003">图书具体信息</a></li>
<li class="disabled"><a href="#">信息</a></li>
</ul>
</div>
</div>
<form action="${pageContext.request.contextPath}/book/add" method="POST">
<P style="padding: 30px 0px 10px; position: relative;">
<SPAN class="u_logo"></SPAN>
<INPUT id="bookId" name="bookId" class="ipt" type="text"
placeholder="请输入书号" value="${book.bookId}">
</P>
<P style="position: relative;">
<SPAN class="p_logo"></SPAN>
<INPUT id="name" name="name" class="ipt" type="text"
placeholder="请输入书名" value="${book.name }">
</P>
<P style="position: relative;">
<SPAN class="p_logo"></SPAN>
<INPUT id="number" name="number" class="ipt" type="text"
placeholder="请输入书页数" value="${book.number }">
</P>
<P style="position: relative;">
<SPAN class="p_logo"></SPAN>
<INPUT id="detail" name="detail" class="ipt" type="text"
placeholder="请输入书描述" value="${book.detail }">
</P>
<DIV
style="height: 50px; line-height: 50px; margin-top: 30px; border-top-color: rgb(231, 231, 231); border-top-width: 1px; border-top-style: solid;">
<P style="margin: 0px 35px 20px 45px;">
<SPAN style="float: left;">图书管理系统</SPAN>
<span><font color="red" id="error">${errorInfo }</font></span>
<SPAN
style="float: right;">
<input type="submit"
style="background: rgb(0, 142, 173); padding: 7px 10px; border-radius: 4px; border: 1px solid rgb(26, 117, 152); border-image: none; color: rgb(255, 255, 255); font-weight: bold;"
value="添加" />
</SPAN>
</P>
</DIV>
</DIV>
</form>
</div>
<!-- jQuery (Bootstrap 的 JavaScript 插件需要引入 jQuery) -->
<script src="https://code.jquery.com/jquery.js"></script>
</body>
</html>
末了记得修改list.jsp页面的跳转:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<% String appPath = request.getContextPath(); %>
<html>
<head>
<title>图书列表</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 引入 Bootstrap -->
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="row clearfix">
<div class="col-md-12 column">
<div class="page-header">
<h1>
<small>图书管理系统 </small>
</h1>
</div>
</div>
</div>
<div class="row clearfix">
<div class="col-md-12 column">
<ul class="nav nav-tabs">
<li class="active"><a href="<%=appPath%>/book/list">首页</a></li>
<li><a href="<%=appPath%>/book/detail/1000">图书具体信息</a></li>
<li><a href="<%=appPath%>/book/addPage">添加图书信息</a></li>
<li class="disabled"><a href="#">信息</a></li>
</ul>
</div>
</div>
<div class="row clearfix">
<div class="col-md-12 column">
<div class="page-header">
<h1>图书列表 <small>显示当前图书库存信息</small></h1>
</div>
</div>
</div>
<div class="row clearfix">
<div class="col-md-12 column">
<table class="table table-hover table-striped">
<thead>
<tr>
<th>图书编号</th>
<th>图书名字</th>
<th>图书数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<c:forEach var="book" items="${requestScope.get('list')}" varStatus="status">
<tr>
<td>${book.bookId}</td>
<td>${book.name}</td>
<td>${book.number}</td>
<td>
<a href="<%=appPath%>/book/detail/${book.bookId}">详情</a> |
<a href="<%=appPath%>/book/del/${book.bookId}">删除</a>
</td>
</tr>
</c:forEach>
</tbody>
</table>
</div>
</div>
</div>
<!-- jQuery (Bootstrap 的 JavaScript 插件需要引入 jQuery) -->
<script src="https://code.jquery.com/jquery.js"></script>
</body>
</html
效果图:
通过代码的构建我们发现activemq就是将controller和service层测底的解耦了,controller只需要把消息异步传递出去,却不管你service怎么处理.
项目github地址:小型图书管理系统扩展activemq