最近使用Spring cache,发现使用默认生成的key策略只使用了方法入参作为key,很不灵活,用到真实的项目中也不太靠谱,于是自己实现它的key生成策略。
参考官方文档:http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cache.html
spring 默认的key生成实现是
org.springframework.cache.interceptor.SimpleKeyGenerator
感兴趣的同学自己看下,具体我就不描述了。
自定义生成策略需要实现: 接口对应方法参数,需要调用的目标对象实例,目标方法,目标方法入参
public interface KeyGenerator {
/**
* Generate a key for the given method and its parameters.
* @param target the target instance
* @param method the method being called
* @param params the method parameters (with any var-args expanded)
* @return a generated key
*/
Object generate(Object target, Method method, Object... params);
}
下面展示我实现的方式,仅供参考
/**
* 实现spring cache的默认缓存实现策略
* @author lisuo
*
*/
public class DefaultKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
return new DefaultKey(target, method, params);
}
}
这里必须要知道的是,spring cache在判断是否命中数据时,是拿着生成的(key对象)调用它的是equals进行匹配,如equals相同则为命中,返回缓存数据,其次key对象必须是实现Serializable接口的。
下面展示我的具体Key实现:结合我的业务(说白了就是拿着现有的参数,目标对象,目标方法,目标入参,生成一个key对象,如果下一个对象参数一直,让他们的equals相同,就可以命中数据了..)
/**
* Spring cache key 生成策略
* 类名+方法名+参数信息
* 如果key的hashCode与equals一致,认为是同一个Key
* 如果传入对象是BaseModel,那么便利它所有的一级属性,如果所有一级属性的hashCode一致,
* 则认为Key相同
* @author lisuo
*/
public class DefaultKey implements Serializable{
private static final long serialVersionUID = 1930236297081366076L;
/** 调用目标对象全类名 */
private String targetClassName;
/** 调用目标方法名称 */
private String methodName;
/** 调用目标参数 */
private Object[] params;
private final int hashCode;
public DefaultKey(Object target, Method method, Object[] elements) {
this.targetClassName = target.getClass().getName();
this.methodName = generatorMethodName(method);
if(ArrayUtils.isNotEmpty(elements)){
this.params = new Object[elements.length];
for(int i =0;i<elements.length;i++){
Object ele = elements[i];
if(ele instanceof BaseModel){
this.params[i] = ReflectUtil.beanToMap(ele);
}else{
this.params[i] = ele;
}
}
}
this.hashCode = generatorHashCode();
}
private String generatorMethodName(Method method){
StringBuilder builder = new StringBuilder(method.getName());
Class<?>[] types = method.getParameterTypes();
if(ArrayUtils.isNotEmpty(types)){
builder.append("(");
for(Class<?> type:types){
String name = type.getName();
builder.append(name+",");
}
builder.append(")");
}
return builder.toString();
}
//生成hashCode
private int generatorHashCode() {
final int prime = 31;
int result = 1;
result = prime * result + hashCode;
result = prime * result + ((methodName == null) ? 0 : methodName.hashCode());
result = prime * result + Arrays.hashCode(params);
result = prime * result + ((targetClassName == null) ? 0 : targetClassName.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DefaultKey other = (DefaultKey) obj;
if (hashCode != other.hashCode)
return false;
if (methodName == null) {
if (other.methodName != null)
return false;
} else if (!methodName.equals(other.methodName))
return false;
if (!Arrays.equals(params, other.params))
return false;
if (targetClassName == null) {
if (other.targetClassName != null)
return false;
} else if (!targetClassName.equals(other.targetClassName))
return false;
return true;
}
@Override
public final int hashCode() {
return hashCode;
}
}
其实代码比较简单,重要点在于:ReflectUtil.beanToMap(ele);那一行,把javaBean转换成Map。
查看具体的Map底层对equals的实现,它是对它包含的所以数据进行equals比较的:
参考java.util.AbstractMap<K, V>
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Map))
return false;
Map<?, ?> m = (Map<?, ?>) o;
if (m.size() != size())
return false;
try {
Iterator<Entry<K, V>> i = entrySet().iterator();
while (i.hasNext()) {
Entry<K, V> e = i.next();
K key = e.getKey();
V value = e.getValue();
if (value == null) {
if (!(m.get(key) == null && m.containsKey(key)))
return false;
} else {
if (!value.equals(m.get(key)))
return false;
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
return true;
}
具体的ReflectUtil.beanToMap(ele);如何实现参考下面工程中ReflectUtil类。