A Simple Application of Ehcache-RSSReaderTag

Posted by xtian on Mon, 09 Sep 2019 07:15:20 +0200

Links to the original text: https://my.oschina.net/mohaiyong/blog/221286

Cache code has been written a lot before, but has not been sorted out, always lazy!

In this case, we need to dynamically display RSS entries on the page. If you want to get RSS on the page, you need to send a request to the remote server, at the same time, you need to parse and display. If this page is visited many times, it will lead to many HTTP requests, it is likely that their website did not open, but the RSS server downtime! At this time, we need to cache RSS information, reduce the number of requests to improve response efficiency!
Furthermore, simplify JSP code implementation through tags!
use Reuters: Technological Electronics As an example, the final page is as follows:

Let's talk about RSS nodes, and here's image first.
The following structure is given in RSS
<image>
	<title>Reuters News</title>
	<width>120</width>
	<height>35</height>
	<link>http://cn.reuters.com</link>
	<url>http://cn.reuters.com/resources/images/reuters120.gif</url>
</image>

Give domain object Image correspondingly
import java.io.Serializable;

/**
 * @author <a href="mailto:zlex.dongliang@gmail.com">Beams and pillars</a>
 * @since 1.0
 */
public class Image implements Serializable {
	/**
	 * 
	 */
	private static final long serialVersionUID = 3377367647893337410L;

	/**
	 * high
	 */
	private String height;
	/**
	 * wide
	 */
	private String width;
	/**
	 * link
	 */
	private String link;

	/**
	 * Title
	 */
	private String title;
	/**
	 * Picture Path
	 */
	private String url;
}

import java.io.Serializable; Why?
Considering that these domain objects may need to be cached, we need to involve saving objects to files, that is, serialization operations. Therefore, when we build domain objects, it's best to implement serialized interfaces!
Look at Item again
The following structure is given in RSS
<item>
	<title>Motorola and Verizon Cooperative Development of Digital Flat Panel Equipment--FT</title>
	<description>  LONDON, Aug. 4 (Reuters)---The Financial Times reported Wednesday,Motorola, an American mobile phone manufacturer, is working with Verizon Wireless Cooperative Development of a Digital Flat Panel Equipment,Used with apples. iPad compete.</description>
	<link>http://cn.reuters.com/article/CNTechNews/idCNCHINA-2770720100804?feedType=RSS&amp;feedName=CNTechNews</link>
	<guid isPermaLink="false">CNCHINA-2770720100804</guid>
	<category>CNTechNews</category>
	<pubDate>Wed, 04 Aug 2010 11:08:34 +0800</pubDate>
</item>

Give the corresponding bound domain object
import java.io.Serializable;

/**
 * @author <a href="mailto:zlex.dongliang@gmail.com">Beams and pillars</a>
 * @since 1.0
 * 
 */
public class Item implements Serializable {
	/**
	 * 
	 */
	private static final long serialVersionUID = -8860646418160016186L;

	/**
	 * Title
	 */
	private String title;
	/**
	 * describe
	 */
	private String description;
	/**
	 * link
	 */
	private String link;
	/**
	 * guid
	 */
	private String guid;
	/**
	 * classification
	 */
	private String category;
	/**
	 * Date of publication
	 */
	private String pubDate;
}

