The project uses Redis as the cache data, but faces problems such as Project A, project B using Redis, and Redis as a set of clusters, which poses some problems.
Questions:For example, the developer of Project A, to cache some hot data, think of Redis, and then put the data into the Redis, custom a cache key:hot_data_key, the data format is Project A's own data format, Project B also encountered the same problem, but also to cache popular data, is also hot_data_key, the data format is the project B is its own data format, because the use of the same set of Redis cluster, so that key is the same key, some data format suitable for project A, some data format for Project B, will error, we have encountered such a mistake in the project, Can not find the reason, the result is two platform to use the same key, very annoyed.
How to resolve:
1, get a constant class project, all of the Redis key is put into this project, plus the respective platform logo, so it's wrong
2, Spring AOP combined with Redis, and then the corresponding service layer, plus annotations, key specification is the package name +key name, so it is good to repeat
Ideas:
1, custom annotations, add to the place where the data needs to be cached
2. Spring AOP combined with Redis implementation
3, spel parsing annotation parameters, used to get the response of the annotated information
4. Redis Key: Package name +key prevent Redis key from repeating
The implementation is as follows:
Project preparation, because it is MAVEN project, need to introduce the relevant package
fastjson包 <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>${com.alibaba.fastjson}</version> </dependency>spring-data-redis <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>${spring.redis.version}</version> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>${jedis.redis.clients.version}</version> </dependency> 还有一些必备的就是spring工程相关的包
1. Custom annotations
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 shangdc * */@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface RedisCache { /** * 缓存key的名称 * @return */ String key(); /** * key是否转换成md5值,有的key是整个参数对象,有的大内容的,比如一个大文本,导致redis的key很长 * 需要转换成md5值作为redis的key * @return */ boolean keyTransformMd5() default false; /** * key 过期日期 秒 * @return */ int expireTime() default 60; /** * 时间单位,默认为秒 * @return */ TimeUnit dateUnit() default TimeUnit.SECONDS;}
2. Define the Pointcut Pointcut
/** * Redis Cache Facets * * @author SHANGDC * */public class Rediscacheaspect {///due to the environment of each person, the log is not the same, afraid of error, directly turn off the log output, if necessary to add Private Logger LOG = Logger.getlogger (Rediscacheaspect.class); /** * This can be configured, each company will have their own cache configuration, then you can configure their own company's cache framework and configuration method */@Resource (name = "Redistemplate") Private Valueoperati Ons<string, string> valueoperations; /** * Specific Method * @param JP * @return * @throws throwable */public Object cache (Proceedingjoinpoint JP , Rediscache cacheable) throws throwable{//result is the final return result of the method, Object result = null; Get the class name, method name, and parameter object[] args = Jp.getargs (); Gets the method of implementing the Class methods = GetMethod (JP); Note Information key String key = Cacheable.key (); Whether to convert to MD5 value Boolean KEYTRANSFORMMD5 = Cacheable.keytransformmd5 (); ----------------------------------------------------------//Use Spel to interpret key value//------------------------------ ----------------------------//Resolution EL The value of the Redis after expression String keyval = Springexpressionutils.parsekey (key, method, Jp.getargs (), KEYTRANSFORMMD5); Gets the target objects object target = Jp.gettarget (); This block is the full path package name + target object name, the default prefix, to prevent some developers to use the key, randomly define key name, resulting in duplicate key, so in this prefix, will not reuse key String Target_class_name = Target.get Class (). GetName (); StringBuilder Redis_key = new StringBuilder (target_class_name); Redis_key.append (Keyval); The final Redis key String Redis_final_key = Redis_key.tostring (); String value = Valueoperations.get (Redis_final_key); if (value = = null) {//This block is empty//cache misses, this block does not have log output, can be customized output System.out.println (Redis_final_key + "cache misses slow Save "); If the Redis does not have data then the method of interception is performed by the body result = Jp.proceed (args); In JSON format string to Redis string result_json_data = jsonobject.tojsonstring (result); System.out.println (Result_json_data); Serialization results are put into cache Valueoperations.set (Redis_final_key, Result_json_data, Getexpiretimeseconds (cacheable), timeunit.seconds); } else {//cache hit, this block useless log output, can be customized output System.out.println (Redis_final_key + "hit cache, get Data"); Gets the return value type of the Proxy method class<?> ReturnType = ((methodsignature) jp.getsignature ()). Getreturntype (); Get data format result = GetData (value, returntype); } return result; /** * Returns data based on different class * @param value * @param clazz * @return */public <T> T GetData (Str ing value, class<t> clazz) {T result = Jsonobject.parseobject (value, clazz); return result; }/** * Get method * @param PJP * @return * @throws nosuchmethodexception */public static method Getm Ethod (Proceedingjoinpoint pjp) throws Nosuchmethodexception {//-------------------------------------------------- ------------------------//Get the type of parameter//--------------------------------------------------------------------------object[] args = Pjp.getargs (); class[] argtypes = new Class[pjp.getargs (). length]; for (int i = 0; i < args.length; i++) {Argtypes[i] = Args[i].getclass (); } String MethodName = Pjp.getsignature (). GetName (); class<?> Targetclass = Pjp.gettarget (). GetClass (); Method[] methods = Targetclass.getmethods (); --------------------------------------------------------------------------//Find the function name, number of arguments, class<?> The parameter type (same or subclass) is the same method as the method of interception (the same or sub-Class)//-------------------------------------------------------------------------- method = null; for (int i = 0; i < methods.length; i++) {if (methods[i].getname () = = MethodName) {class< ;? >[] parametertypes = Methods[i].getparametertypes (); Boolean issamemethod = true; If the parameter length of the two method is different, the loop is ended, and if (args.length! = parametertypes.length) is compared with the next method { Continue }//--------------------------------------------------------------------------//Comparison of two Metho Each parameter of D is either the same type or the type of the incoming object is the subclass of the formal parameter//-------------------------------------------------------------------------- for (int j = 0; Parametertypes! = null && J < Parametertypes.length; J + +) { if (parametertypes[j]! = Argtypes[j] &&!parametertypes[j].isassignablefrom (Argtypes[j])) { Issamemethod = false; Break }} if (Issamemethod) {method = Methods[i]; Break }}} return method; /** * Calculates the number of seconds to cache based on expire and dateunit of cacheable annotations * @param cacheable * @return */public int Getexpire Timeseconds (Rediscache rediscache) {int expire = Rediscache.expiretime ();Timeunit unit = Rediscache.dateunit (); if (expire <= 0) {//incoming illegal value, default one minute, 60 seconds return 60; } if (unit = = timeunit.minutes) {return expire * 60; } else if (unit = = timeunit.hours) {return expire * 60 * 60; } else if (unit = = timeunit.days) {return expire * 60 * 60 * 24; }else {//Nothing, default one minute, 60 seconds return 60; } }}
3. Spring Related Configuration
由于是公司的项目,所有包就的路径就去掉了 <!-- aop配置,切面类 .RedisCacheAspect类 bean--> <bean id="redisCacheAspect" class="包.RedisCacheAspect"> </bean> <!-- 拦截所有指定 包和指定类型下的 下所有的方法 ,你是想这个在哪些包下可以实现--> <aop:config proxy-target-class="true"> <aop:aspect ref="redisCacheAspect"> <aop:pointcut id="redisCacheAopPointcut" expression="(execution(* 包.business.web.service.*.*(..)) and @annotation(cacheable))"/> <!-- 环绕 ,命中缓存则直接放回缓存数据,不会往下走,未命中直接放行,直接执行对应的方法--> <aop:around pointcut-ref="redisCacheAopPointcut" method="cache"/> </aop:aspect> </aop:config>
4, Tool class Spel
/** * Spring El expression * * @author SHANGDC * */public class Springexpressionutils {/** * Get cached key key defined on annotations, support Spel expression Note: The parameter of method supports the basic type of JavaBean and map * method to be defined as an object, otherwise it cannot be read to the name * * Example1:phone phone = new phone (); "#{phone.cpu}" is the value of the object, * example2:map apple = new HashMap (); Apple.put ("name", "Good Apple"); "#{apple[name]}" is the value of map * example3: "#{phone.cpu}_#{apple[name]}" * * * @param key * @param method * @param args * @return */public static string Parsekey (String key, Method method, object[] args, Boolean KEYTRANSFORMMD5) {//Gets the list of blocked method parameter names (using Spring support class library) localvariabletableparameternamediscoverer u = new Localvari Abletableparameternamediscoverer (); string[] Paranamearr = U.getparameternames (method); Use Spel for key parsing expressionparser parser = new Spelexpressionparser (); Spel Contextual Standardevaluationcontext context = new Standardevaluationcontext (); Put the method parameter into the SPEL context for (int i = 0; i < paranamearr.length; i++) {context.setvariable (Paranamearr[i], args[i]); } parsercontext ParserContext = new Templateparsercontext (); ----------------------------------------------------------//Replace the #{with #{# to fit the format of the Spel template//------------- ---------------------------------------------//For example, @ Note name (key= "#{player.username}", expire = $)//#{PHONE[CP U]}_#{phone[ram]}//#{player.username}_#{phone[cpu]}_#{phone[ram]}_#{pageno}_#{pagesize} Object ReturnVal = Parser.parseexpression (Key.replace ("#{", "#{#"), ParserContext). GetValue (context, object.class); This is done for object and string can be converted to string type, can be as key string return_data_key = Jsonobject.tojsonstring (returnval); Converted to MD5 because the Redis key is too long, and the number of such large key is too high, it will occupy memory, affect performance if (KEYTRANSFORMMD5) {Return_data_key = Md5util.diges T (Return_data_key); } return returnval = = null? Null:return_data_key; }}
5. Redis Related Configuration
重要的是自己的redis配置,可能跟我的不太一样,用自己的就好 <!-- redisTemplate defination --> <bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate"> <property name="connectionFactory" ref="jedisConnectionFactory" /> </bean>
Test
public class RedisCacheTest { @Test public void test() { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/spring-applicationContext.xml"); RedisCacheAopService redisCacheAopService = (RedisCacheAopService) context.getBean("redisCacheAopService"); Api api = redisCacheAopService.getApi(1l); System.out.println(api.getClass()); System.out.println(JSONObject.toJSONString(api)); ApiParam param = new ApiParam(); param.setId(2l); param.setApiName("短信服务接口数据");// System.out.println("toString:" + param.toString());// System.out.println(MD5Util.digest(param.toString())); Api api_1 = redisCacheAopService.getApiByParam(param); System.out.println(api_1.getClass()); System.out.println(JSONObject.toJSONString(api_1)); }}
Test printing information:
The general idea is this, need to do their own hands-on practice, not everything is taken directly copy, use, the whole process is not operational, do not know the specific place, what to use, their actual operation, you can get a lot of information.
Secondary information class:
public class Api implements Serializable {private static final long serialversionuid = 1L; /** * * Self-increment primary Key ID */private Long ID; /** * * API name */private String apiname; /** * * API Description */private String apidescription; /** * * Valid time * */private Integer valid; /** * Processing class */private String handlerclass; /** * * * */private Date updatetime; /** * * * */private Date createtime; Public Api () {} public String toString () {return ' ID: ' + ID + ', apiname: "+ Apiname +", Apidescription: " + apidescription + ", valid:" + valid + ", UpdateTime:" + UpdateTime + ", Createtime:" + createtime; } public Long GetId () {return this.id; } public void SetId (Long id) {this.id = ID; } public String Getapiname () {return this.apiname; } public void Setapiname (String apiname) {this.apiname = Apiname; } public String getapidescription () {return this.apidescription; } public void Setapidescription (String apidescription) {this.apidescription = apidescription; } public Integer Getvalid () {return this.valid; } public void Setvalid (Integer valid) {this.valid = valid; Public Date Getupdatetime () {return this.updatetime; } public void Setupdatetime (Date updatetime) {this.updatetime = UpdateTime; Public Date Getcreatetime () {return this.createtime; } public void Setcreatetime (Date createtime) {this.createtime = Createtime; } public String Gethandlerclass () {return handlerclass; } public void Sethandlerclass (String handlerclass) {this.handlerclass = Handlerclass; }} parameter class information public class Apiparam {/** * */private static final long serialversionuid = 1L; /** * API table PRIMARY KEY ID */private Long ID; /** * * API Name * */Private StriNg Apiname; /** * * Valid or invalid */private Integer valid; Public String Getapiname () {return apiname; } public void Setapiname (String apiname) {this.apiname = Apiname; } public Integer Getvalid () {return valid; } public void Setvalid (Integer valid) {this.valid = valid; } public Long GetId () {return id; } public void SetId (Long id) {this.id = ID; }/* @Override public String toString () {return "Apiparam [id=" + ID + ", apiname=" + Apiname + ", valid=" + Valid + "]"; }*/}
Spring AOP consolidates Redis for unified cache management