spring的Cache注解和redis的区别说明
作者:吧唧小猪
spring Cache注解和redis区别
1.不支持TTL
即不能设置过期时间 expires time,SpringCache 认为这是各个Cache实现自己去完成的事情,有方案但是只能设置统一的过期时间,明显不够灵活。
2.内部调用
非 public 方法上使用注解,会导致缓存无效。内部调用方法的时候不会调用cache方法。
由于 SpringCache 是基于 Spring AOP 的动态代理实现,由于代理本身的问题,当同一个类中调用另一个方法,会导致另一个方法的缓存不能使用,这个在编码上需要注意,避免在同一个类中这样调用。如果非要这样做,可以通过再次代理调用,如 ((Category)AopContext.currentProxy()).get(category) 这样避免缓存无效。
3.key的问题
在清除缓存的时候,无法指定多个缓存块,同时清除多个缓存的key。
Spring Cache注解+redis整合及遇到的坑
背景:项目经理让我做缓存,我心想不是有redis吗?他说你把Spring Cache注解结合进来,我只好硬着头皮来做。
先介绍Spring Cache注解
从3.1开始,Spring引入了对Cache的支持。其使用方法和原理都类似于Spring对事务管理的支持。Spring Cache是作用在方法上的,其核心思想是这样的:当我们在调用一个缓存方法时会把该方法参数和返回结果作为一个键值对存放在缓存中,等到下次利用同样的参数来调用该方法时将不再执行该方法,而是直接从缓存中获取结果进行返回。所以在使用Spring Cache的时候我们要保证我们缓存的方法对于相同的方法参数要有相同的返回结果。
Spring为我们提供了几个注解来支持Spring Cache。其核心主要是@Cacheable和@CacheEvict。使用@Cacheable标记的方法在执行后Spring Cache将缓存其返回结果,而使用@CacheEvict标记的方法会在方法执行前或者执行后移除Spring Cache中的某些元素。下面我们将来详细介绍一下Spring基于注解对Cache的支持所提供的几个注解。
根据项目情况只用到@Cacheable,这里面一共有几个常用的参数,一个是value,这一定必须指定其表示当前方法的返回值是会被缓存在哪个Cache上的,对应Cache的名称。其可以是一个Cache也可以是多个Cache,当需要指定多个Cache时其是一个数组。
还有就是key,key属性是用来指定Spring缓存方法的返回结果时对应的key的。该属性支持SpringEL表达式。当我们没有指定该属性时,Spring将使用默认策略生成key。
我们这里先来看看自定义策略,至于默认策略会在后文单独介绍自定义策略是指我们可以通过Spring的EL表达式来指定我们的key。
这里的EL表达式可以使用方法参数及它们对应的属性。使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”。最后一个就是condition,有的时候我们可能并不希望缓存一个方法所有的返回结果。通过condition属性可以实现这一功能。condition属性默认为空,表示将缓存所有的调用情形。
其值是通过SpringEL表达式来指定的,当为true时表示进行缓存处理;当为false时表示不进行缓存处理,即每次调用该方法时该方法都会执行一次。还有几个别的注解@CacheEvict,@CachePut,基本跟@Cacheable一样只是作用不一样换汤不换药。
当你看到这的时候我相信你,你已经对Spring缓存注解了解的很多了。下面就来配置Spring注解与Redis来整合。(本人不做配置Redis)
配置Spring注解与Redis整合
先导入jar包
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>1.6.0.RELEASE</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.3.2</version> </dependency>
我们在Spring-config.xml中来配置
xmlns:cache="http://www.springframework.org/schema/cache" http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.2.xsd" <cache:annotation-driven cache-manager="cacheManager" /> //这句话一定要加上,要不注解不管用
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager"> <property name="caches"> <set> <bean class="com.zswy.mkedu.utils.RedisCache"> <property name="redisTemplate" ref="redisTemplate" /> <!--name一定要与你注解那个方法中value一样,否则找不到--> <property name="name" value="courseCache" /> </bean> </set> </property> </bean>
来写RedisCache这个类,让其实现Cache接口
public class RedisCache implements Cache{ private RedisTemplate<String, Object> redisTemplate; private String name; public RedisTemplate<String, Object> getRedisTemplate() { return redisTemplate; } public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) { this.redisTemplate = redisTemplate; } public void setName(String name) { this.name = name; } public String getName() { return this.name; } public Object getNativeCache() { return this.redisTemplate; } public ValueWrapper get(Object key) { final String keyf = (String) key; Object object = null; object = redisTemplate.execute(new RedisCallback<Object>() { public Object doInRedis(RedisConnection connection) throws DataAccessException { byte[] key = keyf.getBytes(); byte[] t = name.getBytes(); byte[] value = connection.hGet(t, key); if (value == null) { return null; } return toObject(value); } }); ValueWrapper obj=(object != null ? new SimpleValueWrapper(object) : null); return obj; } public <T> T get(Object o, Class<T> aClass) { return null; } public void put(Object key, Object value) { final String keyf = (String) key; final Object valuef = value; final long liveTime = 86400; redisTemplate.execute(new RedisCallback<Long>() { public Long doInRedis(RedisConnection connection) throws DataAccessException { byte[] keyb = keyf.getBytes(); byte[] valueb = toByteArray(valuef); byte[] t = name.getBytes(); connection.hSet(t, keyb, valueb); if (liveTime > 0) { connection.expire(keyb, liveTime); } return 1L; } }); } private byte[] toByteArray(Object obj) { byte[] bytes = null; ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(obj); oos.flush(); bytes = bos.toByteArray(); oos.close(); bos.close(); } catch (IOException ex) { ex.printStackTrace(); } return bytes; } private Object toObject(byte[] bytes) { Object obj = null; try { ByteArrayInputStream bis = new ByteArrayInputStream(bytes); ObjectInputStream ois = new ObjectInputStream(bis); obj = ois.readObject(); ois.close(); bis.close(); } catch (IOException ex) { ex.printStackTrace(); } catch (ClassNotFoundException ex) { ex.printStackTrace(); } return obj; } public ValueWrapper putIfAbsent(Object o, Object o1) { return null; } public void evict(Object key) { final String keyf = (String) key; redisTemplate.execute(new RedisCallback<Long>() { public Long doInRedis(RedisConnection connection) throws DataAccessException { return connection.hDel(name.getBytes(), keyf.getBytes()); } }); } public void clear() { // 清楚缓存,需要根据Cache的name属性,在redis中模糊查询相关key值的集合,并全部删除 redisTemplate.execute(new RedisCallback<String>() { public String doInRedis(RedisConnection connection) throws DataAccessException { byte[] t = name.getBytes(); Set<byte[]> fields = connection.hKeys(t); for (byte[] bs : fields) { connection.hDel(t, bs); } return "ok"; } }); } }
开始使用注解,我是把其作用在倒dao层中
注意:这有一个坑:一般我们都是三层架构,service层调dao层,我一开始把注解加到Service实现层,运行的时候Cache机制不触发。我开始大量百度,查书,最后发现根本不会触发,因为Spring把实现类装载成为Bean的时候,会用代理包装一下,所以从Spring Bean的角度看,只有接口里面的方法是可见的,其它的都隐藏了,自然课看不到实现类里面的非接口方法,最后我把注解放到dao层上
这里的key我使用的方法名,一般会使用参数的属性,value就是上文在配置文件里配置的name。这样所有的配置都配好了
运行结果
这个就是我们存的key
第二个坑就是,你的实体类一定要序列化 实现Serializable,否则就报空指针异常。这个一定要记住
key的生成策略
键的生成策略有两种,一种是默认策略,一种是自定义策略。
默认策略:
默认的key是通过KeyGenerator生成的,其默认策略如下:
1.如果方法没有参数,则使用0作为key;
2.如果只有一个参数的话则使用该参数作为key;
3.如果参数多于一个则使用所有参数的hashcode作为key;
自定义策略:
自定义策略是指我们通过Spring的EL表达式来指定我们的key。这里的EL表达式可以使用参数以及它们对应的属性。使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。