Finally, Channel
The following structure is given in RSS, which is also a complete RSS.
<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" >
	<channel>
		<title>Reuters: Science and Technology Electronics</title>
		<link>http://cn.reuters.com</link>
		<description>Reuters Chinese provides real-time news, financial information and investment information. Reuters is one of the largest news agencies in the world, providing news coverage, financial information and related technical solutions to the global media, financial entities, business organizations and individuals.</description>
		<image>
			<title>Reuters News</title>
			<width>120</width>
			<height>35</height>
			<link>http://cn.reuters.com</link>
			<url>http://cn.reuters.com/resources/images/reuters120.gif</url>
		</image>
		<language>en-us</language>
		<lastBuildDate>Wed, 04 Aug 2010 14:03:25 +0800</lastBuildDate>
		<copyright>All rights reserved. Users may download and print extracts of content from this website for their own personal and non-commercial use only. Republication or redistribution of Reuters content, including by framing or similar means, is expressly prohibited without the prior written consent of Reuters. Reuters and the Reuters sphere logo are registered trademarks or trademarks of the Reuters group of companies around the world. &#169; Reuters 2010</copyright>
		<item>
			<title>Motorola and Verizon Cooperative Development of Digital Flat Panel Equipment--FT</title>
			<description>  LONDON, Aug. 4 (Reuters)---The Financial Times reported Wednesday,Motorola, an American mobile phone manufacturer, is working with Verizon Wireless Cooperative Development of a Digital Flat Panel Equipment,Used with apples. iPad compete.</description>
			<link>http://cn.reuters.com/article/CNTechNews/idCNCHINA-2770720100804?feedType=RSS&amp;feedName=CNTechNews</link>
			<guid isPermaLink="false">CNCHINA-2770720100804</guid>
			<category>CNTechNews</category>
			<pubDate>Wed, 04 Aug 2010 11:08:34 +0800</pubDate>
		</item>
	</channel>
</rss>

There is only one image node, but there may be more than one item node!
The corresponding Channel domain objects are as follows:
import java.io.Serializable;
import java.util.List;

/**
 * @author <a href="mailto:zlex.dongliang@gmail.com">Beams and pillars</a>
 * @since 1.0
 * 
 */
public class Channel implements Serializable {
	/**
	 * 
	 */
	private static final long serialVersionUID = 549783894750767576L;

	/**
	 *Title
	 */
	private String title;
	/**
	 * link
	 */
	private String link;
	/**
	 * describe
	 */
	private String description;
	/**
	 * Last release date
	 */
	private String lastBuildDate;
	/**
	 * doc
	 */
	private String docs;
	/**
	 * language
	 */
	private String language;
	/**
	 * copyright
	 */
	private String copyright;
	/**
	 * picture
	 */
	private Image image;

	/**
	 * Item List
	 */
	private List<Item> itemList;

When domain objects are ready, we need to transform xml and implement it through Dom4J. This part of the content, too basic, see the annex for details! (View the RSSReader class)
Today's protagonist is CacheHolder, which maintains cache calls!
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheException;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

import org.apache.log4j.Logger;

/**
 * Cache Controller
 * 
 * @author <a href="mailto:zlex.dongliang@gmail.com">Beams and pillars</a>
 * @since 1.0
 * 
 */
public class CacheHolder {

	/**
	 * Logger for this class
	 */
	private static final Logger logger = Logger.getLogger(CacheHolder.class);

	private static CacheHolder INSTANCE;

	/**
	 * Cache Manager
	 */
	private CacheManager cm;

	/**
	 * cache
	 */
	private Cache cache;

	/**
	 * Getting the Cache Controller
	 * 
	 * @param cacheName
	 * @return
	 */
	public static synchronized CacheHolder getCacheHolder(String cacheName) {

		if (INSTANCE == null) {
			INSTANCE = new CacheHolder(cacheName);
		}
		return INSTANCE;
	}

	/**
	 * @param cacheName
	 */
	private CacheHolder(String cacheName) {
		try {

			cm = CacheManager.create();

		} catch (CacheException e) {
			logger.warn(e.getMessage());
		}

		cache = cm.getCache(cacheName);

		if (cache == null) {
			cache = new Cache("LRU", 500, false, false, 60 * 60 * 24,
					60 * 60 * 12);
			cm.addCache(cache);
		}
	}

	/**
	 * Add cached objects
	 * 
	 * @param key
	 * @param value
	 */
	public void add(Object key, Object value) {
		Element e = new Element(key, value);
		cache.put(e);
		if (logger.isDebugEnabled()) {
			logger.debug("Cache:[" + key + "]");
		}
	}

	/**
	 * Get cached objects
	 * 
	 * @param key
	 */
	public Object get(Object key) {
		Element e = cache.get(key);
		if (e != null) {
			if (logger.isDebugEnabled()) {
				logger.debug("Cache Hit:[" + key + "]");
			}
			return e.getObjectValue();
		}
		return null;
	}

	/**
	 * Delete the specified cache object
	 * 
	 * @param key
	 * @return {@link Object}
	 */
	public void remove(Object key) {
		cache.remove(key);
	}

	/**
	 * Delete all cached objects
	 * 
	 */
	public void removeAll() {
		cache.removeAll();
	}

}

Usually, we need to schedule through the singleton mode:
private static CacheHolder INSTANCE;

	/**
	 * Cache Manager
	 */
	private CacheManager cm;

	/**
	 * Getting the Cache Controller
	 * 
	 * @param cacheName
	 * @return
	 */
	public static synchronized CacheHolder getCacheHolder(String cacheName) {

		if (INSTANCE == null) {
			INSTANCE = new CacheHolder(cacheName);
		}
		return INSTANCE;
	}

When building a singleton pattern, the most important thing is to make the construction method private:
/**
	 * cache
	 */
	private Cache cache;
	/**
	 * @param cacheName
	 */
	private CacheHolder(String cacheName) {
		try {

			cm = CacheManager.create();

		} catch (CacheException e) {
			logger.warn(e.getMessage());
		}

		cache = cm.getCache(cacheName);

		if (cache == null) {
			cache = new Cache("LRU", 500, false, false, 60 * 60 * 24,
					60 * 60 * 12);
			cm.addCache(cache);
		}
	}

The default invocation algorithm is "LRU", which means that the least recently accessed objects will be cleaned up!
The default timeToLiveSeconds is 60 * 60 * 24 = 1 day, which means that the cache is kept for up to one day, once it is cleaned up automatically.
The default timeToIdleSeconds is 60 * 60 * 12=0.5day, which means that the cache is free for up to half a day, and if the object is not accessed within half a day, it will be cleaned up.
If you want to configure flexibly, configure the ehcache.xml file.
<?xml version="1.0" encoding="UTF-8"?>
<ehcache
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:noNamespaceSchemaLocation="ehcache.xsd">
	<diskStore
		path="java.io.tmpdir" />
	<defaultCache
		maxElementsInMemory="10000"
		eternal="false"
		timeToIdleSeconds="120"
		timeToLiveSeconds="120"
		overflowToDisk="true"
		diskPersistent="false"
		diskSpoolBufferSizeMB="30"
		maxElementsOnDisk="10000000"
		diskExpiryThreadIntervalSeconds="120"
		memoryStoreEvictionPolicy="LRU" />
	<cache
		name="com.netqin.tag.rss.Channel"
		maxElementsInMemory="1"
		eternal="false"
		overflowToDisk="true"
		timeToIdleSeconds="3600"
		timeToLiveSeconds="3600"
		memoryStoreEvictionPolicy="LFU" />
</ehcache>

Here through
<cache
		name="com.netqin.tag.rss.Channel"
		maxElementsInMemory="1"
		eternal="false"
		overflowToDisk="true"
		timeToIdleSeconds="3600"
		timeToLiveSeconds="3600"
		memoryStoreEvictionPolicy="LFU" />
Configure com.netqin.tag.rss.Channel to ensure that the cache is refreshed within an hour!
How?
Some RSSReader class implementations are given.
import org.apache.log4j.Logger;

import java.util.LinkedList;
import java.util.List;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

/**
 * RSSReader
 * 
 * @author <a href="mailto:zlex.dongliang@gmail.com">Beams and pillars</a>
 * @since 1.0
 * 
 */
public abstract class RSSReader {
	/**
	 * Logger for this class
	 */
	private static final Logger logger = Logger.getLogger(RSSReader.class);

	private static final CacheHolder cacheHolder = CacheHolder
			.getCacheHolder("com.netqin.tag.rss");
	public static final String KEY = "com.netqin.tag.rss.Channel";

	/**
	 * Access channels
	 * 
	 * @param doc
	 * @return
	 */
	public static Channel getChannel(String url) throws Exception {
		Channel channel = (Channel) cacheHolder.get(KEY);

		if (channel == null) {

			Document doc = getDocument(url);
			channel = toChannel(doc);

			cacheHolder.add(KEY, channel);
		}
		return channel;
	}
}

Use, it's so simple!
Finally, wrap it in tag:
import org.apache.log4j.Logger;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;

/**
 * RSSTag Label
 * 
 * @author <a href="mailto:zlex.dongliang@gmail.com">Beams and pillars</a>
 * @since 1.0
 * 
 */
public class RSSTag extends TagSupport {
	/**
	 * Logger for this class
	 */
	private static final Logger logger = Logger.getLogger(RSSTag.class);

	/**
	 * 
	 */
	private static final long serialVersionUID = -8392115916509318259L;

	public static final String REQUEST_ATTRIBUTE_NAME = "rssChannel";

	/**
	 * Request path
	 */
	private String url;

	/**
	 * request Channel name in Attribute
	 */
	private String channelName;

	/**
	 * @return the channelName
	 */
	public String getChannelName() {
		return channelName == null ? REQUEST_ATTRIBUTE_NAME : channelName;
	}

	/**
	 * @param channelName
	 *            the channelName to set
	 */
	public void setChannelName(String channelName) {
		this.channelName = channelName;
	}

	/**
	 * @param url
	 *            the url to set
	 */
	public void setUrl(String url) {
		this.url = url;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.servlet.jsp.tagext.TagSupport#doStartTag()
	 */
	@Override
	public int doStartTag() throws JspException {
		try {
			Channel channel = RSSReader.getChannel(url);
			this.pageContext.getRequest().setAttribute(getChannelName(),
					channel);
		} catch (Exception e) {
			logger.warn(e.getMessage());
			throw new JspException(e);
		}
		return super.doStartTag();
	}

}

We set the rss site of the request through the url, and specify the name of the Channel object in the Attribute of Request by setting channelName.
Build another TLD:
<?xml version="1.0" encoding="UTF-8"?>
<taglib
	xmlns="http://java.sun.com/xml/ns/j2ee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
	version="2.0">
	<description>Restricts JSP pages to the RSS tag libraries</description>
	<display-name>rssTaglibs</display-name>
	<tlib-version>1.0</tlib-version>
	<short-name>rss</short-name>
	<uri>http://www.zlex.org/tag/rss</uri>

	<!-- <rss:rss/> -->
	<tag>
		<description>&lt;rss:rss/&gt;</description>
		<name>rss</name>
		<tag-class>org.zlex.commons.web.tag.rss.RSSTag</tag-class>
		<body-content>JSP</body-content>
		<attribute>
			<description>Request address</description>
			<name>url</name>
			<required>true</required>
			<rtexprvalue>true</rtexprvalue>
		</attribute>
		<attribute>
			<description>Request Of Attribute Medium Channel Name</description>
			<name>channelName</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
		</attribute>
	</tag>

</taglib>

That's enough!
Note that when packaging, include tld files and include them in the META-INF directory:

So we don't need to configure it in web.xml!
Use in pages
<%@ taglib prefix="rss" uri="http://www.zlex.org/tag/rss"%>

Introduce the tag and write the following code in the JSP:
<rss:rss url="http://cn.reuters.com/rssFeed/CNTechNews/"
	channelName="channel" />
<a href="${channel.link}"><img
	src='<c:url value="${channel.image.url}"/>'
	alt="${channel.image.title}" /></a>
<dt><c:out value="${channel.title}" /></dt>
<dl>
	<c:forEach begin="0" end="2" items="${channel.itemList}" var="item">
		<dd><a href='<c:url value="${item.link}" />'><c:out
			value="${item.title}" /></a></dd>
	</c:forEach>
</dl>

You can get the picture of the beginning of this article!
Is the cache effective?

Details, see the attachment. Next time I need to cache, I don't have to flip around! Ha-ha!

Reproduced in: https://my.oschina.net/mohaiyong/blog/221286

Topics: Java xml Ehcache JSP