轉載 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
啟動您的瀏覽器去看看應用的執行情況: