Spring AOP如何整合redis(注解方式)实现缓存统一管理详解
前言
项目使用redis作为缓存数据,但面临着问题,比如,项目A,项目B都用到redis,而且用的redis都是一套集群,这样会带来一些问题。
问题:比如项目A的开发人员,要缓存一些热门数据,想到了redis,于是乎把数据放入到了redis,自定义一个缓存key:hot_data_key,数据格式是项目A自己的数据格式,项目B也遇到了同样的问题,也要缓存热门数据,也是hot_data_key,数据格式是项目B是自己的数据格式,由于用的都是同一套redis集群,这样key就是同一个key,有的数据格式适合项目A,有的数据格式适合项目B,会报错的,我们项目中就遇到这样的一个错误,找不到原因,结果就是两个平台用到了同一key,很懊恼。
解决方式:
1、弄一个常量类工程,所有的redis的key都放入到这个工程里,加上各自的平台标识,这样就不错错了
2、springAop结合redis,再相应的service层,加上注解,key的规范是包名+key名,这样就不错重复了
思路:
1、自定义注解,加在需要缓存数据的地方
2、springaop结合redis实现
3、SPEL解析注解参数,用来得到响应的注解信息
4、redis的key:包名+key防止redis的key重复
实现如下:
项目准备,由于是maven项目,需要引入相关的包
fastjson包spring-data-redis com.alibaba fastjson ${com.alibaba.fastjson} org.springframework.data spring-data-redis ${spring.redis.version} redis.clients jedis ${jedis.redis.clients.version}
还有一些必备的就是spring工程相关的包
1、自定义注解
importjava.lang.annotation.ElementType;
importjava.lang.annotation.Retention;
importjava.lang.annotation.RetentionPolicy;
importjava.lang.annotation.Target;
importjava.util.concurrent.TimeUnit;
/**
*缓存注解
*
*@authorshangdc
*
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public@interfaceRedisCache{
/**
*缓存key的名称
*@return
*/
Stringkey();
/**
*key是否转换成md5值,有的key是整个参数对象,有的大内容的,比如一个大文本,导致redis的key很长
*需要转换成md5值作为redis的key
*@return
*/
booleankeyTransformMd5()defaultfalse;
/**
*key过期日期秒
*@return
*/
intexpireTime()default60;
/**
*时间单位,默认为秒
*@return
*/
TimeUnitdateUnit()defaultTimeUnit.SECONDS;
}
2、定义切点Pointcut
/**
*redis缓存切面
*
*@authorshangdc
*
*/
publicclassRedisCacheAspect{
//由于每个人的环境,日志用的不一样,怕报错,就直接关掉了此日志输出,如有需要可加上
//privateLoggerLOG=Logger.getLogger(RedisCacheAspect.class);
/**
*这块可配置,每个公司都要自己的缓存配置方式,到时候可配置自己公司所用的缓存框架和配置方式
*/
@Resource(name="redisTemplate")
privateValueOperationsvalueOperations;
/**
*具体的方法
*@paramjp
*@return
*@throwsThrowable
*/
publicObjectcache(ProceedingJoinPointjp,RedisCachecacheable)throwsThrowable{
//result是方法的最终返回结果
Objectresult=null;
//得到类名、方法名和参数
Object[]args=jp.getArgs();
//获取实现类的方法
Methodmethod=getMethod(jp);
//注解信息key
Stringkey=cacheable.key();
//是否转换成md5值
booleankeyTransformMd5=cacheable.keyTransformMd5();
//----------------------------------------------------------
//用SpEL解释key值
//----------------------------------------------------------
//解析EL表达式后的的redis的值
StringkeyVal=SpringExpressionUtils.parseKey(key,method,jp.getArgs(),keyTransformMd5);
//获取目标对象
Objecttarget=jp.getTarget();
//这块是全路径包名+目标对象名,默认的前缀,防止有的开发人员乱使用key,乱定义key的名称,导致重复key,这样在这加上前缀了,就不会重复使用key
Stringtarget_class_name=target.getClass().getName();
StringBuilderredis_key=newStringBuilder(target_class_name);
redis_key.append(keyVal);
//最终的redis的key
Stringredis_final_key=redis_key.toString();
Stringvalue=valueOperations.get(redis_final_key);
if(value==null){//这块是判空
//缓存未命中,这块没用log输出,可以自定义输出
System.out.println(redis_final_key+"缓存未命中缓存");
//如果redis没有数据则执行拦截的方法体
result=jp.proceed(args);
//存入json格式字符串到redis里
Stringresult_json_data=JSONObject.toJSONString(result);
System.out.println(result_json_data);
//序列化结果放入缓存
valueOperations.set(redis_final_key,result_json_data,getExpireTimeSeconds(cacheable),TimeUnit.SECONDS);
}else{
//缓存命中,这块没用log输出,可以自定义输出
System.out.println(redis_final_key+"命中缓存,得到数据");
//得到被代理方法的返回值类型
Class>returnType=((MethodSignature)jp.getSignature()).getReturnType();
//拿到数据格式
result=getData(value,returnType);
}
returnresult;
}
/**
*根据不同的class返回数据
*@paramvalue
*@paramclazz
*@return
*/
publicTgetData(Stringvalue,Classclazz){
Tresult=JSONObject.parseObject(value,clazz);
returnresult;
}
/**
*获取方法
*@parampjp
*@return
*@throwsNoSuchMethodException
*/
publicstaticMethodgetMethod(ProceedingJoinPointpjp)throwsNoSuchMethodException{
//--------------------------------------------------------------------------
//获取参数的类型
//--------------------------------------------------------------------------
Object[]args=pjp.getArgs();
Class[]argTypes=newClass[pjp.getArgs().length];
for(inti=0;itargetClass=pjp.getTarget().getClass();
Method[]methods=targetClass.getMethods();
//--------------------------------------------------------------------------
//查找Class>里函数名称、参数数量、参数类型(相同或子类)都和拦截的method相同的Method
//--------------------------------------------------------------------------
Methodmethod=null;
for(inti=0;i[]parameterTypes=methods[i].getParameterTypes();
booleanisSameMethod=true;
//如果相比较的两个method的参数长度不一样,则结束本次循环,与下一个method比较
if(args.length!=parameterTypes.length){
continue;
}
//--------------------------------------------------------------------------
//比较两个method的每个参数,是不是同一类型或者传入对象的类型是形参的子类
//--------------------------------------------------------------------------
for(intj=0;parameterTypes!=null&&j
3、spring相关配置
由于是公司的项目,所有包就的路径就去掉了
4、工具类SPEL
/**
*springEL表达式
*
*@authorshangdc
*
*/
publicclassSpringExpressionUtils{
/**
*获取缓存的keykey定义在注解上,支持SPEL表达式注:method的参数支持Javabean和Map
*method的基本类型要定义为对象,否则没法读取到名称
*
*example1:Phonephone=newPhone();"#{phone.cpu}"为对象的取值、
*example2:Mapapple=newHashMap();apple.put("name","goodapple");"#{apple[name]}"为map的取值
*example3:"#{phone.cpu}_#{apple[name]}"
*
*
*@paramkey
*@parammethod
*@paramargs
*@return
*/
publicstaticStringparseKey(Stringkey,Methodmethod,Object[]args,booleankeyTransformMd5){
//获取被拦截方法参数名列表(使用Spring支持类库)
LocalVariableTableParameterNameDiscovereru=newLocalVariableTableParameterNameDiscoverer();
String[]paraNameArr=u.getParameterNames(method);
//使用SPEL进行key的解析
ExpressionParserparser=newSpelExpressionParser();
//SPEL上下文
StandardEvaluationContextcontext=newStandardEvaluationContext();
//把方法参数放入SPEL上下文中
for(inti=0;i
5、redis相关配置
重要的是自己的redis配置,可能跟我的不太一样,用自己的就好
测试
publicclassRedisCacheTest{
@Test
publicvoidtest(){
ApplicationContextcontext=newClassPathXmlApplicationContext("classpath:spring/spring-applicationContext.xml");
RedisCacheAopServiceredisCacheAopService=(RedisCacheAopService)context.getBean("redisCacheAopService");
Apiapi=redisCacheAopService.getApi(1l);
System.out.println(api.getClass());
System.out.println(JSONObject.toJSONString(api));
ApiParamparam=newApiParam();
param.setId(2l);
param.setApiName("短信服务接口数据");
//System.out.println("toString:"+param.toString());
//System.out.println(MD5Util.digest(param.toString()));
Apiapi_1=redisCacheAopService.getApiByParam(param);
System.out.println(api_1.getClass());
System.out.println(JSONObject.toJSONString(api_1));
}
}
测试打印信息:
大体思路是这样,需要自己动手实践,不要什么都是拿过来直接copy,使用,整个过程都不操作,也不知道具体的地方,该用什么,自己实际操作,可以得到很多信息。
辅助信息类:
publicclassApiimplementsSerializable{
privatestaticfinallongserialVersionUID=1L;
/**
*
*自增主键id
*/
privateLongid;
/**
*
*api名称
*/
privateStringapiName;
/**
*
*api描述
*/
privateStringapiDescription;
/**
*
*有效时间
*/
privateIntegervalid;
/**
*处理类
*/
privateStringhandlerClass;
/**
*
*
*/
privateDateupdateTime;
/**
*
*
*/
privateDatecreateTime;
publicApi(){
}
publicStringtoString(){
return"id:"+id+",apiName:"+apiName+",apiDescription:"+apiDescription+",valid:"+valid+",updateTime:"+updateTime+",createTime:"+createTime;
}
publicLonggetId(){
returnthis.id;
}
publicvoidsetId(Longid){
this.id=id;
}
publicStringgetApiName(){
returnthis.apiName;
}
publicvoidsetApiName(StringapiName){
this.apiName=apiName;
}
publicStringgetApiDescription(){
returnthis.apiDescription;
}
publicvoidsetApiDescription(StringapiDescription){
this.apiDescription=apiDescription;
}
publicIntegergetValid(){
returnthis.valid;
}
publicvoidsetValid(Integervalid){
this.valid=valid;
}
publicDategetUpdateTime(){
returnthis.updateTime;
}
publicvoidsetUpdateTime(DateupdateTime){
this.updateTime=updateTime;
}
publicDategetCreateTime(){
returnthis.createTime;
}
publicvoidsetCreateTime(DatecreateTime){
this.createTime=createTime;
}
publicStringgetHandlerClass(){
returnhandlerClass;
}
publicvoidsetHandlerClass(StringhandlerClass){
this.handlerClass=handlerClass;
}
}
参数类信息
publicclassApiParam{
/**
*
*/
privatestaticfinallongserialVersionUID=1L;
/**
*api表主键id
*/
privateLongid;
/**
*
*api名称
*/
privateStringapiName;
/**
*
*有效OR无效
*/
privateIntegervalid;
publicStringgetApiName(){
returnapiName;
}
publicvoidsetApiName(StringapiName){
this.apiName=apiName;
}
publicIntegergetValid(){
returnvalid;
}
publicvoidsetValid(Integervalid){
this.valid=valid;
}
publicLonggetId(){
returnid;
}
publicvoidsetId(Longid){
this.id=id;
}
/*@Override
publicStringtoString(){
return"ApiParam[id="+id+",apiName="+apiName+",valid="+valid+"]";
}
*/
}
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。