1 课程计划
第八天:
- Activemq整合springMQ的应用场景
- 添加商品同步索引库
- 商品详情页面动态展示
- 展示详情页面使用缓存
2 Activemq整合spring
2.1 使用方法
第一步:引用相关的jar包。
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-jms</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> </dependency> |
第二步:配置Activemq整合spring。配置ConnectionFactory
在e3-manager-service创建applicationContext-activemq.xml配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" 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-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd">
<!-- 真正可以产生Connection的ConnectionFactory,由对应的 JMS服务厂商提供 --> <bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> <property name="brokerURL" value="tcp://192.168.25.168:61616" /> </bean> <!-- Spring用于管理真正的ConnectionFactory的ConnectionFactory --> <bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory"> <!-- 目标ConnectionFactory对应真实的可以产生JMS Connection的ConnectionFactory --> <property name="targetConnectionFactory" ref="targetConnectionFactory" /> </bean> </beans> |
第三步:配置生产者。
使用JMSTemplate对象。发送消息。
第四步:在spring容器中配置Destination。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" 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-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd">
<!-- 真正可以产生Connection的ConnectionFactory,由对应的 JMS服务厂商提供 --> <bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> <property name="brokerURL" value="tcp://192.168.25.167:61616" /> </bean> <!-- Spring用于管理真正的ConnectionFactory的ConnectionFactory --> <bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory"> <!-- 目标ConnectionFactory对应真实的可以产生JMS Connection的ConnectionFactory --> <property name="targetConnectionFactory" ref="targetConnectionFactory" /> </bean> <!-- 配置生产者 --> <!-- Spring提供的JMS工具类,它可以进行消息发送、接收等 --> <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate"> <!-- 这个connectionFactory对应的是我们定义的Spring提供的那个ConnectionFactory对象 --> <property name="connectionFactory" ref="connectionFactory" /> </bean> <!--这个是队列目的地,点对点的 --> <bean id="queueDestination" class="org.apache.activemq.command.ActiveMQQueue"> <constructor-arg> <value>spring-queue</value> </constructor-arg> </bean> <!--这个是主题目的地,一对多的 --> <bean id="topicDestination" class="org.apache.activemq.command.ActiveMQTopic"> <constructor-arg value="topic" /> </bean> </beans> |
第五步:代码测试
在e3-manager-service工程创建测试类:
@Test public void testSpringActiveMq() throws Exception { //初始化spring容器 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext-activemq.xml"); //从spring容器中获得JmsTemplate对象 JmsTemplate jmsTemplate = applicationContext.getBean(JmsTemplate.class); //从spring容器中取Destination对象 Destination destination = (Destination) applicationContext.getBean("queueDestination"); //使用JmsTemplate对象发送消息。 jmsTemplate.send(destination, new MessageCreator() {
@Override public Message createMessage(Session session) throws JMSException { //创建一个消息对象并返回 TextMessage textMessage = session.createTextMessage("spring activemq queue message"); return textMessage; } }); } |
启动消费者:
2.2 代码测试
2.2.1 发送消息
第一步:初始化一个spring容器
第二步:从容器中获得JMSTemplate对象。
第三步:从容器中获得一个Destination对象
第四步:使用JMSTemplate对象发送消息,需要知道Destination
@Test public void testQueueProducer() throws Exception { // 第一步:初始化一个spring容器 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext-activemq.xml"); // 第二步:从容器中获得JMSTemplate对象。 JmsTemplate jmsTemplate = applicationContext.getBean(JmsTemplate.class); // 第三步:从容器中获得一个Destination对象 Queue queue = (Queue) applicationContext.getBean("queueDestination"); // 第四步:使用JMSTemplate对象发送消息,需要知道Destination jmsTemplate.send(queue, new MessageCreator() {
@Override public Message createMessage(Session session) throws JMSException { TextMessage textMessage = session.createTextMessage("spring activemq test"); return textMessage; } }); } |
2.2.2 接收消息
e3-search-Service中接收消息复制applicationContext-activemq.xml配置文件到工程下。
第一步:把Activemq相关的jar包添加到工程中
第二步:创建一个MessageListener的实现类。
public class MyMessageListener implements MessageListener {
@Override public void onMessage(Message message) {
try { TextMessage textMessage = (TextMessage) message; //取消息内容 String text = textMessage.getText(); System.out.println(text); } catch (JMSException e) { e.printStackTrace(); } }
} |
第三步:配置spring和Activemq整合。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" 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-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd">
<!-- 真正可以产生Connection的ConnectionFactory,由对应的 JMS服务厂商提供 --> <bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> <property name="brokerURL" value="tcp://192.168.25.167:61616" /> </bean> <!-- Spring用于管理真正的ConnectionFactory的ConnectionFactory --> <bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory"> <!-- 目标ConnectionFactory对应真实的可以产生JMS Connection的ConnectionFactory --> <property name="targetConnectionFactory" ref="targetConnectionFactory" /> </bean> <!--这个是队列目的地,点对点的 --> <bean id="queueDestination" class="org.apache.activemq.command.ActiveMQQueue"> <constructor-arg> <value>spring-queue</value> </constructor-arg> </bean> <!--这个是主题目的地,一对多的 --> <bean id="topicDestination" class="org.apache.activemq.command.ActiveMQTopic"> <constructor-arg value="topic" /> </bean> <!-- 接收消息 --> <!-- 配置监听器 --> <bean id="myMessageListener" class="cn.e3mall.search.listener.MyMessageListener" /> <!-- 消息监听容器 --> <bean class="org.springframework.jms.listener.DefaultMessageListenerContainer"> <property name="connectionFactory" ref="connectionFactory" /> <property name="destination" ref="queueDestination" /> <property name="messageListener" ref="myMessageListener" /> </bean> </beans> |
第四步:测试代码。
@Test public void testQueueConsumer() throws Exception { //初始化spring容器 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext-activemq.xml"); //等待 System.in.read(); } |
3 添加商品同步索引库
e3-search-service(消费者):
ItemMapper.java:
ItemMapper.xml:
ItemAddMessageListener.java:
applicationContext-activemq.xml配置消费者生效 (被多个客户端接受使用topic):
e3-manager-service(消息提供者):
applicationContext-activemq.xml:
ItemServiceImpl:
在添加商品的方法中(问题:事务还没提交那边已经查数据库可能导致空指针异常):
解决空指针异常(接受消息时等待1秒或者在事务提交后在controller层进行消息发送):
e3-search-service:
ItemAddMessageListener.java:
切换solr为单机版测试:
e3-search-service:
applicationContext-solr.xml:
启动zookeeper单机版 redis单机版 solr单机版即可
/3.1 Producer()
e3-manager-server工程中发送消息。
当商品添加完成后发送一个TextMessage,包含一个商品id。
@Override public e3Result addItem(TbItem item, String desc) { // 1、生成商品id final long itemId = IDUtils.genItemId(); // 2、补全TbItem对象的属性 item.setId(itemId); //商品状态,1-正常,2-下架,3-删除 item.setStatus((byte) 1); Date date = new Date(); item.setCreated(date); item.setUpdated(date); // 3、向商品表插入数据 itemMapper.insert(item); // 4、创建一个TbItemDesc对象 TbItemDesc itemDesc = new TbItemDesc(); // 5、补全TbItemDesc的属性 itemDesc.setItemId(itemId); itemDesc.setItemDesc(desc); itemDesc.setCreated(date); itemDesc.setUpdated(date); // 6、向商品描述表插入数据 itemDescMapper.insert(itemDesc); //发送一个商品添加消息 jmsTemplate.send(topicDestination, new MessageCreator() {
@Override public Message createMessage(Session session) throws JMSException { TextMessage textMessage = session.createTextMessage(itemId + ""); return textMessage; } }); // 7、e3Result.ok() return e3Result.ok(); } |
3.2 Consumer()
3.2.1 功能分析
- 接收消息。需要创建MessageListener接口的实现类。
- 取消息,取商品id。
- 根据商品id查询数据库。
- 创建一SolrInputDocument对象。
- 使用SolrServer对象写入索引库。
- 返回成功,返回e3Result。
3.2.2 Dao层
根据商品id查询商品信息。
映射文件:
<select id="getItemById" parameterType="long" resultType="cn.e3mall.common.pojo.SearchItem"> SELECT a.id, a.title, a.sell_point, a.price, a.image, b. NAME category_name, c.item_desc FROM tb_item a JOIN tb_item_cat b ON a.cid = b.id JOIN tb_item_desc c ON a.id = c.item_id WHERE a.status = 1 AND a.id=#{itemId} </select> |
3.2.3 Service层
参数:商品ID
业务逻辑:
- 根据商品id查询商品信息。
- 创建一SolrInputDocument对象。
- 使用SolrServer对象写入索引库。
- 返回成功,返回e3Result。
返回值:e3Result
public e3Result addDocument(long itemId) throws Exception { // 1、根据商品id查询商品信息。 SearchItem searchItem = searchItemMapper.getItemById(itemId); // 2、创建一SolrInputDocument对象。 SolrInputDocument document = new SolrInputDocument(); // 3、使用SolrServer对象写入索引库。 document.addField("id", searchItem.getId()); document.addField("item_title", searchItem.getTitle()); document.addField("item_sell_point", searchItem.getSell_point()); document.addField("item_price", searchItem.getPrice()); document.addField("item_image", searchItem.getImage()); document.addField("item_category_name", searchItem.getCategory_name()); document.addField("item_desc", searchItem.getItem_desc()); // 5、向索引库中添加文档。 solrServer.add(document); solrServer.commit(); // 4、返回成功,返回e3Result。 return e3Result.ok(); } |
3.2.4 Listener
public class ItemChangeListener implements MessageListener {
@Autowired private SearchItemServiceImpl searchItemServiceImpl;
@Override public void onMessage(Message message) { try { TextMessage textMessage = null; Long itemId = null; //取商品id if (message instanceof TextMessage) { textMessage = (TextMessage) message; itemId = Long.parseLong(textMessage.getText()); } //向索引库添加文档 searchItemServiceImpl.addDocument(itemId);
} catch (Exception e) { e.printStackTrace(); } }
} |
3.2.5 Spring配置监听
/
3.2.6 实现流程
4 商品详情页面展示
创建一个商品详情页面展示的工程。是一个表现层工程。
4.1 工程搭建
e3-item-web。打包方式war。可以参考e3-portal-web
4.1.1 Pom文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>cn.e3mall</groupId> <artifactId>e3-parent</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <groupId>cn.e3mall</groupId> <artifactId>e3-item-web</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <dependency> <groupId>cn.e3mall</groupId> <artifactId>e3-manager-interface</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <!-- Spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jms</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> </dependency> <!-- JSP相关 --> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jsp-api</artifactId> <scope>provided</scope> </dependency> <!-- dubbo相关 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <!-- 排除依赖 --> <exclusions> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring</artifactId> </exclusion> <exclusion> <groupId>org.jboss.netty</groupId> <artifactId>netty</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> </dependency> <dependency> <groupId>com.github.sgroschupf</groupId> <artifactId>zkclient</artifactId> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency> </dependencies> <!-- 配置tomcat插件 --> <build> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <configuration> <port>8086</port> <path>/</path> </configuration> </plugin> </plugins> </build> </project> |
复制配置文件:
1.删除resource.properties内容
2.修改springmvc.xml配置文件:
创建包:
复制web.xml配置文件:
复制静态资源:
4.2 功能分析
在搜索结果页面点击商品图片或者商品标题,展示商品详情页面。
请求的url:/item/{itemId}
参数:商品id
返回值:String 逻辑视图
业务逻辑:
1. 从url中取参数,商品id
2. 根据商品id查询商品信息(tb_item)得到一个TbItem对象,缺少images属性,可以创建一个pojo继承TbItem,添加一个getImages方法。在e3-item-web工程中。
public class Item extends TbItem {
public String[] getImages() { String image2 = this.getImage(); if (image2 != null && !"".equals(image2)) { String[] strings = image2.split(","); return strings; } return null; }
public Item() {
}
public Item(TbItem tbItem) { this.setBarcode(tbItem.getBarcode()); this.setCid(tbItem.getCid()); this.setCreated(tbItem.getCreated()); this.setId(tbItem.getId()); this.setImage(tbItem.getImage()); this.setNum(tbItem.getNum()); this.setPrice(tbItem.getPrice()); this.setSellPoint(tbItem.getSellPoint()); this.setStatus(tbItem.getStatus()); this.setTitle(tbItem.getTitle()); this.setUpdated(tbItem.getUpdated()); }
} |
3. 根据商品id查询商品描述。
4. 展示到页面。
4.3 Dao层
查询tb_item, tb_item_desc两个表,都是单表查询。可以使用逆向工程。
4.4 Service层
- 根据商品id查询商品信息
参数:商品id
返回值:TbItem
- 根据商品id查询商品描述
参数:商品id
返回值:TbItemDesc
@Override public TbItemDesc getItemDescById(long itemId) { TbItemDesc itemDesc = itemDescMapper.selectByPrimaryKey(itemId); return itemDesc; } |
4.5 表现层
4.5.1 Controller
请求的url:/item/{itemId}
参数:商品id
返回值:String 逻辑视图
@Controller public class ItemController {
@Autowired private ItemService itemService;
@RequestMapping("/item/{itemId}") public String showItemInfo(@PathVariable Long itemId, Model model) { //跟据商品id查询商品信息 TbItem tbItem = itemService.getItemById(itemId); //把TbItem转换成Item对象 Item item = new Item(tbItem); //根据商品id查询商品描述 TbItemDesc tbItemDesc = itemService.getItemDescById(itemId); //把数据传递给页面 model.addAttribute("item", item); model.addAttribute("itemDesc", tbItemDesc); return "item"; } } |
引用服务
4.6 向业务逻辑中添加缓存
4.6.1 缓存添加分析
使用redis做缓存。
复制applicationContext-redis.xml配置文件到e3-manager-service工程:
业务逻辑:
- 根据商品id到缓存中命中
- 查到缓存,直接返回。
- 差不到,查询数据库
- 把数据放到缓存中
- 返回数据
缓存中缓存热点数据,提供缓存的使用率。需要设置缓存的有效期。一般是一天的时间,可以根据实际情况跳转。
需要使用String类型来保存商品数据。
可以加前缀方法对象redis中的key进行归类。
ITEM_INFO:123456:BASE
ITEM_INFO:123456:DESC
如果把二维表保存到redis中:
- 表名就是第一层
- 主键是第二层
- 字段名第三次
三层使用“:”分隔作为key,value就是字段中的内容。
4.6.2 把redis相关的jar包添加到工程
4.6.3 添加缓存
配置resource.properties:
applicationContext-dao.xml配置文件修改加载配置文件:
引入缓存对象:
@Override public TbItem getItemById(long itemId) { try { //查询缓存 String json = jedisClient.get(ITEM_INFO_PRE + ":" + itemId + ":BASE"); if (StringUtils.isNotBlank(json)) { //把json转换为java对象 TbItem item = JsonUtils.jsonToPojo(json, TbItem.class); return item; } } catch (Exception e) { e.printStackTrace(); } //根据商品id查询商品信息 //TbItem tbItem = itemMapper.selectByPrimaryKey(itemId); TbItemExample example = new TbItemExample(); //设置查询条件 Criteria criteria = example.createCriteria(); criteria.andIdEqualTo(itemId); List<TbItem> list = itemMapper.selectByExample(example); if (list != null && list.size() > 0) { TbItem item = list.get(0); try { //把数据保存到缓存 jedisClient.set(ITEM_INFO_PRE + ":" + itemId + ":BASE", JsonUtils.objectToJson(item)); //设置缓存的有效期 jedisClient.expire(ITEM_INFO_PRE + ":" + itemId + ":BASE", ITEM_INFO_EXPIRE); } catch (Exception e) { e.printStackTrace(); } return item; } return null; } |
取商品描述添加缓存:
@Override public TbItemDesc getItemDescById(long itemId) { try { String json = jedisClient.get(ITEM_INFO_PRE + ":" + itemId + ":DESC"); //判断缓存是否命中 if (StringUtils.isNotBlank(json) ) { //转换为java对象 TbItemDesc itemDesc = JsonUtils.jsonToPojo(json, TbItemDesc.class); return itemDesc; } } catch (Exception e) { e.printStackTrace(); } TbItemDesc itemDesc = itemDescMapper.selectByPrimaryKey(itemId); try { jedisClient.set(ITEM_INFO_PRE + ":" + itemId + ":DESC", JsonUtils.objectToJson(itemDesc)); //设置过期时间 jedisClient.expire(ITEM_INFO_PRE + ":" + itemId + ":DESC", ITEM_INFO_EXPIRE); } catch (Exception e) { e.printStackTrace(); } return itemDesc; }
|
5 FreeMarker测试类
安装freemarker插件:
复制到ecplise下的dropins目录下:
创建模板文件:
添加jar包:
在表现层e3-item-web创建测试类: