转载 http://blog.csdn.net/mingshuo0615/article/details/6085248
作为一名Java开发人员你一定想知道如何在Spring应用中使用新的Ehcache注解功能;是吧?ehcache-spring-annotatios是获得Apache认证的一个开源项目;它大大简化了在Spring应用中基于业界使用广泛的Ehacche-2.0版本实现缓存的技术,1.1.2版本的ehcache-spring-annotations刚刚发布不久,在本文中,我将会介绍如何在一个web工程时使用ehcache-spring-annotations实现缓存机制。
创建一个Web工程
在本例中,我们将会创建一个基于Spring MVC的web工程,如果您使用的IDE是Eclipse的话请确保您安装了m2eclipse插件(译者注:因为我们的工程基于Maven构建);接着创建一个基于JAVA EE 5 Web应用模型的Maven工程(group id:org.codehaus.mojo.archetypes, artifact id: webapp-jee5)。
上述示例在Eclipse3.6下可以很好的运行。
工程结构
首先我创建了一个简单的web工程,项目中包含一个控制器:MessageController:
- @Controller
- public class MessageController {
- @Autowired(required = true)
- private MessageStorage messageStorage;
- public MessageController(MessageStorage messageStorage) {
- super();
- this.messageStorage = messageStorage;
- }
- public MessageController() {
- }
- @RequestMapping(method = RequestMethod.GET, value = "/message/add")
- public ModelAndView messageForm() {
- return new ModelAndView("message-form", "command", new Message());
- }
- @RequestMapping(method = RequestMethod.POST, value = "/message/add")
- public ModelAndView addMessage(@ModelAttribute Message message) {
- messageStorage.addMessage(message);
- return getMessageById(message.getId());
- }
- @RequestMapping(method = RequestMethod.GET, value = "/message/{id}")
- public ModelAndView getMessageById(@PathVariable("id") long id) {
- Message message = messageStorage.findMessage(id);
- ModelAndView mav = new ModelAndView("message-details");
- mav.addObject("message", message);
- return mav;
- }
- @RequestMapping(method = RequestMethod.GET, value = "/message")
- public ModelAndView getAllMessages() {
- Collection<Message> messages = messageStorage.findAllMessages();
- ModelAndView mav = new ModelAndView("messages");
- mav.addObject("messages", new CollectionOfElements(messages));
- return mav;
- }
- }
上面的控制器依赖于一个简单的DAO对象:MessageStorage,其实现如下:
- public interface MessageStorage {
- Message findMessage(long id);
- Collection<Message> findAllMessages();
- void addMessage(Message message);
- void setDelegate(MessageStorage storageDelegate);
- }
MessageStorage接口的唯一实现类是MemoryMessageStorage:
- @Component
- public class MemoryMessageStorage implements MessageStorage {
- private Map<Long, Message> messages;
- private AtomicLong newID;
- @PostConstruct
- public void initialize() {
- // add some messages
- addMessage(new Message("user:1", "content-1"));
- addMessage(new Message("user:2", "content-2"));
- addMessage(new Message("user:3", "content-3"));
- addMessage(new Message("user:4", "content-4"));
- addMessage(new Message("user:5", "content-5"));
- }
- @Override
- @Cacheable(cacheName = "messageCache")
- public Message findMessage(long id) {
- //...
- }
- @Override
- @Cacheable(cacheName = "messagesCache")
- public Collection<Message> findAllMessages() {
- //...
- }
- @Override
- @TriggersRemove(cacheName = "messagesCache", when = When.AFTER_METHOD_INVOCATION, removeAll = true)
- public void addMessage(Message message) {
- //...
- }
- }
通过如下的所展示的必须的依赖配置之后我们就可以运行这个应用程序了(见下载选项获取完整的应用程序代码)
- <dependencies>
- <dependency>
- <groupId>javax.servlet</groupId>
- <artifactId>servlet-api</artifactId>
- <version>2.5</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>javax.servlet.jsp</groupId>
- <artifactId>jsp-api</artifactId>
- <version>2.1</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>4.8.1</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-webmvc</artifactId>
- <version>3.0.3.RELEASE</version>
- <type>jar</type>
- <scope>compile</scope>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-oxm</artifactId>
- <version>3.0.3.RELEASE</version>
- <type>jar</type>
- <scope>compile</scope>
- </dependency>
- <dependency>
- <groupId>javax.servlet</groupId>
- <artifactId>jstl</artifactId>
- <version>1.2</version>
- <type>jar</type>
- <scope>compile</scope>
- </dependency>
- </dependencies>
介绍基于Spring的web工程中使用Ehcache注解
现在该给项目增加缓存能力了,我们要为MemoryMessageStorage类提供缓存机制。首先,在POM文件中加入所需的依赖:
Spring中Ehcache的注解依赖项:
- <dependency>
- <groupId>com.googlecode.ehcache-spring-annotations</groupId>
- <artifactId>ehcache-spring-annotations</artifactId>
- <version>1.1.2</version>
- <type>jar</type>
- <scope>compile</scope>
- </dependency>
在写本文的时候2.2.0版本的Ehcache也可用了,但是我们使用2.1.0版本的Ehcache,因为Ehcache Annotations for Spring 1.1.2版本是基于2.1.0版本的Ehcache的。
我还填加了SLF4J API实现的依赖(译者注:SLF4J不是具体的日志解决方案,它只服务于各种各样的日志系统。按照官方的说法,SLF4J是一个用于日志系统的简单Facade,允许最终用户在部署其应用时使用其所希望的日志系统。):
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-log4j12</artifactId>
- <version>1.6.1</version>
- <type>jar</type>
- <scope>compile</scope>
- </dependency>
通过上面依赖的配置,我们就可以使用Ehcache Annotations for Spring了,我们现在就如同前面所说的给MemoryMessageStorage添加注解。这里列出几条所需达到的目标:
· 当调用findMessage(long)方法时用名为“messageCache”的名称缓存结果信息。
· 当调用findAllMessages()方法时用名为“messagesCache”的名称缓存结果信息。
· 当调用addMessage(Message)方法时清除所有“messagesCache”名称下的缓存项。
为了达到上面所说的几个目标,我们使用 @Cachable和@TriggersRemove两个注解,见下面介绍:
- @Component
- public class MemoryMessageStorage implements MessageStorage {
- private Map<Long, Message> messages;
- private AtomicLong newID;
- public MemoryMessageStorage() {
- // ...
- }
- @Override
- @Cacheable(cacheName = "messageCache")
- public Message findMessage(long id) {
- // ...
- }
- @Override
- @Cacheable(cacheName = "messagesCache")
- public Collection<Message> findAllMessages() {
- // ...
- }
- @Override
- @TriggersRemove(cacheName = "messagesCache", when = When.AFTER_METHOD_INVOCATION, removeAll = true)
- public void addMessage(Message message) {
- // ...
- }
- }
Spring和Ehcache的配置
注解已经加到位了,接下来我们需要配置这个项目去使它们起作用,通过Spring的配置文件就可以达到这个目的:
- <?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:context="http://www.springframework.org/schema/context"
- xmlns:oxm="http://www.springframework.org/schema/oxm"
- xmlns:mvc="http://www.springframework.org/schema/mvc"
- xmlns:ehcache="http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring"
- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
- http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
- http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd
- http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
- http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring/ehcache-spring-1.1.xsd">
- <ehcache:annotation-driven />
- <ehcache:config cache-manager="cacheManager">
- <ehcache:evict-expired-elements interval="60" />
- </ehcache:config>
- <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>
- <!-- rest of the file omitted -->
- </beans>
最后要做的是添加Ehcache的配置文件,在web应用程序的/WEB-INF目录下面创建ehcache.xml的xml文件:
- <?xml version="1.0" encoding="UTF-8"?>
- <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
- <defaultCache eternal="true" maxElementsInMemory="100" overflowToDisk="false" />
- <cache name="messageCache" maxElementsInMemory="10" eternal="true" overflowToDisk="false" />
- <cache name="messagesCache" maxElementsInMemory="10" eternal="true" overflowToDisk="false" />
- </ehcache>
接下来需要配置缓存manager使它去管理Ehcache的配置,在Spring上下文的配置文件中加入cacheManager 这个Bean的配置,并需要在其中加入configLocation属性:
- <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
- <property name="configLocation" value="/WEB-INF/ehcache.xml"/>
- </bean>
配置完毕,在Tomcat 6应用服务器上运行该应用程序(Run As – Run on server),你会发现有日志输出-如果DEBUG模式被允许的话你会发现类似如下的条目输出:
- DEBUG [net.sf.ehcache.Cache]: Initialised cache: messagesCache
- DEBUG [net.sf.ehcache.Cache]: Initialised cache: messageCache
在项目中做一些调试去观察缓存机制的行为(确保DEBUG日志级别是被允许的)。方法是(去看XML的输出,将“html”替换为“xml”):
获取消息列表 – http://localhost:8080/esa/message.html
通过ID获取消息 – http://localhost:8080/esa/message/{id}.html
添加一条信息(from) – http://localhost:8080/esa/message/add.html
如果您是第一次执行MessageController 类的getMessages()方法,您将会看到:
- DEBUG [com.googlecode.ehcache.annotations.interceptor.EhCacheInterceptor]: Generated key '167334053963' for invocation: ReflectiveMethodInvocation: public abstract java.util.Collection com.goyello.esa.storage.MessageStorage.findAllMessages(); target is of class [com.goyello.esa.storage.MemoryMessageStorage]
- DEBUG [com.goyello.esa.storage.MemoryMessageStorage]: == got all messages, size=5
当第二次调用同一个方法时,上面输出的日志的第二行就不会再出现,因为所有收集的信息都是从Cache中检索出来的。
调用MessageController中的addMessage()方法时清除Cache中的信息很简单,重复上面的步骤,确保cache在之前是被清空的。
单元测试
为了确保缓存机制确实起作用了,而不是只是看到日志,我们创建单元测试去进行测试。为进行这个测试我们修改了MessageStorage接口,我们在其中增加了void setDelegate(MessageStorage storageDelegate)方法;给出的示例是为了检测我们添加的缓存机制确实起作用了;实现类的变化如下(其他所有方法类似):
- @Override
- @Cacheable(cacheName = "messageCache")
- public Message findMessage(long id) {
- LOG.debug("== find message by id={}", id);
- if(storageDelegate != null)
- storageDelegate.findMessage(id);
- return messages.get(id);
- }
为了使测试简单一些我们需要使用了两个依赖:Spring Test 和Mockito:
- <dependency>
- <groupId>org.mockito</groupId>
- <artifactId>mockito-all</artifactId>
- <version>1.8.5</version>
- <type>jar</type>
- <scope>compile</scope>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-test</artifactId>
- <version>3.0.3.RELEASE</version>
- <type>jar</type>
- <scope>compile</scope>
- </dependency>
测试类将依托于SpringJUnit4ClassRunner运行:
- @RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration(locations = { "/spring-context-test.xml" })
- public class CachingTest {
- @Autowired
- ApplicationContext context;
- @Autowired
- CacheManager cacheManager;
- MessageStorage storage;
- MessageStorage storageDelegate;
- MessageController controller;
- @Before
- public void before() throws Exception {
- storageDelegate = Mockito.mock(MessageStorage.class);
- storage = (MessageStorage) context.getBean("messageStorage");
- storage.setDelegate(storageDelegate);
- controller = new MessageController(storage);
- cacheManager.clearAll();
- }
- @Test
- public void testCaching_MessagesCache() {
- controller.getAllMessages();
- controller.getAllMessages();
- verify(storageDelegate, times(1)).findAllMessages();
- }
- @Test
- public void testCaching_MessagesCacheRemove() {
- controller.getAllMessages();
- controller.getAllMessages();
- controller.addMessage(new Message());
- controller.getAllMessages();
- verify(storageDelegate, times(2)).findAllMessages();
- verify(storageDelegate, times(1)).addMessage(any(Message.class));
- }
- @Test
- public void testCaching_MessageCache() {
- controller.getMessageById(1);
- controller.getMessageById(1);
- controller.addMessage(new Message());
- controller.getMessageById(1);
- verify(storageDelegate, times(1)).findMessage(1);
- verify(storageDelegate, times(1)).addMessage(any(Message.class));
- }
- }
本示例是一个模拟的对象用于测试实际的MessageStorage上的调用次数。本测试Spring上下文的配置如下:
- <?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:context="http://www.springframework.org/schema/context" xmlns:ehcache="http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring"
- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
- http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
- http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring/ehcache-spring-1.1.xsd">
- <ehcache:annotation-driven />
- <ehcache:config cache-manager="cacheManager">
- <ehcache:evict-expired-elements interval="60" />
- </ehcache:config>
- <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
- <property name="configLocation" value="classpath:ehcache-test.xml" />
- </bean>
- <bean id="messageStorage" class="com.goyello.esa.storage.MemoryMessageStorage" />
- </beans>
现在我们准备运行创建好的测试用例去验证实际在MemoryMessageStorage上的调用。我们期待的结果是:
总结
使用Ehcache Spring Annotations是很简洁的,通过上述简单的一些步骤我们试图介绍如何在您的应用中使用缓存,我强烈建议您在您的项目中使用本工具之前先去我们的网站上去读一下文档,这样您会了解一些这篇文章没有覆盖到的用法。
参考文献
Ehcache Spring Annotations项目主页– http://code.google.com/p/ehcache-spring-annotations/
Ehcache项目首页– http://ehcache.org/
Spring 3.0参考– http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/
Mockito – http://mockito.org/
Maven2 – http://maven.apache.org/
下载并运行项目
完整的工程代码在这儿可以下载到:demo-project
下载了文件之后,解压缩,进入到工程所在的目录并执行下面的命令(需要安装maven2)
mvn clean tomcat:run
启动您的浏览器去看看应用的执行情况: