欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

手写redis@Cacheable注解 支持过期时间设置方式

程序员文章站 2022-06-17 21:27:45
目录原理解释实现方法源代码原理解释友情链接 手写redis @ cacheable注解参数java对象作为键值@cacheable注解作用,将带有该注解方法的返回值存放到redis的的中;使用方法在方...

原理解释 

友情链接  手写redis @ cacheable注解参数java对象作为键值 

@cacheable注解作用,将带有该注解方法的返回值存放到redis的的中;

使用方法在方法上使用@cacheable(键=“测试+#p0 + p1#...”)

表示键值为测试+方法第一个参数+方法第二个参数,值为该方法的返回值。

以下源代码表示获取人员列表,redis的中存放的关键值为'领袖'+ leadergroupid + uuid + yeardetailid

        @override
	@cacheable(key="'leader'+#p0+#p1+#p2",value="leader")
	public list<leader> listleaders(string leadergroupid, string uuid, string yeardetailid) {
		return sysindexmapper.listleaders(leadergroupid, uuid, yeardetailid);
	}

等同于

        @override
	public list<leader> listleaders(string leadergroupid, string uuid, string yeardetailid) {
		string key = "leader" + leadergroupid + uuid + yeardetailid;
		// 判断缓存是否存在redis中
		boolean haskey = redisutil.haskey(key);
		if (haskey) {
                        //如果存在 返还redis中的值
			object leaderslist = redisutil.get(key);
			return (list<leader>) leaderslist;
		} else {
			list<leader> leadersquotadetaillist = sysindexmapper.listleaders(leadergroupid, uuid, yeardetailid);
                        //将查询结果存放在redis中
			redisutil.set(key, leadersquotadetaillist);
			return leadersquotadetaillist;
		}
	}

说白了就是在原方法的前面判断的关键值是否存在的redis的中,如果存在就取内存中的值,如果不存在就查询数据库,将查询结果存放在redis的的中。

实现方法

  • 使用代理模式,在方法执行前和执行后可以添加其他处理程序,本文采用springaop +注解方式。
  • 集成redis,封装redis工具类
  • 原版本不支持 过期时间 设置,本文将实现

源代码

缓存配置类redisconfig

package com.huajie.config; 
import org.springframework.beans.factory.annotation.value;
import org.springframework.cache.cachemanager;
import org.springframework.cache.annotation.cachingconfigurersupport;
import org.springframework.cache.annotation.enablecaching;
import org.springframework.cache.interceptor.keygenerator;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.data.redis.cache.rediscachemanager;
import org.springframework.data.redis.connection.redisconnectionfactory;
import org.springframework.data.redis.core.redistemplate;
import org.springframework.data.redis.core.stringredistemplate;
import org.springframework.data.redis.serializer.jackson2jsonredisserializer;
import org.springframework.data.redis.serializer.stringredisserializer;
import com.fasterxml.jackson.annotation.jsonautodetect;
import com.fasterxml.jackson.annotation.propertyaccessor;
import com.fasterxml.jackson.databind.objectmapper;
 
/**
 * redis缓存配置类
 */
@configuration
@enablecaching
public class redisconfig extends cachingconfigurersupport {
 
	@value("${spring.redis.host}")
	private string host;
	@value("${spring.redis.port}")
	private int port;
	@value("${spring.redis.timeout}")
	private int timeout;
 
	// 自定义缓存key生成策略
	@bean
	public keygenerator keygenerator() {
		return new keygenerator() {
			@override
			public object generate(object target, java.lang.reflect.method method, object... params) {
				stringbuffer sb = new stringbuffer();
				sb.append(target.getclass().getname());
				sb.append(method.getname());
				for (object obj : params) {
					sb.append(obj.tostring());
				}
				return sb.tostring();
			}
		};
	}
 
	// 缓存管理器
	@bean
	public cachemanager cachemanager(@suppresswarnings("rawtypes") redistemplate redistemplate) {
		rediscachemanager cachemanager = new rediscachemanager(redistemplate);
		// 设置缓存过期时间
		cachemanager.setdefaultexpiration(10000);
		return cachemanager;
	}
 
	@bean
	public redistemplate<string, object> redistemplate(redisconnectionfactory factory) {
		redistemplate<string, object> template = new redistemplate<string, object>();
		template.setconnectionfactory(factory);
		jackson2jsonredisserializer jackson2jsonredisserializer = new jackson2jsonredisserializer(object.class);
		objectmapper om = new objectmapper();
		om.setvisibility(propertyaccessor.all, jsonautodetect.visibility.any);
		om.enabledefaulttyping(objectmapper.defaulttyping.non_final);
		jackson2jsonredisserializer.setobjectmapper(om);
		stringredisserializer stringredisserializer = new stringredisserializer();
		// key采用string的序列化方式
		template.setkeyserializer(stringredisserializer);
		// hash的key也采用string的序列化方式
		template.sethashkeyserializer(stringredisserializer);
		// value序列化方式采用jackson
		template.setvalueserializer(jackson2jsonredisserializer);
		// hash的value序列化方式采用jackson
		template.sethashvalueserializer(jackson2jsonredisserializer);
		template.afterpropertiesset();
		return template;
	}
 
	private void setserializer(stringredistemplate template) {
		@suppresswarnings({ "rawtypes", "unchecked" })
		jackson2jsonredisserializer jackson2jsonredisserializer = new jackson2jsonredisserializer(object.class);
		objectmapper om = new objectmapper();
		om.setvisibility(propertyaccessor.all, jsonautodetect.visibility.any);
		om.enabledefaulttyping(objectmapper.defaulttyping.non_final);
		jackson2jsonredisserializer.setobjectmapper(om);
		template.setvalueserializer(jackson2jsonredisserializer);
	}
}

redis的依赖引入,配置文件,工具类redisutil,网上几个版本都类似,本文参考以下版本传送门

准备工作做好之后开始正式编写注解@cacheable nextkey()用做二级缓存本文中不会用到

nextkey用法详情>

创建的java的注解@extcacheable  

package com.huajie.annotation; 
import java.lang.annotation.elementtype;
import java.lang.annotation.retention;
import java.lang.annotation.retentionpolicy;
import java.lang.annotation.target;
 
@target({ elementtype.method })
@retention(retentionpolicy.runtime)
public @interface extcacheable {	
	string key() default "";	
	string nextkey() default ""; 
	int expiretime() default 1800;//30分钟	
}
 

springaop切面cacheableaspect

package com.huajie.aspect; 
import java.lang.reflect.method;
import java.util.arraylist;
import java.util.list;
import org.aspectj.lang.proceedingjoinpoint;
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.stereotype.component;
import com.huajie.annotation.extcacheable;
import com.huajie.utils.redisutil;
 
/**
 * redis缓存处理
 * 不适用与内部方法调用(this.)或者private
 */
@component
@aspect
public class cacheableaspect {	
	@autowired
	private redisutil redisutil;
 
	@pointcut("@annotation(com.huajie.annotation.extcacheable)")
	public void annotationpointcut() {
	}
 
	@around("annotationpointcut()")
	public object doaround(proceedingjoinpoint joinpoint) throws throwable {
		// 获得当前访问的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 = getexpiretime(annotation);
			}
		} catch (exception e) {
			throw new runtimeexception("redis缓存注解参数异常", e);
		}
		// 获取缓存是否存在
		boolean haskey = redisutil.haskey(key);
		if (haskey) {
			return redisutil.get(key);
		} else {
                         //执行原方法(java反射执行method获取结果)
			object res = joinpoint.proceed();
                         //设置缓存
			redisutil.set(key, res);
                         //设置过期时间
			redisutil.expire(key, expiretime);
			return res;
		}
	}
	
	private int getexpiretime(extcacheable annotation) {
		return annotation.expiretime();
	}
 
	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;
	}	
}

业务模块使用方法

@override
	@extcacheable(key = "leaders+#p0+#p1+#p2")
	// 手机端获取*员列表
	public list<leader> listleaders(string leadergroupid, string uuid, string yeardetailid) {
		list<leader> leadersquotadetaillist = sysindexmapper.listleaders(leadergroupid, uuid, yeardetailid);
		return leadersquotadetaillist;
	}

业务模块过期时间使用方法,5分钟过期

@override
	@extcacheable(key = "mobilecacheflag", expiretime = 60 * 5)
	public int cacheflag() {
		int mobilecacheflag = 1;
		mobilecacheflag = sysindexmapper.cacheflag();
		return mobilecacheflag;
	}

redis的的截图

手写redis@Cacheable注解 支持过期时间设置方式

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。