目录
简单介绍使用Spring框架Cacheable
一、相关注解
1、@Cacheable注解
可以标记在一个方法上,也可以标记在一个类上。@Cacheable可以指定三个属性,value、key和condition。
value属性是必须指定的,其表示当前方法的返回值是会被缓存在哪个Cache上的,对应Cache的名称。其可以是一个Cache也可以是多个Cache,当需要指定多个Cache时其是一个数组。
key属性是用来指定Spring缓存方法的返回结果时对应的key的。该属性支持SpringEL表达式。当我们没有指定该属性时,Spring将使用默认策略生成key。
condition属性默认为空,表示将缓存所有的调用情形。其值是通过SpringEL表达式来指定的,当为true时表示进行缓存处理;当为false时表示不进行缓存处理,即每次调用该方法时该方法都会执行一次。如下示例表示只有当user的id为偶数时才会进行缓存。如 @Cacheable(value={"users"}, key="#user.id", condition="#user.id%2==0")
2、@CachePut
@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
3、@CacheEvict
@CacheEvict是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。
@CacheEvict可以指定的属性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的语义与@Cacheable对应的属性类似。即value表示清除操作是发生在哪些Cache上的(对应Cache的名称);key表示需要清除的是哪个key,如未指定则会使用默认策略生成的key;condition表示清除操作发生的条件。
二、案例代码
1、service层代码
@Service
public class PersonService {
@Cacheable(value = "personCache")
public Person getPersonByName(String name) {
// 方法内部实现不考虑缓存逻辑,直接实现业务
System.out.println("调用了Service方法");
return getFromDB(name);
}
private Person getFromDB(String name) {
System.out.println("从数据库查询了");
return new Person();
}
}
2、配置CacheManager
<context:component-scan base-package="demo06"/>
<cache:annotation-driven />
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">
<property name="name" value="default"/>
</bean>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">
<property name="name" value="personCache"/>
</bean>
</set>
</property>
</bean>
<cache:annotation-driven />,支持缓存的配置项,这个配置项缺省使用了一个名字叫 cacheManager 的缓存管理器,这个缓存管理器有一个 spring 的缺省实现,即org.springframework.cache.support.SimpleCacheManager。这个缓存管理器实现了我们刚刚自己定义的缓存管理器的逻辑,它须要配置一个属性 caches,即此缓存管理器管理的缓存集合,除了缺省的名字叫 default 的缓存,我们还自己定义了一个名字叫 personCache 的缓存,使用了缺省的内存存储方案 ConcurrentMapCacheFactoryBean,它是基于 java.util.concurrent.ConcurrentHashMap 的一个内存缓存实现方案。
<cache:annotation-driven/>还可以指定一个mode属性,可选值有proxy和aspectj。默认是使用proxy。当mode为proxy时,只有缓存方法在外部被调用的时候Spring Cache才会发生作用,这也就意味着如果一个缓存方法在其声明对象内部被调用时Spring Cache是不会发生作用的。而mode为aspectj时就不会有这种问题。另外使用proxy时,只有public方法上的@Cacheable等标注才会起作用,如果需要非public方法上的方法也可以使用Spring Cache时把mode设置为aspectj。
此外,<cache:annotation-driven/>还可以指定一个proxy-target-class属性,表示是否要代理class,默认为false。我们前面提到的@Cacheable、@cacheEvict等也可以标注在接口上,这对于基于接口的代理来说是没有什么问题的,但是需要注意的是当我们设置proxy-target-class为true或者mode为aspectj时,是直接基于class进行操作的,定义在接口上的@Cacheable等Cache注解不会被识别到,那对应的Spring Cache也不会起作用了。
需要注意的是<cache:annotation-driven/>只会去寻找定义在同一个ApplicationContext下的@Cacheable等缓存注解。
3、测试代码
public class PersonCacheTest {
private PersonService personService;
@Before
public void setUp() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans/application_cache.xml");
personService = context.getBean("personService", PersonService.class);
}
@Test
public void testGetPersonByName() {
System.out.println("第一次查询张三………………");
personService.getPersonByName("张三");
System.out.println("第二次查询李四………………");
personService.getPersonByName("李四");
System.out.println("第三次查询张三………………");
personService.getPersonByName("张三");
}
}
输出结果:
第一次查询张三………………
调用了Service方法
从数据库查询了
第二次查询李四………………
调用了Service方法
从数据库查询了
第三次查询张三………………
从上可以看出,当参数相同时,第二次调用时直接从缓存中获取结果了,service层都没有调用了。
三、键的生成策略
键的生成策略有两种,一种是默认策略,一种是自定义策略。比如上面的案例就是使用了默认策略。
1、默认策略
默认的key生成策略是通过KeyGenerator生成的,其默认策略如下:
a、如果方法没有参数,则使用0作为key。
b、如果只有一个参数的话则使用该参数作为key。
c、如果参数多与一个的话则使用所有参数的hashCode作为key。
d、如果我们需要指定自己的默认策略的话,那么我们可以实现KeyGenerator接口,定义生成key的方法,然后指定我们的Spring Cache使用的KeyGenerator为我们自己定义的KeyGenerator。如下
<bean id="userKeyGenerator" class="com.xxx.cache.UserKeyGenerator"/>
<cache:annotation-driven key-generator="userKeyGenerator"/>
2、自定义策略
自定义策略是指我们可以通过Spring的EL表达式来指定我们的key。这里的EL表达式可以使用方法参数及它们对应的属性。使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”。如下:
@Cacheable(value="users", key="#id")
public User find(Integer id) {}
@Cacheable(value="users", key="#user.id")
public User find(User user) {}
四、自定义缓存
1、首先须要自己实现 Cache 接口。Cache 接口负责实际的缓存逻辑。比如添加键值对、存储、查询和清空等。利用 Cache 接口,我们能够对接不论什么第三方的缓存系统。比如 EHCache、OSCache,甚至一些内存数据库比如 memcache 或者 redis 等。
public class MyCache implements Cache {
private String name;
private Map<String, Person> store = new HashMap<String, Person>();
public void setName(String name) {
this.name = name;
}
public MyCache() {
}
public MyCache(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public Object getNativeCache() {
return store;
}
@Override
public ValueWrapper get(Object key) {
ValueWrapper result = null;
Person person = store.get(key);
if (person != null) {
result = new SimpleValueWrapper(person);
}
return result;
}
@Override
public <T> T get(Object o, Class<T> aClass) {
return null;
}
@Override
public void put(Object key, Object value) {
Person person = (Person) value;
store.put((String) key, person);
}
@Override
public void evict(Object key) {
if (store.containsKey(key)) {
store.remove(key);
}
}
@Override
public void clear() {
store.clear();
}
}
2、其次,我们须要提供一个 CacheManager 接口的实现,这个接口告诉 spring 有哪些 cache 实例,spring 会依据 cache 的名字查找 cache 的实例。
public class MyCacheManager extends AbstractCacheManager {
private Collection<? extends MyCache> caches;
@Override
protected Collection<? extends Cache> loadCaches() {
return this.caches;
}
public void setCaches(Collection<? extends MyCache> caches) {
this.caches = caches;
}
}
3、cacheManage配置,caches属性值赋值
<context:component-scan base-package="demo06"/>
<cache:annotation-driven />
<bean id="cacheManager" class="demo06.MyCacheManager">
<property name="caches">
<set>
<bean class="demo06.MyCache">
<property name="name" value="personCache"/>
</bean>
</set>
</property>
</bean>
运行上面的测试类 PersonCacheTest,可以看到输出一样。
先调用了Cache的get方法查询是否有缓存,如果有的话直接从缓存返回,如果没有则调用service之后再put方法放入缓存。
五、总结
1、spring缓存的常用注解:@Cacheable、@CachePut、@CacheEvict
2、<cache:annotation-driven/>及cacheManage
3、缓存的键生成策略,默认的key生成策略是通过KeyGenerator生成的,我们也可以自己实现该接口;我们也可以通过el表达式来自定义key;
4、自定义缓存三步骤:实现Cache接口的增删改查、实现CacheManage接口对cache(或caches进行管理)、XML文件配置cacheManage;