SpringCache实现原理及核心业务逻辑(一)

来源:互联网 发布:拓扑算法数学建模 编辑:程序博客网 时间:2024/05/20 06:05
SpringCache是SpringFramework3.1引入的新特性,提供了基于注解的缓存配置方法。它本质上不是一个具体的缓存实现方案(例如EHCache),而是一个对缓存使用的抽象,通过在已有代码中打上几个预定义的注释,就可以实现我们希望达到的缓存效果。SpringCache支持跟第三方缓存例如EHCache集成;另外也提供了开箱即用的默认实现,可以直接拿来使用。
SpringCache支持使用SpEL(Spring Expression Language)来定义缓存的key和各种condition,因此具备相当的灵活性,并可以支持非常复杂的语义。
下面先给出一个使用案例,然后通过源码分析其实现原理及核心业务逻辑。


第一部分:使用样例

1 一个简单例子

1.1 配置CacheManager与Cache并启动

由于是springframework的内置功能,使用springcache并不需要额外引入jar包。因此只需要简单的配置就可以启用开箱即用的默认缓存实现。

创建Configuration类,在其中配置CacheManager Bean,并为其创建两个cache(注意cache的名称,在下面需要缓存的方法上打注释配置时需要指定)。
@Configuration@EnableCaching(proxyTargetClass = true)public class Configuration{@Bean(name="simpleCacheManager")public CacheManager simpleCacheManager(){SimpleCacheManager cacheManager = new SimpleCacheManager();List<Cache> caches = new ArrayList<Cache>();ConcurrentMapCache cache1 = new ConcurrentMapCache("mycache");ConcurrentMapCache cache2 = new ConcurrentMapCache("mycache2");caches.add(cache1);caches.add(cache2);cacheManager.setCaches(caches);return cacheManager;}}

Configuration类上的@EnableCaching(proxyTargetClass = true)注释就表示启动SpringCache功能。其中proxyTargetClass=true表示:当需要代理的类是一个接口或者是一个动态生成的代理类时使用JdkProxy代理;而当要代理的类是一个具体类时,使用cglib来代理。假如不设置该属性,则默认使用JdkProxy代理,而JdkProxy能够代理的类必须实现接口,因此如果想要一个没实现接口的类被代理,就必须设置proxyTargetClass = true来使用cglib完成代理。

另外@EnableCaching还有一个属性AdviceMode mode,取值有两个AdviceMode.PROXY和AdviceMode.ASPECTJ,意思是Spring AOP使用代理模式实现还是使用原生AspectJ模式实现,默认是代理模式。在此我们只介绍代理模式。

SimpleCacheManager与ConcurrentMapCache都是SpringCache提供的默认实现。而当我们使用SpringBoot时,由于其spring-boot-autoconfigure模块里对SpringCache做了默认的自动配置,因此我们甚至连CacheManager都不需要配置。仅仅在Configuration类上打上@EnableCaching(proxyTargetClass = true)注释便可以启动springcache了(具体源码分析将在后面章节)。

1.2 在需要缓存的方法上添加对应的注释

光是启用SpringCache并没有用,我们还需要指明在哪些类的哪些方法上需要缓存,以及需要什么样的缓存行为。
SpringCache提供了@Cacheable、@CachePut、@CacheEvict等注释,并支持使用SpEL(Spring Expression Language)来定义缓存的key和各种condition,因此具备相当的灵活性,并可以支持非常复杂的语义。关于SpringCache使用语法的详细说明请参照《Spring Cache抽象详解》。

接下来我们创建一个Service,并在其方法上打上SpringCache标签以定义其缓存行为:

