Spring 緩存機制


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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM