Spring的緩存機制非常靈活,可以對容器中任意Bean或者Bean的方法進行緩存,因此這種緩存機制可以在JavaEE應用的任何層次上進行緩存。
Spring緩存底層也是需要借助其他緩存工具來實現,例如EhCache(Hibernate緩存工具),上層則以統一API編程。
要使用Spring緩存,需要以下三步
- 1.向Spring配置文件導入context:命名空間
- 2.在Spring配置文件啟用緩存,具體是添加 <cache:annotation-driven cache-manager="緩存管理器ID" />
- 3.配置緩存管理器,不同的緩存實現配置不同,如果是EhCache,需要先配置一個ehcache.xml
例如
1 <?xml version="1.0" encoding="UTF-8"?> 2 <ehcache> 3 <diskStore path="java.io.tmpdir" /> 4 <!-- 配置默認的緩存區 --> 5 <defaultCache 6 maxElementsInMemory="10000" 7 eternal="false" 8 timeToIdleSeconds="120" 9 timeToLiveSeconds="120" 10 maxElementsOnDisk="10000000" 11 diskExpiryThreadIntervalSeconds="120" 12 memoryStoreEvictionPolicy="LRU"/> 13 <!-- 配置名為users的緩存區 --> 14 <cache name="users" 15 maxElementsInMemory="10000" 16 eternal="false" 17 overflowToDisk="true" 18 timeToIdleSeconds="300" 19 timeToLiveSeconds="600" /> 20 </ehcache>
上面的ehcache.xml配置了兩個緩存區,Spring中的Bean將會緩存在這些緩存區中,一般的,Spring容器中有多少個Bean,就會在ehcache中定義多少個緩存區。
接着在Spring配置文件中配置緩存管理器如下,其中第一個Bean是一個工廠Bean,用來配置EhCache的CacheManager, 第二個Bean才是為Spring緩存配置的緩存管理器,所以將第一個Bean注入第二個Bean。
1 <cache:annotation-driven cache-manager="cacheManager" /> 2 3 <!-- 配置EhCache的CacheManager 4 通過configLocation指定ehcache.xml文件的位置 --> 5 <bean id="ehCacheManager" 6 class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" 7 p:configLocation="classpath:ehcache.xml" 8 p:shared="false" /> 9 <!-- 配置基於EhCache的緩存管理器 10 並將EhCache的CacheManager注入該緩存管理器Bean --> 11 <bean id="cacheManager" 12 class="org.springframework.cache.ehcache.EhCacheCacheManager" 13 p:cacheManager-ref="ehCacheManager" > 14 </bean>
下面是一個完整的Spring配置,
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:p="http://www.springframework.org/schema/p" 5 xmlns:cache="http://www.springframework.org/schema/cache" 6 xmlns:context="http://www.springframework.org/schema/context" 7 xsi:schemaLocation="http://www.springframework.org/schema/beans 8 http://www.springframework.org/schema/beans/spring-beans-4.0.xsd 9 http://www.springframework.org/schema/cache 10 http://www.springframework.org/schema/cache/spring-cache-4.0.xsd 11 http://www.springframework.org/schema/context 12 http://www.springframework.org/schema/context/spring-context-4.0.xsd"> 13 14 <context:component-scan 15 base-package="com.service"/> 16 17 <cache:annotation-driven cache-manager="cacheManager" /> 18 19 <!-- 配置EhCache的CacheManager 20 通過configLocation指定ehcache.xml文件的位置 --> 21 <bean id="ehCacheManager" 22 class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" 23 p:configLocation="classpath:ehcache.xml" 24 p:shared="false" /> 25 <!-- 配置基於EhCache的緩存管理器 26 並將EhCache的CacheManager注入該緩存管理器Bean --> 27 <bean id="cacheManager" 28 class="org.springframework.cache.ehcache.EhCacheCacheManager" 29 p:cacheManager-ref="ehCacheManager" > 30 </bean> 31 32 </beans>
下面將以@Cacheable為例,演示Spring基於EhCache緩存的用法。@Cacheable用於修飾類或者方法,如果修飾類,則類中所有方法都會被緩存。
類級別的緩存
例如有如下Bean類,
1 @Service("userService") 2 @Cacheable(value="users") 3 public class UserServiceImpl implements UserService { 4 5 @Override 6 public User getUsersByNameAndAge(String name, int age) { 7 System.out.println("正在執行getUsersByNameAndAge().."); 8 return new User(name,age); 9 } 10 11 @Override 12 public User getAnotherUser(String name, int age) { 13 System.out.println("正在執行getAnotherUser().."); 14 return new User(name,age); 15 } 16 }
基於類的緩存,將會緩存類中的所有方法,緩存之后,程序調用該類實例的任何方法,只要傳入的參數相同,Spring將不會真正執行該方法,而是直接根據傳入的參數去查找緩存中的數據!
比如像下面這樣使用緩存數據,
1 public static void test2() { 2 ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); 3 UserService us = ctx.getBean("userService", UserService.class); 4 User u1 = us.getUsersByNameAndAge("張三", 50); 5 //由於第二次調用userService方法時,使用了相同參數,那么真正的方法將不會執行, 6 //Spring將直接從緩存按參數查找數據 7 User u2 = us.getAnotherUser("張三", 50); 8 System.out.println(u1==u2); 9 }
輸出結果,
1 正在執行getUsersByNameAndAge().. 2 true
可以看到,上面的getAnotherUser()並沒有真正執行,因為傳入的參數與之前的方法傳入的參數相同,於是Spring直接從緩存區數據了。
上面的Bean類中的注解@Cacheable除了必選屬性value之外,還有key, condition,, unless屬性,后面三個都是用來設置Spring存儲策略,對於基於類的緩存來說,Spring默認以方法傳入的參數作為key去緩存中查找結果。
當然我們也可以修改key的策略,讓Spring按照其他標准,比如按照第一個參數是否相同來作為key,在緩存中查找結果。
將上面的Bean類修改如下,
1 @Service("userService") 2 @Cacheable(value="users", key="#name") 3 public class UserServiceImpl implements UserService { 4 5 @Override 6 public User getUsersByNameAndAge(String name, int age) {
意味着我們傳入相同的name,Spring就不會真正執行方法。只有name不同的時候,方法才會真正執行,例如下面,
1 public static void test2() { 2 ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); 3 UserService us = ctx.getBean("userService", UserService.class); 4 User u1 = us.getUsersByNameAndAge("張三", 50); 5 //將@Cacheable的key參數改為key="#name"之后,下面的方法將可以執行。 6 User u2 = us.getAnotherUser("李四", 50); 7 System.out.println(u1==u2); 8 }
可以看到這回getAnotherUser()方法得到執行了,
1 正在執行getUsersByNameAndAge().. 2 正在執行getAnotherUser().. 3 false
我們也可以設置condition屬性,例如,
1 @Service("userService") 2 @Cacheable(value="users", condition="#age<100") 3 public class UserServiceImpl implements UserService { 4 5 @Override 6 public User getUsersByNameAndAge(String name, int age) {
那么對於下面的代碼來說,兩個方法都不會被緩存,Spring每次都是執行真正的方法取結果,
1 public static void test2() { 2 ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); 3 UserService us = ctx.getBean("userService", UserService.class); 4 User u1 = us.getUsersByNameAndAge("張三", 500); 5 User u2 = us.getAnotherUser("李四", 500); 6 System.out.println(u1==u2); 7 }
執行結果,
1 正在執行getUsersByNameAndAge().. 2 正在執行getAnotherUser().. 3 false
方法級別的緩存
方法級別的緩存則只會對方法起作用了,不同的方法可以設置不用的緩存區,例如下面這樣,
1 @Service("userService") 2 public class UserServiceImpl implements UserService { 3 4 @Cacheable("users1") 5 @Override 6 public User getUsersByNameAndAge(String name, int age) { 7 System.out.println("正在執行getUsersByNameAndAge().."); 8 return new User(name,age); 9 } 10 11 @Cacheable("users2") 12 @Override 13 public User getAnotherUser(String name, int age) { 14 System.out.println("正在執行getAnotherUser().."); 15 return new User(name,age); 16 } 17 }
使用下面的測試代碼,
1 public static void test2() { 2 ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); 3 UserService us = ctx.getBean("userService", UserService.class); 4 //第一次執行方法,方法將會真正執行並緩存 5 User u1 = us.getUsersByNameAndAge("張三", 500); 6 //雖然下面方法傳入相同參數,但是因為這兩個方法在不同的緩存區,所以無法使用緩存數據 7 User u2 = us.getAnotherUser("張三", 500); 8 System.out.println(u1==u2); 9 //上面已經緩存過,這里不會真正執行,直接使用緩存 10 User u3 = us.getAnotherUser("張三", 500); 11 System.out.println(u3==u2); 12 }
執行結果,
1 正在執行getUsersByNameAndAge().. 2 正在執行getAnotherUser().. 3 false 4 true
使用@CacheEvict清除緩存
被@CacheEvict修飾的方法可以用來清除緩存,使用@CacheEvict可以指定如下屬性。
allEntries, 是否清空整個緩存區
beforeInvocation: 是否在執行方法之前清除緩存。默認是方法執行成功之后才清除。
condiition以及key, 與@Cacheable中一樣的含義。
下面示范簡單用啊,
1 @Service("userService") 2 @Cacheable("users") 3 public class UserServiceImpl implements UserService { 4 5 @Override 6 public User getUsersByNameAndAge(String name, int age) { 7 System.out.println("正在執行getUsersByNameAndAge().."); 8 return new User(name,age); 9 } 10 11 @Override 12 public User getAnotherUser(String name, int age) { 13 System.out.println("正在執行getAnotherUser().."); 14 return new User(name,age); 15 } 16 //指定根據name,age參數清楚緩存 17 @CacheEvict(value="users") 18 public void evictUser(String name, int age) { 19 System.out.println("--正在清空"+name+","+age+"對應的緩存--"); 20 } 21 22 //指定清除user緩存區所有緩存的數據 23 @CacheEvict(value="users", allEntries=true) 24 public void evictAll() { 25 System.out.println("--正在清空整個緩存--"); 26 } 27 }
下面是測試類,
1 public static void test2() { 2 ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); 3 UserService us = ctx.getBean("userService", UserService.class); 4 //系統會緩存兩個方法 5 User u1 = us.getUsersByNameAndAge("張三", 500); 6 User u2 = us.getAnotherUser("李四",400); 7 //調用evictUser()方法清除緩沖區指定的數據 8 us.evictUser("李四", 400); 9 //前面清除了 李四, 400 的緩存,下面的方法返回的數據將會再次被緩存 10 User u3 = us.getAnotherUser("李四", 400); 11 System.out.println(us == u3); //false 12 //前面已經緩存了 張三, 500的數據,下面方法不會重新執行,直接取緩存中的數據 13 User u4 = us.getAnotherUser("張三", 500); 14 System.out.println(u1==u4); //輸出true 15 //清空整個緩存 16 us.evictAll(); 17 //由於整個緩存都已經被清空,下面的代碼都會被重新執行 18 User u5 = us.getAnotherUser("張三", 500); 19 User u6 = us.getAnotherUser("李四", 400); 20 System.out.println(u1==u5); //輸出false 21 System.out.println(u3==u6); //輸出false 22 }
執行結果,
1 正在執行getUsersByNameAndAge().. 2 正在執行getAnotherUser().. 3 --正在清空李四,400對應的緩存-- 4 正在執行getAnotherUser().. 5 false 6 true 7 --正在清空整個緩存-- 8 正在執行getAnotherUser().. 9 正在執行getAnotherUser().. 10 false 11 false