SpringCache实现原理及核心业务逻辑(一)
来源:互联网 发布:拓扑算法数学建模 编辑:程序博客网 时间:2024/05/20 06:05
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实现原理及核心业务逻辑(三)
- SpringCache实现原理及核心业务逻辑(一)
- SpringCache实现原理及核心业务逻辑(二)
- SpringCache实现原理及核心业务逻辑(三)
- opencart源码学习(一)——业务逻辑核心controller
- 什么是业务逻辑层(业务层)及业务逻辑层(业务层)的功能
- opencart 业务逻辑核心controller
- SpringCache--介绍(一)
- 自己写“俄罗斯方块”(一).实现基本业务逻辑和绘图
- 逻辑回归原理介绍及Matlab实现
- 递归实现汉诺塔原理及核心代码
- jsonp实现原理及核心代码例子
- 业务逻辑实现方式选择
- HTTP 代理原理及实现(一)
- HTTP 代理原理及实现(一)
- HTTP 代理原理及实现(一)
- SpringAOP基本概念及实现原理(一)
- 银行核心业务系统研发日记(一)
- 引进国外银行核心业务系统的讨论(一)
- python参数
- POJ3660---Cow Contest(Floyd,传递闭包,连通)
- Flink DataSet API 使用示范
- 判断一个链表是否是回文结构 Python 版
- Linux top命令的用法详细详解
- SpringCache实现原理及核心业务逻辑(一)
- Java容器类小结
- BFS+优先队列——迷宫最短路径——两种最优方法比较及详细图解
- 再见了,我深爱过的单片机------单片机带我入门软件, 也最能揭示硬件与软件的关系
- Linux上Oracle 11g启动与关闭
- linux小技巧
- get与post的区别
- 在CLion中使用Qt
- python学习之 输入 输出