最近再给云随笔后台增加redis模块,突然发现spring-boot-starter-data-redis模块很不人性化,实现不了通用的方式,(当然,你也可以自己写个通用的CacheUtil来实现通用的方式),但由于本人非常的爱装逼,就在这里不讲解那种傻瓜式操作了,这里只讲干货,干到你不可置信的干货).
例如:这里我使用了它其中的RedisTemplate ,发现存到redis中后,数据是乱码,看了底层才知道,它里面的序列化机制是jdk,为了修改它其中的序列化机制,就自定义redisTempate.
@Configuration
public class MyRedisConfig {
/**
自定义redistemplate
*/
@Bean
public RedisTemplate<Object, SysUser> userRedisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, SysUser> template = new RedisTemplate<Object, SysUser>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<SysUser> ser = new Jackson2JsonRedisSerializer<SysUser>(SysUser.class);
template.setDefaultSerializer(ser);
return template;
}
// CacheManagerCustomizers可以来定制缓存的一些规则
@Primary // 将某个缓存管理器作为默认的
@Bean
public RedisCacheManager userCacheManager(RedisTemplate<Object, SysUser> userRedisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(userRedisTemplate);
// key多了一个前缀
// 使用前缀,默认会将CacheName作为key的前缀
cacheManager.setUsePrefix(true);
return cacheManager;
}
}
此时,又发现了个问题,RedisTemplate的泛型第二个参数竟然不能是object,key,value能放进去却拿不出了,String 转换不了实际类型.
此时我决定用aspectJ+注解+反射的方式来实现通用缓存模块(用的StringRedisTamplate,这样就可以实现通用)
1 .创建aspect类 (在这里要多最一句:切面=切入点+通知/增强)
package com.orhonit.yunsuibi.common.aop;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
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.StringRedisTemplate;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.StringUtil;
import com.orhonit.yunsuibi.common.annotation.Cacheable;
import com.orhonit.yunsuibi.common.utils.CacheUtil;
/**
* 切面=通知+切入点
*
* @author cyf
* @date 2018/11/30 上午10:16:01
*/
@Component
@Aspect
public class CacheableAop {
}
2 .创建自定义注解
package com.orhonit.yunsuibi.common.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
/**
* 自定义注解,对于查询使用缓存的方法加入该注解
*
* @author Chenth
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
public @interface Cacheable {
String name() default "";
String key() default ""; //要存储的key,默认是查询条件的第一个参数
int expireTime() default 30;//默认30分钟
TimeUnit unit() default TimeUnit.MINUTES; //默认值是以分钟为单位
}
3.开始写aspect中的切入点和通知
package com.orhonit.yunsuibi.common.aop;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
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.StringRedisTemplate;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.StringUtil;
import com.orhonit.yunsuibi.common.annotation.Cacheable;
import com.orhonit.yunsuibi.common.utils.CacheUtil;
/**
* 切面=通知+切入点
*
* @author cyf
* @date 2018/11/30 上午10:16:01
*/
@Component
@Aspect
public class CacheableAop {
//切入点:方法上带有@Cacheable注解的方法
@Pointcut(value = "@annotation(com.orhonit.yunsuibi.common.annotation.Cacheable)")
public void pointCut() {
}
//这里通知选用环绕通知,由于首先要判断缓存中是否存在,存在则返回,不存在则放过查询数据库,查询完数据库就要放入缓存中,所以其他四种都不合适
@Around(value = "pointCut()")
public Object cache(ProceedingJoinPoint joinPoint) throws Throwable {
return null;
}
}
4.写一个RedisUitl,用于给aspect用
package com.orhonit.yunsuibi.common.utils;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.DefaultStringRedisConnection;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
import com.orhonit.yunsuibi.common.annotation.Cacheable;
import cn.hutool.core.io.IoUtil;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
/**
*
* ClassName RedisUtil
* Package com.orhonit.yunsuibi.common.utils
* Description 缓存工具类,采用序列化对象的方式,之前是fastjson ,发现也是转换不了实际类型-,-
*
* @author cyf
* @date 2018/11/20 下午11:04:32
*/
@Component
public class RedisUtil {
JedisPool jedisPool = new JedisPool();
/**
* 获取缓存中的数据
*
* @param key
* @return
* @throws UnsupportedEncodingException
*/
public Object getCacheByKey(String key) {
// 查询
Jedis jedis = jedisPool.getResource();
byte[] result = jedis.get(key.getBytes());
// 如果查询没有为空
if (null == result) {
return null;
}
// 查询到了,反序列化
return SerializeUtil.unSerialize(result);
}
/**
* 删除缓存中的数据
* @param key
*/
public void delCacheByKey(String key) {
Jedis jedis = jedisPool.getResource();
jedis.del(key);
}
/**
* 将数据保存到缓存中的数据
* @param key
* @return
*/
public boolean setCache(String key, Object obj, int expireTime, TimeUnit unit) {
// 序列化
byte[] bytes = SerializeUtil.serialize(obj);
// 存入redis
Jedis jedis = jedisPool.getResource();
String success = jedis.set(key.getBytes(), bytes);
if ("OK".equals(success)) {
System.out.println("数据成功保存到redis...");
}
return true;
}
}
4.此时此刻,万事俱备,只欠东风了 ,开始写主要逻辑
package com.orhonit.yunsuibi.common.aop;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
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.StringRedisTemplate;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.orhonit.yunsuibi.common.annotation.Cacheable;
import com.orhonit.yunsuibi.common.parser.DefaultResultParser;
import com.orhonit.yunsuibi.common.utils.CacheKeyUtil;
import com.orhonit.yunsuibi.common.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
/**
* 切面=通知+切入点
*
* @author cyf
* @date 2018/11/30 上午10:16:01
*/
@Component
@Aspect
@Slf4j
public class CacheableAop {
@Autowired
private CacheKeyUtil keyUtil;
@Autowired
private RedisUtil redisUtil ;
private ConcurrentHashMap<String, DefaultResultParser> parserMap = new ConcurrentHashMap<String, DefaultResultParser>();
@Pointcut(value = "@annotation(com.orhonit.yunsuibi.common.annotation.Cacheable)")
public void getCache() {
}
@Around(value = "getCache()")
public Object cache(ProceedingJoinPoint joinPoint) throws Throwable {
log.debug("@Cacheable 注解:拦截开始");
//获取方法签名
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
//通过反射得到方法
Method method = methodSignature.getMethod();
//获取注解
Cacheable cacheable = method.getAnnotation(Cacheable.class);
// 首先查询缓存,key默认使用第一个查询参数
String cacheKey = keyUtil.getCacheableKey(joinPoint);
Object proceed = null;
Object result=null;
if (StringUtils.isNotBlank(cacheKey)) {
// 查询缓存
Class returnType = method.getReturnType();
Object value = redisUtil.getCacheByKey(cacheKey);
if (null != value) {
return result = value;
}
proceed = joinPoint.proceed();
// 查询到的数据库数据保存到redis
log.debug("@Cacheable 注解:redis数据保存成功");
redisUtil.setCache(cacheKey, proceed, cacheable.expireTime(), cacheable.unit());
}
return proceed;
}
}
此时此刻,我就要恭喜你了,你将又成为一个大牛! 只需要在你要缓存的方法上加上@Cacheable就可以实现自定义注解实现通用缓存 -.-
/**
* 通过账号查找用户信息
* @param usercode
* @return
*/
@Cacheable()
public SysUser selectUserByUserCode(String usercode) {
return userMapper.selectUserByUserCode(usercode);
}
好了 ,到此为止,本节内容讲解完毕.讲的不好还请谅解,等云随笔后台管理完事后,云随笔前后台,以及代码开源共享!
不好意思 ,补充一个 ,发现少了一个序列化工具和获取缓存key的工具类
package com.orhonit.yunsuibi.common.utils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
*
* ClassName SerializeUtil
* Package com.orhonit.yunsuibi.common.utils
* Description 序列化反序列化工具
*
* @author cyf
* @date 2018/11/21 上午11:19:09
*/
public class SerializeUtil {
/**
*
* 序列化
*/
public static byte[] serialize(Object obj) {
ObjectOutputStream oos = null;
ByteArrayOutputStream baos = null;
try {
// 序列化
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
byte[] byteArray = baos.toByteArray();
return byteArray;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
*
* 反序列化
*
* @param bytes
* @return
*/
public static Object unSerialize(byte[] bytes) {
ByteArrayInputStream bais = null;
try {
// 反序列化为对象
bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
return ois.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
package com.orhonit.yunsuibi.common.utils;
import java.lang.reflect.Method;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import com.orhonit.yunsuibi.common.annotation.Cacheable;
import com.orhonit.yunsuibi.common.annotation.CacheableDel;
/**
*
* ClassName CacheKeyUtil
* Package com.orhonit.yunsuibi.common.utils
* Description 获取缓存keyUtil
*
* @author cyf
* @date 2018/12/21 上午11:22:32
*/
@Component
public class CacheKeyUtil {
/**
* 获取要缓存的key 默认值为查询条件的第一个参数 可以通过key属性指定key
*
* @param joinPoint
* @return
*/
public String getCacheableKey(ProceedingJoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
Cacheable cacheable = method.getAnnotation(Cacheable.class);
String key = cacheable.key();
String catalog = cacheable.catalog();
String cacheKey="";
if(StringUtils.isNotBlank(catalog)) {
cacheKey+=catalog+":";
}
if (StringUtils.isNotBlank(key)) {
return cacheKey+=key;
}
Object[] args = joinPoint.getArgs();
return cacheKey+=args[0];
}
/**
* 获取要删除的key 默认值为查询条件的第一个参数 可以通过key属性指定key
*
* @param joinPoint
* @return
*/
public String getCacheDelKey(ProceedingJoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
CacheableDel cacheable = method.getAnnotation(CacheableDel.class);
String key = cacheable.key();
if (StringUtils.isNotBlank(key)) {
return key;
}
Object[] args = joinPoint.getArgs();
return (String) args[0];
}
}