前言:
由于项目的原因,需要对项目中大量访问多修改少的数据进行缓存并管理,为达到开发过程中通过Annotation简单的配置既可以完成对缓存的设置与更新的需求,故而设计的该简易的解决方案。
涉及技术:
1、Spring AOP
2、Java Annotation
3、Memcache (项目中使用的缓存组件)
4、JVM基础 (Class文件结构,用于解析出方法中的形参名称,动态生成缓存key,目测效率不高0.0)
5、Ognl (用于动态解析缓存的key)
实现细节:
Annotation:LoadFromMemcached 用与method之上的注解,作用是使带有该注解的method在调用的时候先经过缓存查询,缓存中查询不到再去数据库查询并将结果缓存至缓存服务器Memcache中,
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import java.lang.annotation.ElementType; 2 import java.lang.annotation.Retention; 3 import java.lang.annotation.RetentionPolicy; 4 import java.lang.annotation.Target; 5 6 @Retention(RetentionPolicy.RUNTIME) 7 @Target(ElementType.METHOD) 8 public @interface LoadFromMemcached { 9 10 String value();//缓存的key 11 12 int timeScope() default 600;//默认过期时间,单位秒 13 14 String condition() default "";//执行缓存查询的条件 15 16 }
Annotation:UpdateForMemcached 类似于LoadFromMemcached,作用是使带有该注解的method在调用的时候更新缓存服务器中的缓存,
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import java.lang.annotation.ElementType; 2 import java.lang.annotation.Retention; 3 import java.lang.annotation.RetentionPolicy; 4 import java.lang.annotation.Target; 5 6 @Retention(RetentionPolicy.RUNTIME) 7 @Target(ElementType.METHOD) 8 public @interface UpdateForMemcached { 9 10 String[] value();//可能有多个key需要更新 11 12 String condition() default "";//执行缓存的条件 13 14 }
AOP:MemcachedCacheInterceptor 缓存AOP实现的核心类,用于对Annotation注解了的method进行拦截并进行相应的操作,
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import java.lang.annotation.Annotation; 2 import java.lang.reflect.Method; 3 import java.util.ArrayList; 4 import java.util.HashMap; 5 import java.util.List; 6 import java.util.Map; 7 import java.util.concurrent.TimeoutException; 8 import java.util.regex.Matcher; 9 import java.util.regex.Pattern; 10 11 import javax.annotation.Resource; 12 13 import net.rubyeye.xmemcached.MemcachedClient; 14 import net.rubyeye.xmemcached.exception.MemcachedException; 15 16 import ognl.Ognl; 17 import ognl.OgnlException; 18 19 import org.aspectj.lang.ProceedingJoinPoint; 20 import org.aspectj.lang.annotation.Around; 21 import org.aspectj.lang.annotation.Aspect; 22 import org.aspectj.lang.reflect.MethodSignature; 23 import org.slf4j.Logger; 24 import org.slf4j.LoggerFactory; 25 import org.springframework.stereotype.Component; 26 27 @Component 28 @Aspect 29 public class MemcachedCacheInterceptor { 30 31 private final String GET = "@annotation(LoadFromMemcached)"; 32 private final String UPDATE = "@annotation(UpdateForMemcached)"; 33 //替换为其他缓存组件即可切换为其他缓存系统,这里是使用的Memcached。如果再抽象一层缓存系统管理,则可以动态的更换缓存系统。 34 @Resource 35 private MemcachedClient cache; 36 37 private Logger log = LoggerFactory.getLogger(MemcachedCacheInterceptor.class); 38 39 /** 40 * 41 * @Title: get 42 * @Description: 首先从缓存中加载数据,缓存命中则返回数据,未命中则从数据库查找,并加入缓存 43 * @param @param call 44 * @param @return 45 * @param @throws Throwable 46 * @return Object 47 * @throws 48 */ 49 @Around(GET) 50 public Object get(ProceedingJoinPoint call) throws Throwable { 51 52 LoadFromMemcached anno = getAnnotation(call,LoadFromMemcached.class); 53 String key = anno.value(); 54 int timeSocpe = anno.timeScope(); 55 56 if(!executeCondition(anno.condition(),call)){//不满足条件,直接调用方法,不进行缓存AOP操作 57 return call.proceed(); 58 } 59 60 key = getKeyNameFromParam(key,call); 61 62 Object value = null; 63 64 try { 65 value = cache.get(key); 66 } catch (TimeoutException e) { 67 log.error("Get Data From Memcached TimeOut!About Key:"+key,e); 68 e.printStackTrace(); 69 } catch (InterruptedException e) { 70 log.error("Get Data From Memcached TimeOut And Interrupted!About Key:"+key,e); 71 e.printStackTrace(); 72 } catch (MemcachedException e) { 73 log.error("Get Data From Memcached And Happend A Unexpected Error!About Key:"+key,e); 74 e.printStackTrace(); 75 } 76 77 if(value == null){ 78 value = call.proceed(); 79 if(value != null){ 80 try { 81 cache.add(key, timeSocpe, value); 82 log.info("Add Data For Memcached Success!About Key:"+key); 83 } catch (TimeoutException e) { 84 log.error("Add Data For Memcached TimeOut!About Key:"+key,e); 85 e.printStackTrace(); 86 } catch (InterruptedException e) { 87 log.error("Add Data For Memcached TimeOut And Interrupted!About Key:"+key,e); 88 e.printStackTrace(); 89 } catch (MemcachedException e) { 90 log.error("Add Data For Memcached And Happend A Unexpected Error!About Key:"+key,e); 91 e.printStackTrace(); 92 } 93 } 94 } 95 96 return value; 97 } 98 99 /** 100 * 101 * @Title: update 102 * @Description: 执行方法的同时更新缓存中的数据 103 * @param @param call 104 * @param @return 105 * @param @throws Throwable 106 * @return Object 107 * @throws 108 */ 109 @Around(UPDATE) 110 public Object update(ProceedingJoinPoint call) throws Throwable { 111 112 UpdateForMemcached anno = getAnnotation(call,UpdateForMemcached.class); 113 String[] key = anno.value();//可能需要更新多个key 114 115 Object value = call.proceed(); 116 if(!executeCondition(anno.condition(),call)){//不满足条件,直接调用方法,不进行缓存AOP操作 117 return value; 118 } 119 120 if(value != null){ 121 try { 122 for(String singleKey:key){//循环处理所有需要更新的key 123 String tempKey = getKeyNameFromParam(singleKey, call); 124 cache.delete(tempKey); 125 } 126 log.info("Update Data For Memcached Success!About Key:"+key); 127 } catch (TimeoutException e) { 128 log.error("Update Data For Memcached TimeOut!About Key:"+key,e); 129 e.printStackTrace(); 130 } catch (InterruptedException e) { 131 log.error("Update Data For Memcached TimeOut And Interrupted!About Key:"+key,e); 132 e.printStackTrace(); 133 } catch (MemcachedException e) { 134 log.error("Update Data For Memcached And Happend A Unexpected Error!About Key:"+key,e); 135 e.printStackTrace(); 136 } 137 138 } 139 return value; 140 } 141 142 /** 143 * 144 * @Title: getAnnotation 145 * @Description: 获得Annotation对象 146 * @param @param <T> 147 * @param @param jp 148 * @param @param clazz 149 * @param @return 150 * @return T 151 * @throws 152 */ 153 private <T extends Annotation> T getAnnotation(ProceedingJoinPoint jp,Class<T> clazz){ 154 MethodSignature joinPointObject = (MethodSignature) jp.getSignature(); 155 Method method = joinPointObject.getMethod(); 156 return method.getAnnotation(clazz); 157 } 158 159 /** 160 * 161 * @Title: getKeyNameFromParam 162 * @Description: 获得组合后的KEY值 163 * @param @param key 164 * @param @param jp 165 * @param @return 166 * @return String 167 * @throws 168 */ 169 private String getKeyNameFromParam(String key,ProceedingJoinPoint jp){ 170 if(!key.contains("$")){ 171 return key; 172 } 173 174 String regexp = "\$\{[^\}]+\}"; 175 Pattern pattern = Pattern.compile(regexp); 176 Matcher matcher = pattern.matcher(key); 177 List<String> names = new ArrayList<String>(); 178 try{ 179 while(matcher.find()){ 180 names.add(matcher.group()); 181 } 182 key = executeNames(key,names,jp); 183 }catch (Exception e) { 184 log.error("Regex Parse Error!", e); 185 } 186 187 188 return key; 189 } 190 191 /** 192 * 193 * @Title: executeNames 194 * @Description: 对KEY中的参数进行替换 195 * @param @param key 196 * @param @param names 197 * @param @param jp 198 * @param @return 199 * @param @throws OgnlException 200 * @return String 201 * @throws 202 */ 203 private String executeNames(String key, List<String> names,ProceedingJoinPoint jp) throws OgnlException { 204 205 Method method = ((MethodSignature)jp.getSignature()).getMethod(); 206 207 //形参列表 208 List<String> param = MethodParamNamesScaner.getParamNames(method); 209 210 if(names==null||names.size()==0){ 211 return key; 212 } 213 214 Object[] params = jp.getArgs(); 215 216 Map<String,Object> map = new HashMap<String,Object>(); 217 for(int i=0;i<param.size();i++){ 218 map.put(param.get(i), params[i]); 219 } 220 221 for(String name:names){ 222 String temp = name.substring(2); 223 temp = temp.substring(0,temp.length()-1); 224 key = myReplace(key,name, (String)Ognl.getValue(temp, map)); 225 } 226 227 return key; 228 } 229 230 /** 231 * 232 * @Title: myReplace 233 * @Description: 不依赖Regex的替换,避免$符号、{}等在String.replaceAll方法中当做Regex处理时候的问题。 234 * @param @param src 235 * @param @param from 236 * @param @param to 237 * @param @return 238 * @return String 239 * @throws 240 */ 241 private String myReplace(String src,String from,String to){ 242 int index = src.indexOf(from); 243 if(index == -1){ 244 return src; 245 } 246 247 return src.substring(0,index)+to+src.substring(index+from.length()); 248 } 249 250 251 /** 252 * 253 * @Title: executeCondition 254 * @Description: 判断是否需要进行缓存操作 255 * @param @param condition parm 256 * @param @return 257 * @return boolean true:需要 false:不需要 258 * @throws 259 */ 260 private boolean executeCondition(String condition,ProceedingJoinPoint jp){ 261 262 if("".equals(condition)){ 263 return true; 264 } 265 266 Method method = ((MethodSignature)jp.getSignature()).getMethod(); 267 268 //形参列表 269 List<String> param = MethodParamNamesScaner.getParamNames(method); 270 271 if(param==null||param.size()==0){ 272 return true; 273 } 274 275 Object[] params = jp.getArgs(); 276 277 Map<String,Object> map = new HashMap<String,Object>(); 278 for(int i=0;i<param.size();i++){ 279 map.put(param.get(i), params[i]); 280 } 281 boolean returnVal = false; 282 try { 283 returnVal = (Boolean) Ognl.getValue(condition, map); 284 } catch (OgnlException e) { 285 e.printStackTrace(); 286 } 287 288 return returnVal; 289 } 290 291 public void setCache(MemcachedClient cache) { 292 this.cache = cache; 293 } 294 295 }
辅助类:借用MethodParamNamesScaner类与Ognl结合完成对缓存key的动态解析功能,
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //引用至:https://gist.github.com/wendal/2011728,用于解析方法的形参名称 2 import java.io.BufferedInputStream; 3 import java.io.DataInputStream; 4 import java.io.IOException; 5 import java.io.InputStream; 6 import java.lang.reflect.Constructor; 7 import java.lang.reflect.Method; 8 import java.util.ArrayList; 9 import java.util.HashMap; 10 import java.util.List; 11 import java.util.Map; 12 13 /** 14 * 通过读取Class文件,获得方法形参名称列表 15 * @author wendal(wendal1985@gmail.com) 16 * 17 */ 18 public class MethodParamNamesScaner { 19 20 /** 21 * 获取Method的形参名称列表 22 * @param method 需要解析的方法 23 * @return 形参名称列表,如果没有调试信息,将返回null 24 */ 25 public static List<String> getParamNames(Method method) { 26 try { 27 int size = method.getParameterTypes().length; 28 if (size == 0) 29 return new ArrayList<String>(0); 30 List<String> list = getParamNames(method.getDeclaringClass()).get(getKey(method)); 31 if (list != null && list.size() != size) 32 return list.subList(0, size); 33 return list; 34 } catch (Throwable e) { 35 throw new RuntimeException(e); 36 } 37 } 38 39 /** 40 * 获取Constructor的形参名称列表 41 * @param constructor 需要解析的构造函数 42 * @return 形参名称列表,如果没有调试信息,将返回null 43 */ 44 public static List<String> getParamNames(Constructor<?> constructor) { 45 try { 46 int size = constructor.getParameterTypes().length; 47 if (size == 0) 48 return new ArrayList<String>(0); 49 List<String> list = getParamNames(constructor.getDeclaringClass()).get(getKey(constructor)); 50 if (list != null && list.size() != size) 51 return list.subList(0, size); 52 return list; 53 } catch (Throwable e) { 54 throw new RuntimeException(e); 55 } 56 } 57 58 //--------------------------------------------------------------------------------------------------- 59 60 /** 61 * 获取一个类的所有方法/构造方法的形参名称Map 62 * @param klass 需要解析的类 63 * @return 所有方法/构造方法的形参名称Map 64 * @throws IOException 如果有任何IO异常,不应该有,如果是本地文件,那100%遇到bug了 65 */ 66 public static Map<String, List<String>> getParamNames(Class<?> klass) throws IOException { 67 InputStream in = klass.getResourceAsStream("/" + klass.getName().replace('.', '/') + ".class"); 68 return getParamNames(in); 69 } 70 71 public static Map<String, List<String>> getParamNames(InputStream in) throws IOException { 72 DataInputStream dis = new DataInputStream(new BufferedInputStream(in)); 73 Map<String, List<String>> names = new HashMap<String, List<String>>(); 74 Map<Integer, String> strs = new HashMap<Integer, String>(); 75 dis.skipBytes(4);//Magic 76 dis.skipBytes(2);//副版本号 77 dis.skipBytes(2);//主版本号 78 79 //读取常量池 80 int constant_pool_count = dis.readUnsignedShort(); 81 for (int i = 0; i < (constant_pool_count - 1); i++) { 82 byte flag = dis.readByte(); 83 switch (flag) { 84 case 7://CONSTANT_Class: 85 dis.skipBytes(2); 86 break; 87 case 9://CONSTANT_Fieldref: 88 case 10://CONSTANT_Methodref: 89 case 11://CONSTANT_InterfaceMethodref: 90 dis.skipBytes(2); 91 dis.skipBytes(2); 92 break; 93 case 8://CONSTANT_String: 94 dis.skipBytes(2); 95 break; 96 case 3://CONSTANT_Integer: 97 case 4://CONSTANT_Float: 98 dis.skipBytes(4); 99 break; 100 case 5://CONSTANT_Long: 101 case 6://CONSTANT_Double: 102 dis.skipBytes(8); 103 i++;//必须跳过一个,这是class文件设计的一个缺陷,历史遗留问题 104 break; 105 case 12://CONSTANT_NameAndType: 106 dis.skipBytes(2); 107 dis.skipBytes(2); 108 break; 109 case 1://CONSTANT_Utf8: 110 int len = dis.readUnsignedShort(); 111 byte[] data = new byte[len]; 112 dis.read(data); 113 strs.put(i + 1, new String(data, "UTF-8"));//必然是UTF8的 114 break; 115 case 15://CONSTANT_MethodHandle: 116 dis.skipBytes(1); 117 dis.skipBytes(2); 118 break; 119 case 16://CONSTANT_MethodType: 120 dis.skipBytes(2); 121 break; 122 case 18://CONSTANT_InvokeDynamic: 123 dis.skipBytes(2); 124 dis.skipBytes(2); 125 break; 126 default: 127 throw new RuntimeException("Impossible!! flag="+flag); 128 } 129 } 130 131 dis.skipBytes(2);//版本控制符 132 dis.skipBytes(2);//类名 133 dis.skipBytes(2);//超类 134 135 //跳过接口定义 136 int interfaces_count = dis.readUnsignedShort(); 137 dis.skipBytes(2 * interfaces_count);//每个接口数据,是2个字节 138 139 //跳过字段定义 140 int fields_count = dis.readUnsignedShort(); 141 for (int i = 0; i < fields_count; i++) { 142 dis.skipBytes(2); 143 dis.skipBytes(2); 144 dis.skipBytes(2); 145 int attributes_count = dis.readUnsignedShort(); 146 for (int j = 0; j < attributes_count; j++) { 147 dis.skipBytes(2);//跳过访问控制符 148 int attribute_length = dis.readInt(); 149 dis.skipBytes(attribute_length); 150 } 151 } 152 153 //开始读取方法 154 int methods_count = dis.readUnsignedShort(); 155 for (int i = 0; i < methods_count; i++) { 156 dis.skipBytes(2); //跳过访问控制符 157 String methodName = strs.get(dis.readUnsignedShort()); 158 String descriptor = strs.get(dis.readUnsignedShort()); 159 short attributes_count = dis.readShort(); 160 for (int j = 0; j < attributes_count; j++) { 161 String attrName = strs.get(dis.readUnsignedShort()); 162 int attribute_length = dis.readInt(); 163 if ("Code".equals(attrName)) { //形参只在Code属性中 164 dis.skipBytes(2); 165 dis.skipBytes(2); 166 int code_len = dis.readInt(); 167 dis.skipBytes(code_len); //跳过具体代码 168 int exception_table_length = dis.readUnsignedShort(); 169 dis.skipBytes(8 * exception_table_length); //跳过异常表 170 171 int code_attributes_count = dis.readUnsignedShort(); 172 for (int k = 0; k < code_attributes_count; k++) { 173 int str_index = dis.readUnsignedShort(); 174 String codeAttrName = strs.get(str_index); 175 int code_attribute_length = dis.readInt(); 176 if ("LocalVariableTable".equals(codeAttrName)) {//形参在LocalVariableTable属性中 177 int local_variable_table_length = dis.readUnsignedShort(); 178 List<String> varNames = new ArrayList<String>(local_variable_table_length); 179 for (int l = 0; l < local_variable_table_length; l++) { 180 dis.skipBytes(2); 181 dis.skipBytes(2); 182 String varName = strs.get(dis.readUnsignedShort()); 183 dis.skipBytes(2); 184 dis.skipBytes(2); 185 if (!"this".equals(varName)) //非静态方法,第一个参数是this 186 varNames.add(varName); 187 } 188 names.put(methodName + "," + descriptor, varNames); 189 } else 190 dis.skipBytes(code_attribute_length); 191 } 192 } else 193 dis.skipBytes(attribute_length); 194 } 195 } 196 dis.close(); 197 return names; 198 } 199 200 /** 201 * 传入Method或Constructor,获取getParamNames方法返回的Map所对应的key 202 */ 203 public static String getKey(Object obj) { 204 StringBuilder sb = new StringBuilder(); 205 if (obj instanceof Method) { 206 sb.append(((Method)obj).getName()).append(','); 207 getDescriptor(sb, (Method)obj); 208 } else if (obj instanceof Constructor) { 209 sb.append("<init>,"); //只有非静态构造方法才能用有方法参数的,而且通过反射API拿不到静态构造方法 210 getDescriptor(sb, (Constructor<?>)obj); 211 } else 212 throw new RuntimeException("Not Method or Constructor!"); 213 return sb.toString(); 214 } 215 216 public static void getDescriptor(StringBuilder sb ,Method method){ 217 sb.append('('); 218 for (Class<?> klass : method.getParameterTypes()) 219 getDescriptor(sb, klass); 220 sb.append(')'); 221 getDescriptor(sb, method.getReturnType()); 222 } 223 224 public static void getDescriptor(StringBuilder sb , Constructor<?> constructor){ 225 sb.append('('); 226 for (Class<?> klass : constructor.getParameterTypes()) 227 getDescriptor(sb, klass); 228 sb.append(')'); 229 sb.append('V'); 230 } 231 232 /**本方法来源于ow2的asm库的Type类*/ 233 public static void getDescriptor(final StringBuilder buf, final Class<?> c) { 234 Class<?> d = c; 235 while (true) { 236 if (d.isPrimitive()) { 237 char car; 238 if (d == Integer.TYPE) { 239 car = 'I'; 240 } else if (d == Void.TYPE) { 241 car = 'V'; 242 } else if (d == Boolean.TYPE) { 243 car = 'Z'; 244 } else if (d == Byte.TYPE) { 245 car = 'B'; 246 } else if (d == Character.TYPE) { 247 car = 'C'; 248 } else if (d == Short.TYPE) { 249 car = 'S'; 250 } else if (d == Double.TYPE) { 251 car = 'D'; 252 } else if (d == Float.TYPE) { 253 car = 'F'; 254 } else /* if (d == Long.TYPE) */{ 255 car = 'J'; 256 } 257 buf.append(car); 258 return; 259 } else if (d.isArray()) { 260 buf.append('['); 261 d = d.getComponentType(); 262 } else { 263 buf.append('L'); 264 String name = d.getName(); 265 int len = name.length(); 266 for (int i = 0; i < len; ++i) { 267 char car = name.charAt(i); 268 buf.append(car == '.' ? '/' : car); 269 } 270 buf.append(';'); 271 return; 272 } 273 } 274 } 275 }
使用案例:
1.使用缓存:
1 /** 2 * value:缓存中的键,${map.name}会动态替换为传入参数map里面的key为name的值。 3 * comdition:缓存执行条件:!map.containsKey('execute')表示map中不包含execute这个key的时候才进行缓存操作。 4 * 这里面的map是传入的参数名称。 5 * 执行到该方法会自动去缓存里面查找该key,有就直接返回,没有就执行该方法,如果返回值不为空则同时存入缓存并返回结果。 6 */ 7 @LoadFromMemcached(value="Resource_selectByMap_${map.name}",condition="!map.containsKey('execute')" ) 8 public List<Resource> selectByMap(Object map) { 9 return super.selectByMap(map); 10 }
表示执行该method(selectByMap)的时候会首先去缓存组件中查找数据,如果查找到数据就直接返回,如果找不到数据就执行方法体,并将返回值记录入缓存中。
2.更新缓存:
1 /* 2 * 同样value为缓存中的key,${t.name}会动态替换为update方法传入参数Resource的name字段 3 * comdition:字段作用同上,不演示了 4 */ 5 @UpdateForMemcached(value="Resource_selectByMap_${t.name}") 6 public int update(Resource t) { 7 return super.update(t); 8 }
表示执行该method(update)的时候会同步将缓存中的key置为过期(并不是把该方法的返回值放入缓存,只是将对应的缓存设为过期,下次再执行selectByMap的时候获取的就是最新的数据了)。
扩展:
本文只是简单的解决方案,可能有很多不足的地方,欢迎交流0.0,以此简单的结构为基础进行扩展,将MemcachedClient以及相关的缓存操作方法提取出来并完善细节即可完成基本通用的缓存组件。