由于是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了(具体源码分析将在后面章节)。@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;
}
}
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());
}
}
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'时才会将结果缓存。
org.springframework
spring-context-support
net.sf.ehcache
ehcache-core
2.6.6
@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);
}
}
其中指定了持久化到磁盘上的位置"/home/wangd/data/ehcache2",并定义了两个Cache分别命名为mycache和mycache2,与在UserService各方法中指定的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
}
}
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
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;
}
}
@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;
}
小结: 至此,对SpringCache的配置使用做了一个介绍。其中,我们首先使用了SpringCache自带的开箱即用的存储实现方案、然后集成了EHCache的存储实现方案、最后定制并集成了自己的存储实现方案。由此可见SpringCache本质上是一个对缓存使用的抽象。它并不会要求你使用什么具体的存储实现方案,而是提供了非常便利的方式允许各种存储方案轻松集成进来。下面的章节我们将分析SpringCache的源码以了解其实现原理及核心处理逻辑。
相关文章:SpringCache实现原理及核心业务逻辑(二)
相关文章:SpringCache实现原理及核心业务逻辑(三)