前言:
由于项目的原因,需要对项目中大量访问多修改少的数据进行缓存并管理,为达到开发过程中通过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进行拦截并进行相应的操作,
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Resource;
import net.rubyeye.xmemcached.MemcachedClient;
import net.rubyeye.xmemcached.exception.MemcachedException;
import ognl.Ognl;
import ognl.OgnlException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class MemcachedCacheInterceptor {
private final String GET = "@annotation(LoadFromMemcached)";
private final String UPDATE = "@annotation(UpdateForMemcached)";
// 替换为其他缓存组件即可切换为其他缓存系统,这里是使用的Memcached。如果再抽象一层缓存系统管理,则可以动态的更换缓存系统。
@Resource
private MemcachedClient cache;
private Logger log = LoggerFactory
.getLogger(MemcachedCacheInterceptor.class);
/**
*
* @Title: get
* @Description: 首先从缓存中加载数据,缓存命中则返回数据,未命中则从数据库查找,并加入缓存
* @param @param call
* @param @return
* @param @throws Throwable
* @return Object
* @throws
*/
@Around(GET)
public Object get(ProceedingJoinPoint call) throws Throwable {
LoadFromMemcached anno = getAnnotation(call, LoadFromMemcached.class);
String key = anno.value();
int timeSocpe = anno.timeScope();
if (!executeCondition(anno.condition(), call)) {// 不满足条件,直接调用方法,不进行缓存AOP操作
return call.proceed();
}
key = getKeyNameFromParam(key, call);
Object value = null;
try {
value = cache.get(key);
} catch (TimeoutException e) {
log.error("Get Data From Memcached TimeOut!About Key:" + key, e);
e.printStackTrace();
} catch (InterruptedException e) {
log.error(
"Get Data From Memcached TimeOut And Interrupted!About Key:"
+ key, e);
e.printStackTrace();
} catch (MemcachedException e) {
log.error(
"Get Data From Memcached And Happend A Unexpected Error!About Key:"
+ key, e);
e.printStackTrace();
}
if (value == null) {
value = call.proceed();
if (value != null) {
try {
cache.add(key, timeSocpe, value);
log.info("Add Data For Memcached Success!About Key:" + key);
} catch (TimeoutException e) {
log.error(
"Add Data For Memcached TimeOut!About Key:" + key,
e);
e.printStackTrace();
} catch (InterruptedException e) {
log.error(
"Add Data For Memcached TimeOut And Interrupted!About Key:"
+ key, e);
e.printStackTrace();
} catch (MemcachedException e) {
log.error(
"Add Data For Memcached And Happend A Unexpected Error!About Key:"
+ key, e);
e.printStackTrace();
}
}
}
return value;
}
/**
*
* @Title: update
* @Description: 执行方法的同时更新缓存中的数据
* @param @param call
* @param @return
* @param @throws Throwable
* @return Object
* @throws
*/
@Around(UPDATE)
public Object update(ProceedingJoinPoint call) throws Throwable {
UpdateForMemcached anno = getAnnotation(call, UpdateForMemcached.class);
String[] key = anno.value();// 可能需要更新多个key
Object value = call.proceed();
if (!executeCondition(anno.condition(), call)) {// 不满足条件,直接调用方法,不进行缓存AOP操作
return value;
}
if (value != null) {
try {
for (String singleKey : key) {// 循环处理所有需要更新的key
String tempKey = getKeyNameFromParam(singleKey, call);
cache.delete(tempKey);
}
log.info("Update Data For Memcached Success!About Key:" + key);
} catch (TimeoutException e) {
log.error("Update Data For Memcached TimeOut!About Key:" + key,
e);
e.printStackTrace();
} catch (InterruptedException e) {
log.error(
"Update Data For Memcached TimeOut And Interrupted!About Key:"
+ key, e);
e.printStackTrace();
} catch (MemcachedException e) {
log.error(
"Update Data For Memcached And Happend A Unexpected Error!About Key:"
+ key, e);
e.printStackTrace();
}
}
return value;
}
/**
*
* @Title: getAnnotation
* @Description: 获得Annotation对象
* @param @param <T>
* @param @param jp
* @param @param clazz
* @param @return
* @return T
* @throws
*/
private <T extends Annotation> T getAnnotation(ProceedingJoinPoint jp,
Class<T> clazz) {
MethodSignature joinPointObject = (MethodSignature) jp.getSignature();
Method method = joinPointObject.getMethod();
return method.getAnnotation(clazz);
}
/**
*
* @Title: getKeyNameFromParam
* @Description: 获得组合后的KEY值
* @param @param key
* @param @param jp
* @param @return
* @return String
* @throws
*/
private String getKeyNameFromParam(String key, ProceedingJoinPoint jp) {
if (!key.contains("$")) {
return key;
}
String regexp = "\\$\\{[^\\}]+\\}";
Pattern pattern = Pattern.compile(regexp);
Matcher matcher = pattern.matcher(key);
List<String> names = new ArrayList<String>();
try {
while (matcher.find()) {
names.add(matcher.group());
}
key = executeNames(key, names, jp);
} catch (Exception e) {
log.error("Regex Parse Error!", e);
}
return key;
}
/**
*
* @Title: executeNames
* @Description: 对KEY中的参数进行替换
* @param @param key
* @param @param names
* @param @param jp
* @param @return
* @param @throws OgnlException
* @return String
* @throws
*/
private String executeNames(String key, List<String> names,
ProceedingJoinPoint jp) throws OgnlException {
Method method = ((MethodSignature) jp.getSignature()).getMethod();
// 形参列表
List<String> param = MethodParamNamesScaner.getParamNames(method);
if (names == null || names.size() == 0) {
return key;
}
Object[] params = jp.getArgs();
Map<String, Object> map = new HashMap<String, Object>();
for (int i = 0; i < param.size(); i++) {
map.put(param.get(i), params[i]);
}
for (String name : names) {
String temp = name.substring(2);
temp = temp.substring(0, temp.length() - 1);
key = myReplace(key, name, (String) Ognl.getValue(temp, map));
}
return key;
}
/**
*
* @Title: myReplace
* @Description: 不依赖Regex的替换,避免$符号、{}等在String.replaceAll方法中当做Regex处理时候的问题。
* @param @param src
* @param @param from
* @param @param to
* @param @return
* @return String
* @throws
*/
private String myReplace(String src, String from, String to) {
int index = src.indexOf(from);
if (index == -1) {
return src;
}
return src.substring(0, index) + to
+ src.substring(index + from.length());
}
/**
*
* @Title: executeCondition
* @Description: 判断是否需要进行缓存操作
* @param @param condition parm
* @param @return
* @return boolean true:需要 false:不需要
* @throws
*/
private boolean executeCondition(String condition, ProceedingJoinPoint jp) {
if ("".equals(condition)) {
return true;
}
Method method = ((MethodSignature) jp.getSignature()).getMethod();
// 形参列表
List<String> param = MethodParamNamesScaner.getParamNames(method);
if (param == null || param.size() == 0) {
return true;
}
Object[] params = jp.getArgs();
Map<String, Object> map = new HashMap<String, Object>();
for (int i = 0; i < param.size(); i++) {
map.put(param.get(i), params[i]);
}
boolean returnVal = false;
try {
returnVal = (Boolean) Ognl.getValue(condition, map);
} catch (OgnlException e) {
e.printStackTrace();
}
return returnVal;
}
public void setCache(MemcachedClient cache) {
this.cache = cache;
}
}
辅助类:借用MethodParamNamesScaner类与Ognl结合完成对缓存key的动态解析功能,
//引用至:https://gist.github.com/wendal/2011728,用于解析方法的形参名称
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 通过读取Class文件,获得方法形参名称列表
*
* @author wendal(wendal1985@gmail.com)
*
*/
public class MethodParamNamesScaner {
/**
* 获取Method的形参名称列表
*
* @param method
* 需要解析的方法
* @return 形参名称列表,如果没有调试信息,将返回null
*/
public static List<String> getParamNames(Method method) {
try {
int size = method.getParameterTypes().length;
if (size == 0)
return new ArrayList<String>(0);
List<String> list = getParamNames(method.getDeclaringClass()).get(
getKey(method));
if (list != null && list.size() != size)
return list.subList(0, size);
return list;
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
/**
* 获取Constructor的形参名称列表
*
* @param constructor
* 需要解析的构造函数
* @return 形参名称列表,如果没有调试信息,将返回null
*/
public static List<String> getParamNames(Constructor<?> constructor) {
try {
int size = constructor.getParameterTypes().length;
if (size == 0)
return new ArrayList<String>(0);
List<String> list = getParamNames(constructor.getDeclaringClass())
.get(getKey(constructor));
if (list != null && list.size() != size)
return list.subList(0, size);
return list;
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
// ---------------------------------------------------------------------------------------------------
/**
* 获取一个类的所有方法/构造方法的形参名称Map
*
* @param klass
* 需要解析的类
* @return 所有方法/构造方法的形参名称Map
* @throws IOException
* 如果有任何IO异常,不应该有,如果是本地文件,那100%遇到bug了
*/
public static Map<String, List<String>> getParamNames(Class<?> klass)
throws IOException {
InputStream in = klass.getResourceAsStream("/"
+ klass.getName().replace('.', '/') + ".class");
return getParamNames(in);
}
public static Map<String, List<String>> getParamNames(InputStream in)
throws IOException {
DataInputStream dis = new DataInputStream(new BufferedInputStream(in));
Map<String, List<String>> names = new HashMap<String, List<String>>();
Map<Integer, String> strs = new HashMap<Integer, String>();
dis.skipBytes(4);// Magic
dis.skipBytes(2);// 副版本号
dis.skipBytes(2);// 主版本号
// 读取常量池
int constant_pool_count = dis.readUnsignedShort();
for (int i = 0; i < (constant_pool_count - 1); i++) {
byte flag = dis.readByte();
switch (flag) {
case 7:// CONSTANT_Class:
dis.skipBytes(2);
break;
case 9:// CONSTANT_Fieldref:
case 10:// CONSTANT_Methodref:
case 11:// CONSTANT_InterfaceMethodref:
dis.skipBytes(2);
dis.skipBytes(2);
break;
case 8:// CONSTANT_String:
dis.skipBytes(2);
break;
case 3:// CONSTANT_Integer:
case 4:// CONSTANT_Float:
dis.skipBytes(4);
break;
case 5:// CONSTANT_Long:
case 6:// CONSTANT_Double:
dis.skipBytes(8);
i++;// 必须跳过一个,这是class文件设计的一个缺陷,历史遗留问题
break;
case 12:// CONSTANT_NameAndType:
dis.skipBytes(2);
dis.skipBytes(2);
break;
case 1:// CONSTANT_Utf8:
int len = dis.readUnsignedShort();
byte[] data = new byte[len];
dis.read(data);
strs.put(i + 1, new String(data, "UTF-8"));// 必然是UTF8的
break;
case 15:// CONSTANT_MethodHandle:
dis.skipBytes(1);
dis.skipBytes(2);
break;
case 16:// CONSTANT_MethodType:
dis.skipBytes(2);
break;
case 18:// CONSTANT_InvokeDynamic:
dis.skipBytes(2);
dis.skipBytes(2);
break;
default:
throw new RuntimeException("Impossible!! flag=" + flag);
}
}
dis.skipBytes(2);// 版本控制符
dis.skipBytes(2);// 类名
dis.skipBytes(2);// 超类
// 跳过接口定义
int interfaces_count = dis.readUnsignedShort();
dis.skipBytes(2 * interfaces_count);// 每个接口数据,是2个字节
// 跳过字段定义
int fields_count = dis.readUnsignedShort();
for (int i = 0; i < fields_count; i++) {
dis.skipBytes(2);
dis.skipBytes(2);
dis.skipBytes(2);
int attributes_count = dis.readUnsignedShort();
for (int j = 0; j < attributes_count; j++) {
dis.skipBytes(2);// 跳过访问控制符
int attribute_length = dis.readInt();
dis.skipBytes(attribute_length);
}
}
// 开始读取方法
int methods_count = dis.readUnsignedShort();
for (int i = 0; i < methods_count; i++) {
dis.skipBytes(2); // 跳过访问控制符
String methodName = strs.get(dis.readUnsignedShort());
String descriptor = strs.get(dis.readUnsignedShort());
short attributes_count = dis.readShort();
for (int j = 0; j < attributes_count; j++) {
String attrName = strs.get(dis.readUnsignedShort());
int attribute_length = dis.readInt();
if ("Code".equals(attrName)) { // 形参只在Code属性中
dis.skipBytes(2);
dis.skipBytes(2);
int code_len = dis.readInt();
dis.skipBytes(code_len); // 跳过具体代码
int exception_table_length = dis.readUnsignedShort();
dis.skipBytes(8 * exception_table_length); // 跳过异常表
int code_attributes_count = dis.readUnsignedShort();
for (int k = 0; k < code_attributes_count; k++) {
int str_index = dis.readUnsignedShort();
String codeAttrName = strs.get(str_index);
int code_attribute_length = dis.readInt();
if ("LocalVariableTable".equals(codeAttrName)) {// 形参在LocalVariableTable属性中
int local_variable_table_length = dis
.readUnsignedShort();
List<String> varNames = new ArrayList<String>(
local_variable_table_length);
for (int l = 0; l < local_variable_table_length; l++) {
dis.skipBytes(2);
dis.skipBytes(2);
String varName = strs.get(dis
.readUnsignedShort());
dis.skipBytes(2);
dis.skipBytes(2);
if (!"this".equals(varName)) // 非静态方法,第一个参数是this
varNames.add(varName);
}
names.put(methodName + "," + descriptor, varNames);
} else
dis.skipBytes(code_attribute_length);
}
} else
dis.skipBytes(attribute_length);
}
}
dis.close();
return names;
}
/**
* 传入Method或Constructor,获取getParamNames方法返回的Map所对应的key
*/
public static String getKey(Object obj) {
StringBuilder sb = new StringBuilder();
if (obj instanceof Method) {
sb.append(((Method) obj).getName()).append(',');
getDescriptor(sb, (Method) obj);
} else if (obj instanceof Constructor) {
sb.append("<init>,"); // 只有非静态构造方法才能用有方法参数的,而且通过反射API拿不到静态构造方法
getDescriptor(sb, (Constructor<?>) obj);
} else
throw new RuntimeException("Not Method or Constructor!");
return sb.toString();
}
public static void getDescriptor(StringBuilder sb, Method method) {
sb.append('(');
for (Class<?> klass : method.getParameterTypes())
getDescriptor(sb, klass);
sb.append(')');
getDescriptor(sb, method.getReturnType());
}
public static void getDescriptor(StringBuilder sb,
Constructor<?> constructor) {
sb.append('(');
for (Class<?> klass : constructor.getParameterTypes())
getDescriptor(sb, klass);
sb.append(')');
sb.append('V');
}
/** 本方法来源于ow2的asm库的Type类 */
public static void getDescriptor(final StringBuilder buf, final Class<?> c) {
Class<?> d = c;
while (true) {
if (d.isPrimitive()) {
char car;
if (d == Integer.TYPE) {
car = 'I';
} else if (d == Void.TYPE) {
car = 'V';
} else if (d == Boolean.TYPE) {
car = 'Z';
} else if (d == Byte.TYPE) {
car = 'B';
} else if (d == Character.TYPE) {
car = 'C';
} else if (d == Short.TYPE) {
car = 'S';
} else if (d == Double.TYPE) {
car = 'D';
} else if (d == Float.TYPE) {
car = 'F';
} else /* if (d == Long.TYPE) */{
car = 'J';
}
buf.append(car);
return;
} else if (d.isArray()) {
buf.append('[');
d = d.getComponentType();
} else {
buf.append('L');
String name = d.getName();
int len = name.length();
for (int i = 0; i < len; ++i) {
char car = name.charAt(i);
buf.append(car == '.' ? '/' : car);
}
buf.append(';');
return;
}
}
}
}
使用案例:
1.使用缓存:
/*
* value:缓存中的键,${map.name}会动态替换为传入参数map里面的key为name的值。
* comdition:缓存执行条件:!map.containsKey('execute')表示map中不包含execute这个key的时候才进行缓存操作。
* 这里面的map是传入的参数名称。
* 执行到该方法会自动去缓存里面查找该key,有就直接返回,没有就执行该方法,如果返回值不为空则同时存入缓存并返回结果。
*/
@LoadFromMemcached(value="Resource_selectByMap_${map.name}",condition="!map.containsKey('execute')" )
public List<Resource> selectByMap(Object map) {
return super.selectByMap(map);
}
表示执行该method(selectByMap)的时候会首先去缓存组件中查找数据,如果查找到数据就直接返回,如果找不到数据就执行方法体,并将返回值记录入缓存中。
2.更新缓存:
/*
* 同样value为缓存中的key,${t.name}会动态替换为update方法传入参数Resource的name字段
* comdition:字段作用同上,不演示了
*/
@UpdateForMemcached(value="Resource_selectByMap_${t.name}")
public int update(Resource t) {
return super.update(t);
}
表示执行该method(update)的时候会同步将缓存中的key置为过期(并不是把该方法的返回值放入缓存,只是将对应的缓存设为过期,下次再执行selectByMap的时候获取的就是最新的数据了)。
扩展:
本文只是简单的解决方案,可能有很多不足的地方,欢迎交流,以此简单的结构为基础进行扩展,将MemcachedClient以及相关的缓存操作方法提取出来并完善细节即可完成基本通用的缓存组件。