@Servicepublic class UserService {@CacheEvict(value={"mycache", "mycache2"}, allEntries = true)public void clearCache(){}@CachePut(value = "mycache", key = "#user.id")    public User save(User user) {        return user;    }@CacheEvict(value = "mycache", key = "#user.id") //移除指定key的数据    public User delete(User user) {        return user;    }@Cacheable(value = "mycache", key = "#id")    public User findById(final Long id) {       System.out.println("cache miss, invoke find by id, id:" + id);   Random random = new Random();   User user = new User(id,    "wangd_"+random.nextInt(10000)+"_"+System.currentTimeMillis(),    "wd@123.com"+random.nextInt(10000)+"_"+System.currentTimeMillis());   return user;    }@Cacheable(value="mycache2", key = "#username.concat(#email)", condition = "#username eq 'wangd'")public User findByUsernameAndEmail(String username, String email){Random random = new Random();System.out.println("cache2 miss, invoke find by name and email, name:" + username + ", email:"+email);   User user = new User(System.currentTimeMillis()+random.nextInt(10000),    username,    email);   return user;}@Cacheable(value = "mycache2", key = "#username")public User findByUsername(String username){Random random = new Random();System.out.println("cache miss, invoke find by name, name:" + username);User user = new User(System.currentTimeMillis()+random.nextInt(10000),    username,    "mytestemail@123.com_"+System.currentTimeMillis());   return user;}}

至此,SpringCache已经可以使用了,下面我们编写测试代码来验证。

1.3 测试代码与执行结果

使用springboottest可以方便的对容器内的服务进行测试,首先在pom.xml中加入
<dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-test</artifactId>  </dependency>

然后编写测试代码:
@RunWith(SpringJUnit4ClassRunner.class)  @SpringBootTest(classes = Configuration.class) public class SpringCacheTest {Logger logger = LoggerFactory.getLogger(SpringCacheTest.class);@AutowiredUserService userService;@Test    public void test() throws IOException {    logger.info("invoke userService.clearCache()");userService.clearCache();logger.info("invoke userService.findById(1L)");User user = userService.findById(1L);    //从缓存读数据        Assert.assertNotNull(user);        logger.info("invoke userService.findById(1L)");    User user2 = userService.findById(1L);    Assert.assertEquals(user.getEmail(), user2.getEmail());    User uu = new User(user.getId(), user.getName(), user.getEmail());    uu.setEmail("new_email_addr");        logger.info("invoke userService.save(uu)");    userService.save(uu);        logger.info("invoke userService.findById(1L)");    User user3 = userService.findById(1L);    Assert.assertEquals(uu.getEmail(), user3.getEmail());    Assert.assertNotEquals(user2.getEmail(), user3.getEmail());} @Testpublic void test2() throws Exception{logger.info("invoke userService.clearCache()");userService.clearCache();logger.info("invoke userService.findByUsernameAndEmail(\"wangd\", \"wd@123.com\")");User user = userService.findByUsernameAndEmail("wangd", "wd@123.com");logger.info("invoke userService.findByUsernameAndEmail(\"wangd\", \"wd@123.com\")");User user2 = userService.findByUsernameAndEmail("wangd", "wd@123.com");Assert.assertEquals(user.getId(), user2.getId());logger.info("invoke userService.findByUsernameAndEmail(\"lm\", \"lm@123.com\")");User user3 = userService.findByUsernameAndEmail("lm", "lm@123.com");logger.info("invoke userService.findByUsernameAndEmail(\"lm\", \"lm@123.com\")");User user4 = userService.findByUsernameAndEmail("lm", "lm@123.com");Assert.assertNotEquals(user3.getId(), user4.getId());}@Testpublic void test3() throws Exception{logger.info("invoke userService.clearCache()");userService.clearCache();logger.info("invoke userService.findByUsername(\"wangd123\")");User user = userService.findByUsername("wangd123");logger.info("invoke userService.findByUsername(\"wangd123\")");User user2 = userService.findByUsername(user.getName());logger.info("invoke userService.clearCache()");userService.clearCache();logger.info("invoke userService.findByUsername(\"wangd123\")");User user3 = userService.findByUsername(user.getName());Assert.assertEquals(user.getId(), user2.getId());Assert.assertNotEquals(user.getId(), user3.getId());}}


Run As Junit Test运行测试程序,测试通过并输出结果如下:
springcache.test.SpringCacheTest         : invoke userService.clearCache()springcache.test.SpringCacheTest         : invoke userService.findById(1L)springcache.service.UserService          : cache miss, invoke find by id, id:1springcache.test.SpringCacheTest         : invoke userService.findById(1L)springcache.test.SpringCacheTest         : invoke userService.save(uu)springcache.test.SpringCacheTest         : invoke userService.findById(1L)springcache.test.SpringCacheTest         : invoke userService.clearCache()springcache.test.SpringCacheTest         : invoke userService.findByUsernameAndEmail("wangd", "wd@123.com")springcache.service.UserService          : cache2 miss, invoke find by name and email, name:wangd, email:wd@123.comspringcache.test.SpringCacheTest         : invoke userService.findByUsernameAndEmail("wangd", "wd@123.com")springcache.test.SpringCacheTest         : invoke userService.findByUsernameAndEmail("lm", "lm@123.com")springcache.service.UserService          : cache2 miss, invoke find by name and email, name:lm, email:lm@123.comspringcache.test.SpringCacheTest         : invoke userService.findByUsernameAndEmail("lm", "lm@123.com")springcache.service.UserService          : cache2 miss, invoke find by name and email, name:lm, email:lm@123.comspringcache.test.SpringCacheTest         : invoke userService.clearCache()springcache.test.SpringCacheTest         : invoke userService.findByUsername("wangd123")springcache.service.UserService          : cache miss, invoke find by name, name:wangd123springcache.test.SpringCacheTest         : invoke userService.findByUsername("wangd123")springcache.test.SpringCacheTest         : invoke userService.clearCache()springcache.test.SpringCacheTest         : invoke userService.findByUsername("wangd123")springcache.service.UserService          : cache miss, invoke find by name, name:wangd123

根据打印的日志,对cache使用的效果一目了然,当cache没有命中时会打出一句cacheX miss, invoke find by.......这样的日志。findById、findByUsernameAndEmail、和findByUsername等方法在使用相同参数调用时,都只会在第一次cache没命中时实际执行方法,后面的调用都是直接使用缓存了。除了findByUsernameAndEmail("lm", "lm@123.com"),因为我们对其方法的注释中是这样定义的:

@Cacheable(value="mycache2", key = "#username.concat(#email)", condition = "#username eq 'wangd'")public User findByUsernameAndEmail(String username, String email)
意思是,只有当username的值是'wangd'时才会将结果缓存。

2 与ehcache集成

上面我们使用的是SpringCache自带的开箱即用的实现,内部使用了ConcurrentHashMap来实现。在生产环境中我们可能需要更给力的缓存实现方案,接下来我们将集成EHCache。
当使用EHCache时我们需要引入ehcache-core的类库并需要spring-context-support类库的支持,在pom.xml中添加如下依赖:

<dependency>            <groupId>org.springframework</groupId>            <artifactId>spring-context-support</artifactId>        </dependency>        <dependency>            <groupId>net.sf.ehcache</groupId>            <artifactId>ehcache-core</artifactId>            <version>2.6.6</version></dependency>

然后在Configuration中使用EhCacheCacheManager来定义CacheManager Bean:
@Bean    public CacheManager cacheManager() {            try {            net.sf.ehcache.CacheManager ehcacheCacheManager                    = new net.sf.ehcache.CacheManager(new ClassPathResource("ehcache.xml").getInputStream());                EhCacheCacheManager cacheCacheManager = new EhCacheCacheManager(ehcacheCacheManager);            return cacheCacheManager;        } catch (IOException e) {            throw new RuntimeException(e);        }    }

其中使用到了ehcache.xml文件来对ehcache做一些配置,在src/main/resources下面创建ehcache.xml,内容如下:
<ehcache updateCheck="false"><diskStore path="/home/wangd/data/ehcache2" /><defaultCache maxElementsInMemory="10000" eternal="false"     timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" /><cache name="mycache"         maxEntriesLocalHeap="10000"         overflowToDisk="false"         eternal="false"         diskPersistent="false"         timeToLiveSeconds="0"         timeToIdleSeconds="0"         statistics="true"/>     <cache name="mycache2"         maxEntriesLocalHeap="10000"         overflowToDisk="false"         eternal="false"         diskPersistent="false"         timeToLiveSeconds="0"         timeToIdleSeconds="0"         statistics="true"/></ehcache>
其中指定了持久化到磁盘上的位置"/home/wangd/data/ehcache2",并定义了两个Cache分别命名为mycache和mycache2,与在UserService各方法中指定的cache名相对应。
再次Run As Junit Test运行测试程序,发现测试结果与上面是一致的。

3 自己定制CacheManager和Cache

SpringCache本质上是一个对缓存使用的抽象,将存储的具体实现方案从缓存执行动作及流程中提取出来。缓存流程中面向的两个抽象接口是CacheManager、Cache。其中Cache提供了缓存操作的读取/写入/移除等方法,本着面向抽象编程的原则,内部将缓存对象统一封装成ValueWrapper。Cache接口代码如下:

public interface Cache {        String getName();  //缓存的名字        Object getNativeCache(); //得到底层使用的缓存,如Ehcache        ValueWrapper get(Object key); //根据key得到一个ValueWrapper,然后调用其get方法获取值        <T> T get(Object key, Class<T> type);//根据key,和value的类型直接获取value        void put(Object key, Object value);//往缓存放数据        void evict(Object key);//从缓存中移除key对应的缓存        void clear(); //清空缓存            interface ValueWrapper { //缓存值的Wrapper            Object get(); //得到真实的value            }    } 

由于在应用中可能定义多个Cache,因此提供了CacheManager抽象,用于缓存的管理,接口代码如下:

import java.util.Collection;    public interface CacheManager {        Cache getCache(String name); //根据Cache名字获取Cache         Collection<String> getCacheNames(); //得到所有Cache的名字    }


任何实现了这两个接口的缓存方案都可以直接配置进SpringCache使用。其自带的SimpleCacheManager、ConcurrentMapCache是如此;使用ehcache作为存储实现的EhCacheCacheManager、EhCacheCache也是如此。我们可以自己实现CacheManager与Cache并将其集成进来。

为了方便展示,我们自定义缓存实现方案只实现最简单的功能,cache内部使用ConcurrentHashMap做为存储方案,使用默认实现SimpleValueWrapper,MyCache代码如下:
public class MyCache implements Cache {final static Logger logger = LoggerFactory.getLogger(MyCache.class);String name;Map<Object, Object> store = new ConcurrentHashMap<Object, Object>();public MyCache() {   }     public MyCache(String name) {     this.name = name;   } @Overridepublic String getName() {return this.name;}public void setName(String name){this.name = name;}@Overridepublic Object getNativeCache() {return store;}@Overridepublic ValueWrapper get(Object key) {ValueWrapper result = null;     Object thevalue = store.get(key);     if(thevalue!=null) {     logger.info("["+name+"]got cache, key:"+key);      result = new SimpleValueWrapper(thevalue);     }else{    logger.info("["+name+"]missing cache, key:"+key);    }    return result;}@SuppressWarnings("unchecked")@Overridepublic <T> T get(Object key, Class<T> type) {ValueWrapper vw = get(key);if(vw==null){return null;}return (T)vw.get();}@SuppressWarnings("unchecked")@Overridepublic <T> T get(Object key, Callable<T> valueLoader) {ValueWrapper vw = get(key);if(vw==null){return null;}return (T)vw.get();}@Overridepublic void put(Object key, Object value) {store.put(key, value);}@Overridepublic ValueWrapper putIfAbsent(Object key, Object value) {Object existing = this.store.putIfAbsent(key, value);return (existing != null ? new SimpleValueWrapper(existing) : null);}@Overridepublic void evict(Object key) {store.remove(key);}@Overridepublic void clear() {store.clear();}}

然后是MyCacheManager:
public class MyCacheManager extends AbstractCacheManager {private Collection<? extends MyCache> caches;     /**   * Specify the collection of Cache instances to use for this CacheManager.   */   public void setCaches(Collection<? extends MyCache> caches) {     this.caches = caches;   }    @Override   protected Collection<? extends MyCache> loadCaches() {     return this.caches;   } }

实现完毕,接下来在Configuration中配置我们自己定制的Cache实现方案:
@Bean(name="myCacheManager")public CacheManager myCacheManager(){MyCacheManager myCacheManager = new MyCacheManager();List<MyCache> caches = new ArrayList<MyCache>();MyCache mycache = new MyCache("mycache");MyCache mycache2 = new MyCache("mycache2");caches.add(mycache);caches.add(mycache2);myCacheManager.setCaches(caches);return myCacheManager;}

完成以上步骤后再次Run As Junit Test运行测试程序,发现测试结果与上面两次都是一致的。


小结: 至此,对SpringCache的配置使用做了一个介绍。其中,我们首先使用了SpringCache自带的开箱即用的存储实现方案、然后集成了EHCache的存储实现方案、最后定制并集成了自己的存储实现方案。由此可见SpringCache本质上是一个对缓存使用的抽象。它并不会要求你使用什么具体的存储实现方案,而是提供了非常便利的方式允许各种存储方案轻松集成进来。下面的章节我们将分析SpringCache的源码以了解其实现原理及核心处理逻辑。




相关文章:SpringCache实现原理及核心业务逻辑(二)

相关文章:SpringCache实现原理及核心业务逻辑(三)