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