spring-redis.xml中的内容:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache-4.2.xsd">
<!-- redis 相关配置 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="2048"/>
<property name="maxIdle" value="16"/>
<property name="numTestsPerEvictionRun" value="1024"/>
<property name="timeBetweenEvictionRunsMillis" value="30000"/>
<property name="minEvictableIdleTimeMillis" value="-1"/>
<property name="softMinEvictableIdleTimeMillis" value="10000"/>
<property name="maxWaitMillis" value="1500"/>
<property name="testOnBorrow" value="true"/>
<property name="testWhileIdle" value="true"/>
<property name="testOnReturn" value="false"/>
<property name="jmxEnabled" value="true"/>
<property name="jmxNamePrefix" value="youyuan"/>
<property name="blockWhenExhausted" value="false"/>
</bean>
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="${redis.hostName}"/>
<property name="port" value="6379"/>
<property name="timeout" value="${redis.timeout}"/>
<property name="password" value="${redis.password}"/>
<property name="usePool" value="${redis.usePool}"/>
<property name="poolConfig" ref="poolConfig"/>
</bean>
<!-- 工具类1 -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory"/>
<!--配置序列化方式,防止乱码-->
<!-- key使用String序列化方式 -->
<property name="keySerializer" ref="keySerializer"/>
<!-- value使用String序列化 -->
<property name="hashKeySerializer" ref="keySerializer"/>
<!-- value使用json序列化 -->
<property name="valueSerializer" ref="jackson2JsonRedisSerializer"/>
<!--<property name="hashValueSerializer" ref="jackson2JsonRedisSerializer"/>-->
<property name="hashValueSerializer" ref="jdkSerializer"/>
</bean>
<bean id="keySerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
<bean id="jackson2JsonRedisSerializer" class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
<bean id="jdkSerializer" class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
<!-- 工具类2 -->
<bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory"></property>
</bean>
<!-- spring自己的缓存管理器,这里定义了缓存位置名称 ,即注解中的value -->
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean class="cn.kingomgr.common.cache.RedisCache">
<property name="redisTemplate" ref="redisTemplate"/>
<property name="name" value="common"/>
</bean>
</set>
</property>
</bean>
</beans>
RedisCache类中的内容:
// An highlighted block
package cn.kingomgr.common.cache;
import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import java.io.*;
import java.util.concurrent.Callable;
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;
}
@Override
public String getName() {
// TODO Auto-generated method stub
return this.name;
}
@Override
public Object getNativeCache() {
// TODO Auto-generated method stub
return this.redisTemplate;
}
@Override
public ValueWrapper get(Object key) {
// TODO Auto-generated method stub
System.out.println("get key");
final String keyf = key.toString();
Object object = null;
object = redisTemplate.execute(new RedisCallback<Object>() {
public Object doInRedis(RedisConnection connection)
throws DataAccessException {
byte[] key = keyf.getBytes();
byte[] value = connection.get(key);
if (value == null) {
return null;
}
return toObject(value);
}
});
return (object != null ? new SimpleValueWrapper(object) : null);
}
@Override
public void put(Object key, Object value) {
// TODO Auto-generated method stub
System.out.println("put key");
final String keyf = key.toString();
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);
connection.set(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;
}
@Override
public void evict(Object key) {
// TODO Auto-generated method stub
System.out.println("del key");
final String keyf = key.toString();
redisTemplate.execute(new RedisCallback<Long>() {
public Long doInRedis(RedisConnection connection)
throws DataAccessException {
return connection.del(keyf.getBytes());
}
});
}
@Override
public void clear() {
// TODO Auto-generated method stub
System.out.println("clear key");
redisTemplate.execute(new RedisCallback<String>() {
public String doInRedis(RedisConnection connection)
throws DataAccessException {
connection.flushDb();
return "ok";
}
});
}
@Override
public <T> T get(Object key, Class<T> type) {
// TODO Auto-generated method stub
return null;
}
@Override
public <T> T get(Object o, Callable<T> callable) {
return null;
}
@Override
public ValueWrapper putIfAbsent(Object key, Object value) {
// TODO Auto-generated method stub
return null;
}
}
在applicationContext.xml文件中引入spring-redis.xml配置文件即可,并且在主配置文件applicationContext.xml中声明
<cache:annotation-driven cache-manager=“cacheManager” />
才会生效。
applicationContext.xml内容:
<!-- 启用缓存注解功能,这个是必须的,否则注解不会生效,另外,该注解一定要声明在spring主配置文件中才会生效 -->
<cache:annotation-driven cache-manager="cacheManager" />
<!--省略其他配置-->
<!-- 导入redis的配置文件 -->
<import resource="spring-redis.xml"/>
在实现类中使用注解:
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserBo userBo;
@Cacheable(value="common",key="'id_'+#id")
public User selectByPrimaryKey(Integer id) {
return userBo.selectByPrimaryKey(id);
}
@CachePut(value="common",key="#user.getUserName()")
public void insertSelective(User user) {
userBo.insertSelective(user);
}
@CacheEvict(value="common",key="'id_'+#id")
public void deleteByPrimaryKey(Integer id) {
userBo.deleteByPrimaryKey(id);
}
}
注意:
1.将@Cacheable(value=“common”,key="‘id_’+#id")变为@Cacheable(value=“common”,key="+#id")。仅仅是去掉了 ‘id_’,就会报错:
java.io.StreamCorruptedException: invalid stream header: 63346537
问题待解决。
2.@Cacheable(value=“common”,key="‘id_’+#id")中的value一定要取RedisCache中的name对应的value。否则会出现以下错误:
java.lang.IllegalArgumentException: Cannot find cache named 'xx' for Builder
<!-- spring自己的缓存管理器,这里定义了缓存位置名称 ,即注解中的value -->
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<!-- 这里可以配置多个redis -->
<!-- <bean class="com.cn.util.RedisCache">
<property name="redisTemplate" ref="redisTemplate" />
<property name="name" value="default"/>
</bean> -->
<bean class="cn.kingomgr.common.cache.RedisCache">
<property name="redisTemplate" ref="redisTemplate"/>
<property name="name" value="common"/>
</bean>
</set>
</property>
</bean>
补充:Spring缓存注解@Cacheable、@CacheEvict、@CachePut使用.
上述注解的缺陷:
1.@CacheEvict:无法删除所有以"id_"为前缀的key。
2.@Cacheable:不能自定义缓存过期时间。
针对以上问题,提出解决方案如下:
自定义注解:
@ExtCacheable注解
/**
* 扩展Cacheable注解
* 用于用户自定义redis缓存过期时间
*
* @author : yuanjinqiao
* @date : 11:57 2020/6/2
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtCacheable {
/**
* 使用方法在方法上使用@ExtCacheable(key="测试+#P0 + P1#...")
* 表示键值为:测试+方法第一个参数+方法第二个参数,值为该方法的返回值。
*
* @return
*/
String key() default "";
//String nextKey() default "";
//过期时间
int expireTime() default 1800;//30分钟
}
@ExtCacheEvict注解
/**
* 扩展CacheEvict注解
* 使用key进行模糊匹配删除redis缓存数据
*
* @author : yuanjinqiao
* @date : 9:53 2020/6/2
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtCacheEvict {
/**
* 需要清除的key,可以模糊匹配
*
* @return
*/
String[] key() default {};
}
RedisCacheAspect切面类:
package cn.kingomgr.common.aop;
import lombok.extern.java.Log;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* 功能描述:redis缓存切面类
*
* @author jasin
* DATE 2019/9/25.
*/
@Component
@Aspect
@Log
public class RedisCacheAspect {
public RedisCacheAspect() {
log.info("........................................................");
log.info(".........清除缓存切面类CacheRemoveAspect已加载.............");
log.info("........................................................");
}
@Autowired
private RedisTemplate<String, Object> redisTemplate;
//截获标有@ExtCacheEvict的方法
@Pointcut(value = "execution(* *.*(..) )&& @annotation(cn.kingomgr.common.aop.ExtCacheEvict))")
private void ExtCacheEvictAspect() {
}
//截获标有@ExtCacheEvict的方法
@Pointcut(value = "execution(* *.*(..) )&& @annotation(cn.kingomgr.common.aop.ExtCacheable))")
private void ExtCacheableAspect() {
}
/**
* 功能描述: 根据方法注解上的key模糊匹配删除
*
* @return void
* @throws
* @author fuyuchao
* @date 2018/9/14 16:55
* @params [joinPoint]
*/
@AfterReturning(value = "ExtCacheEvictAspect()")
private void doAfterReturningExtCacheEvictAspect(JoinPoint joinPoint) {
//获取被代理的类
Object target = joinPoint.getTarget();
//获取切入方法的数据
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//获取切入方法
Method method = signature.getMethod();
//获得注解
ExtCacheEvict extCacheEvict = method.getAnnotation(ExtCacheEvict.class);
if (extCacheEvict != null) {
//需要移除的正则key
String[] keys = extCacheEvict.key();
for (String key : keys) {
//清除指定清除的key的缓存
cleanRedisCache("*" + key + "*");
}
}
}
/**
* 功能描述: 根据方法注解上的key以及过期时间expireTime缓存数据
* @param joinPoint
*/
@Around(value = "ExtCacheableAspect()")
private Object doAroundExtCacheableAspect(ProceedingJoinPoint joinPoint) {
// 获得当前访问的class
Class<?> className = joinPoint.getTarget().getClass();
// 获得访问的方法名
String methodName = joinPoint.getSignature().getName();
// 得到方法的参数的类型
Class<?>[] argClass = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
Object[] args = joinPoint.getArgs();
String key = "";
int expireTime = 1800;
try {
// 得到访问的方法对象
Method method = className.getMethod(methodName, argClass);
method.setAccessible(true);
// 判断是否存在@ExtCacheable注解
if (method.isAnnotationPresent(ExtCacheable.class)) {
ExtCacheable annotation = method.getAnnotation(ExtCacheable.class);
key = getRedisKey(args,annotation);
expireTime = annotation.expireTime();
}
} catch (Exception e) {
throw new RuntimeException("redis缓存注解参数异常", e);
}
// 获取缓存是否存在
boolean hasKey = redisTemplate.hasKey(key);
if (hasKey) {
return redisTemplate.opsForValue().get(key);
} else {
//执行原方法(java反射执行method获取结果)
Object res = null;
try {
res = joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
//设置缓存以及设置过期时间
redisTemplate.opsForValue().set(key, res, expireTime, TimeUnit.SECONDS);
return res;
}
}
private String getRedisKey(Object[] args, ExtCacheable annotation) {
String primalKey = annotation.key();
//获取#p0...集合
List<String> keyList = getKeyParsList(primalKey);
for (String keyName : keyList) {
int keyIndex = Integer.parseInt(keyName.toLowerCase().replace("#p", ""));
Object parValue = args[keyIndex];
primalKey = primalKey.replace(keyName, String.valueOf(parValue));
}
return primalKey.replace("+", "").replace("'", "");
}
// 获取key中#p0中的参数名称
private static List<String> getKeyParsList(String key) {
List<String> ListPar = new ArrayList<String>();
if (key.indexOf("#") >= 0) {
int plusIndex = key.substring(key.indexOf("#")).indexOf("+");
int indexNext = 0;
String parName = "";
int indexPre = key.indexOf("#");
if (plusIndex > 0) {
indexNext = key.indexOf("#") + key.substring(key.indexOf("#")).indexOf("+");
parName = key.substring(indexPre, indexNext);
} else {
parName = key.substring(indexPre);
}
ListPar.add(parName.trim());
key = key.substring(indexNext + 1);
if (key.indexOf("#") >= 0) {
ListPar.addAll(getKeyParsList(key));
}
}
return ListPar;
}
private void cleanRedisCache(String key) {
if (key != null) {
Set<String> stringSet = redisTemplate.keys(key);
redisTemplate.delete(stringSet);//删除缓存
log.info("清除 " + key + " 缓存");
}
}
}
最后在applicationContext.xml中配置扫描切面类:
<context:component-scan base-package="cn.kingomgr.common.aop"/>
注意: 若切面在service层不起作用可能原因是:
如果只是监听service层函数,那么在spring的xml配置文件配置切面代理即可 如果需要监听controller层,涉及到springmvc,所以需要配置springmvc的配置文件开启切面代理。
在applicationContext.xml和springmvc.xml下都配置如下代码:
<aop:aspectj-autoproxy proxy-target-class="true" />