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

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 caches = new ArrayList();
		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标签以定义其缓存行为:

@Service
public 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(), 
			   "[email protected]"+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, 
				   "[email protected]_"+System.currentTimeMillis());
		   return user;
	}
}

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

1.3 测试代码与执行结果

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

然后编写测试代码:
@RunWith(SpringJUnit4ClassRunner.class)  
@SpringBootTest(classes = Configuration.class) 
public class SpringCacheTest {
	Logger logger = LoggerFactory.getLogger(SpringCacheTest.class);
	@Autowired
	UserService 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());
	} 
	
	@Test
	public void test2() throws Exception{
		logger.info("invoke userService.clearCache()");
		userService.clearCache();
		
		logger.info("invoke userService.findByUsernameAndEmail(\"wangd\", \"[email protected]\")");
		User user = userService.findByUsernameAndEmail("wangd", "[email protected]");
		
		logger.info("invoke userService.findByUsernameAndEmail(\"wangd\", \"[email protected]\")");
		User user2 = userService.findByUsernameAndEmail("wangd", "[email protected]");
		Assert.assertEquals(user.getId(), user2.getId());
		
		logger.info("invoke userService.findByUsernameAndEmail(\"lm\", \"[email protected]\")");
		User user3 = userService.findByUsernameAndEmail("lm", "[email protected]");
		
		logger.info("invoke userService.findByUsernameAndEmail(\"lm\", \"[email protected]\")");
		User user4 = userService.findByUsernameAndEmail("lm", "[email protected]");
		Assert.assertNotEquals(user3.getId(), user4.getId());
	}
	
	@Test
	public 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:1
springcache.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", "[email protected]")
springcache.service.UserService          : cache2 miss, invoke find by name and email, name:wangd, email:[email protected]
springcache.test.SpringCacheTest         : invoke userService.findByUsernameAndEmail("wangd", "[email protected]")
springcache.test.SpringCacheTest         : invoke userService.findByUsernameAndEmail("lm", "[email protected]")
springcache.service.UserService          : cache2 miss, invoke find by name and email, name:lm, email:[email protected]
springcache.test.SpringCacheTest         : invoke userService.findByUsernameAndEmail("lm", "[email protected]")
springcache.service.UserService          : cache2 miss, invoke find by name and email, name:lm, email:[email protected]
springcache.test.SpringCacheTest         : invoke userService.clearCache()
springcache.test.SpringCacheTest         : invoke userService.findByUsername("wangd123")
springcache.service.UserService          : cache miss, invoke find by name, name:wangd123
springcache.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", "[email protected]"),因为我们对其方法的注释中是这样定义的:

@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中添加如下依赖:

	
            org.springframework
            spring-context-support
        


        
            net.sf.ehcache
            ehcache-core
            2.6.6
	

然后在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,内容如下:

	
	
	 
    
其中指定了持久化到磁盘上的位置"/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 get(Object key, Class 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 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 store = new ConcurrentHashMap();
	
	public MyCache() { 
	  } 
	  
	  public MyCache(String name) { 
	    this.name = name; 
	  } 
	
	@Override
	public String getName() {
		return this.name;
	}
	public void setName(String name){
		this.name = name;
	}


	@Override
	public Object getNativeCache() {
		return store;
	}


	@Override
	public 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")
	@Override
	public  T get(Object key, Class type) {
		ValueWrapper vw = get(key);
		if(vw==null){
			return null;
		}
		return (T)vw.get();
	}


	@SuppressWarnings("unchecked")
	@Override
	public  T get(Object key, Callable valueLoader) {
		ValueWrapper vw = get(key);
		if(vw==null){
			return null;
		}
		return (T)vw.get();
	}


	@Override
	public void put(Object key, Object value) {
		store.put(key, value);
	}


	@Override
	public ValueWrapper putIfAbsent(Object key, Object value) {
		Object existing = this.store.putIfAbsent(key, value);
		return (existing != null ? new SimpleValueWrapper(existing) : null);
	}


	@Override
	public void evict(Object key) {
		store.remove(key);
	}


	@Override
	public void clear() {
		store.clear();
	}


}

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


}

实现完毕,接下来在Configuration中配置我们自己定制的Cache实现方案:
	@Bean(name="myCacheManager")
	public CacheManager myCacheManager(){
		MyCacheManager myCacheManager = new MyCacheManager();
		List caches = new ArrayList();
		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实现原理及核心业务逻辑(三)

你可能感兴趣的:(技术研究,spring相关)