spring目前在@Cacheable和@CacheEvict等註解上不支持緩存時效設置,只允許通過配置文件設置全局時效。這樣就很不方便設定時間。比如系統參數和業務數據的時效是不一樣的,這給程序開發造成很大的困擾。不得已,我重寫了spring的這兩個註解。以下是具體實現。
首先定義@Cacheable和@CacheEvict註解類。
package com.lh.common.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.rd.ifaes.common.dict.ExpireTime;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Cacheable {
public String key() default ""; // 緩存key
public ExpireTime expire() default ExpireTime.NONE; // 緩存時效,默認無限期
}
package com.lh.common.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 緩存清除
* @author lh
* @version 3.0
* @since 2016-8-28
*
*/
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CacheEvict {
String key() default "";// 緩存key
}
具體的切面代碼(CacheAspect.java)如下:
package com.lh.common.annotation;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import com.rd.ifaes.common.util.ReflectionUtils;
import com.rd.ifaes.common.util.StringUtils;
import com.rd.ifaes.core.core.util.CacheUtils;
@Aspect
@Component
public class CacheAspect {
@SuppressWarnings("rawtypes")
@Autowired
private RedisTemplate redisTemplate;
@Around("@annotation(cache)")
public Object cacheable(final ProceedingJoinPoint pjp, Cacheable cache) throws Throwable {
String key = getCacheKey(pjp, cache.key());
// //方案一:使用自定義緩存工具類操作緩存
// Object value = CacheUtils.getObj(key);// 從緩存獲取數據
// if (value != null) {
// return value; // 如果有數據,則直接返回
// }
// value = pjp.proceed(); // 緩存,到後端查詢數據
// if (value != null) {
// CacheUtils.set(key, value, cache.expire());
// }
// 方案二:使用redisTemplate操作緩存
@SuppressWarnings("unchecked")
ValueOperations<String, Object> valueOper = redisTemplate.opsForValue();
Object value = valueOper.get(key); // 從緩存獲取數據
if (value != null) {
return value; // 如果有數據,則直接返回
}
value = pjp.proceed(); // 緩存,到後端查詢數據
CacheUtils.set(key, value, cache.expire());
if (cache.expire().getTime() <= 0) { // 如果沒有設置過期時間,則無限期緩存
valueOper.set(key, value);
} else { // 否則設置緩存時間
valueOper.set(key, value, cache.expire().getTime(), TimeUnit.SECONDS);
}
return value;
}
@SuppressWarnings("unchecked")
@Around("@annotation(evict)")
public Object cacheEvict(final ProceedingJoinPoint pjp, CacheEvict evict) throws Throwable {
Object value = pjp.proceed(); // 執行方法
String key = getCacheKey(pjp, evict.key());
// //方案一:使用自定義緩存工具類操作緩存
// CacheUtils.del(key);
// 方案二:使用redisTemplate操作緩存
if (evict.key().equals(key)) {// 支持批量刪除
Set<String> keys = redisTemplate.keys(key.concat("*"));
redisTemplate.delete(keys);
}else{
redisTemplate.delete(key);
}
return value;
}
/**
* 獲取緩存的key值
*
* @param pjp
* @param key
* @return
*/
private String getCacheKey(ProceedingJoinPoint pjp, String key) {
StringBuilder buf = new StringBuilder();
Object[] args = pjp.getArgs();
if(StringUtils.isNotBlank(key)){
buf.append(key);
List<String> annoParamNames = AopUtils.getAnnoParams(key);
String[] methodParamNames = AopUtils.getMethodParamNames(AopUtils.getMethod(pjp));
if(!CollectionUtils.isEmpty(annoParamNames)){
for (String ap : annoParamNames) {
String paramValue = "";
for (int i = 0; i < methodParamNames.length; i++) {
if(ap.startsWith(methodParamNames[i])){
Object arg = args[i];
if (ap.contains(".")) {
paramValue = String.valueOf(ReflectionUtils.invokeGetter(arg, ap.substring(ap.indexOf(".") + 1)));
} else {
paramValue = String.valueOf(arg);
}
}
}
int start = buf.indexOf("{" + ap);
int end = start + ap.length() + 2;
buf = buf.replace(start, end, paramValue);
}
}
}else{
buf.append(pjp.getSignature().getDeclaringTypeName()).append(":").append(pjp.getSignature().getName());
for (Object arg : args) {
buf.append(":").append(arg.toString());
}
}
return buf.toString();
}
}
裏面使用到AopUtils.java和ExpireTime.java兩個類,具體代碼如下:
package com.lh.common.annotation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.asm.*;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 切面編程工具類
* @author lh
* @version 3.0
* @since 2016-8-26
*/
public class AopUtils {
/**
* <p>獲取方法的參數名</p>
*
* @param m
* @return
*/
public static String[] getMethodParamNames(final Method m) {
final String[] paramNames = new String[m.getParameterTypes().length];
final String n = m.getDeclaringClass().getName();
final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
String className = m.getDeclaringClass().getSimpleName();
ClassReader cr = null;
InputStream resourceAsStream = null;
try {
// cr = new ClassReader(n);
// String filePathName = Class.forName(n).getResource("EDayHqbProcessManagerImpl.class").getPath();
resourceAsStream = Class.forName(n).getResourceAsStream(className + ".class");
cr = new ClassReader(resourceAsStream);
// cr = new ClassReader(ClassLoader.getSystemResourceAsStream(n + ".class"));
} catch (IOException e) {
//e.printStackTrace();
// Exceptions.uncheck(e);
} catch (ClassNotFoundException e) {
//e.printStackTrace();
} finally {
if (resourceAsStream != null) {
try {
resourceAsStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
assert cr != null;
cr.accept(new ClassVisitor(Opcodes.ASM4, cw) {
@Override
public MethodVisitor visitMethod(final int access,
final String name, final String desc,
final String signature, final String[] exceptions) {
final Type[] args = Type.getArgumentTypes(desc);
// 方法名相同並且參數個數相同
if (!name.equals(m.getName())
|| !sameType(args, m.getParameterTypes())) {
return super.visitMethod(access, name, desc, signature,
exceptions);
}
MethodVisitor v = cv.visitMethod(access, name, desc, signature,
exceptions);
return new MethodVisitor(Opcodes.ASM4, v) {
@Override
public void visitLocalVariable(String name, String desc,
String signature, Label start, Label end, int index) {
int i = index - 1;
// 如果是靜態方法,則第一就是參數
// 如果不是靜態方法,則第一個是"this",然後纔是方法的參數
if (Modifier.isStatic(m.getModifiers())) {
i = index;
}
if (i >= 0 && i < paramNames.length) {
paramNames[i] = name;
}
super.visitLocalVariable(name, desc, signature, start,
end, index);
}
};
}
}, 0);
return paramNames;
}
/**
* <p>比較參數類型是否一致</p>
*
* @param types asm的類型({@link Type})
* @param clazzes java 類型({@link Class})
* @return
*/
private static boolean sameType(Type[] types, Class<?>[] clazzes) {
// 個數不同
if (types.length != clazzes.length) {
return false;
}
for (int i = 0; i < types.length; i++) {
if (!Type.getType(clazzes[i]).equals(types[i])) {
return false;
}
}
return true;
}
/**
* 取得切面調用的方法
* @param pjp
* @return
*/
public static Method getMethod(ProceedingJoinPoint pjp){
Signature sig = pjp.getSignature();
MethodSignature msig = null;
if (!(sig instanceof MethodSignature)) {
throw new IllegalArgumentException("該註解只能用於方法");
}
msig = (MethodSignature) sig;
Object target = pjp.getTarget();
Method currentMethod = null;
try {
currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
} catch (NoSuchMethodException e) {
} catch (SecurityException e) {
}
return currentMethod;
}
public static List<String> getMatcher(String regex, String source) {
List<String> list = new ArrayList<String>();
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(source);
while (matcher.find()) {
list.add(matcher.group());
}
return list;
}
/**
* 取得註解參數
(?=exp) 匹配exp前面的位置
(?<=exp) 匹配exp後面的位置
(?!exp) 匹配後面跟的不是exp的位置
(?<!exp) 匹配前面不是exp的位置
* @param managers
* @return
*/
public static List<String> getAnnoParams(String source){
String regex = "(?<=\\{)(.+?)(?=\\})";
return getMatcher(regex, source);
}
}
package com.lh.common.dict;
/**
* 失效時間枚舉類
* @author lh
* @version 3.0
* @since 2016-8-25
*
*/
public enum ExpireTime {
/**
* 無固定期限
*/
NONE(0, "無固定期限")
/**
* 1秒鐘
*/
,ONE_SEC(1, "1秒鐘")
/**
* 5秒鐘
*/
,FIVE_SEC(5, "5秒鐘")
/**
* 10秒鐘
*/
,TEN_SEC(10, "10秒鐘")
/**
* 30秒鐘
*/
,HALF_A_MIN(30, "30秒鐘")
/**
* 1分鐘
*/
,ONE_MIN(60, "1分鐘")
/**
* 5分鐘
*/
,FIVE_MIN(5 * 60, "5分鐘")
/**
* 10分鐘
*/
,TEN_MIN(10 * 60, "10分鐘")
/**
* 20分鐘
*/
,TWENTY_MIN(20 * 60, "20分鐘")
/**
* 30分鐘
*/
,HALF_AN_HOUR(30 * 60, "30分鐘")
/**
* 1小時
*/
,ONE_HOUR(60 * 60, "1小時")
/**
* 1天
*/
,ONE_DAY(24 * 60 * 60, "1天")
/**
* 1個月
*/
,ONE_MON(30 * 24 * 60 * 60, "1個月")
/**
* 1年
*/
,ONE_YEAR(365 * 24 * 60 * 60, "1年")
;
/**
* 時間
*/
private final int time;
/**
* 描述
*/
private final String desc;
ExpireTime(int time, String desc) {
this.time = time;
this.desc = desc;
}
/**
* 獲取具體時間
* @return
*/
public int getTime() {
return time;
}
/**
* 獲取時間描述信息
* @return
*/
public String getDesc() {
return desc;
}
/**
* 根據時間匹配失效期限
* @param time
* @return
*/
public static ExpireTime match(int time){
if(NONE.getTime() == time){
return NONE;
}else if(ONE_SEC.getTime() == time){
return ONE_SEC;
}else if(FIVE_SEC.getTime() == time){
return FIVE_SEC;
}else if(TEN_SEC.getTime() == time){
return TEN_SEC;
}else if(HALF_A_MIN.getTime() == time){
return HALF_A_MIN;
}else if(ONE_MIN.getTime() == time){
return ONE_MIN;
}else if(FIVE_MIN.getTime() == time){
return FIVE_MIN;
}else if(TEN_MIN.getTime() == time){
return TEN_MIN;
}else if(TWENTY_MIN.getTime() == time){
return TWENTY_MIN;
}else if(HALF_AN_HOUR.getTime() == time){
return HALF_AN_HOUR;
}else if(ONE_HOUR.getTime() == time){
return ONE_HOUR;
}else if(ONE_DAY.getTime() == time){
return ONE_DAY;
}else if(ONE_MON.getTime() == time){
return ONE_MON;
}else if(ONE_YEAR.getTime() == time){
return ONE_YEAR;
}
return HALF_AN_HOUR;
}
}
配置中的RdRedisCache.java 代碼如下:
package com.lh.common.jedis;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import net.sf.ehcache.Element;
import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
public class RdRedisCache implements Cache {
private RedisTemplate<String, Object> redisTemplate;
private String name;
public RedisTemplate<String, Object> getRedisTemplate() {
return redisTemplate;
}
public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void setName(String name) {
this.name = name;
}
@Override
public String getName() {
return this.name;
}
@Override
public Object getNativeCache() {
return this.redisTemplate;
}
@Override
public ValueWrapper get(Object key) {
final String keyf = obj2Str(key);
Object object = null;
object = redisTemplate.execute(new RedisCallback<Object>() {
public Object doInRedis(RedisConnection connection)
throws DataAccessException {
byte[] key = keyf.getBytes();
byte[] value = connection.get(key);
if (value == null) {
return null;
}
return toObject(value);
}
});
return (object != null ? new SimpleValueWrapper(object) : null);
}
@Override
public void put(Object key, Object value) {
final String keyf = obj2Str(key);
final Object valuef = value;
final long liveTime = 86400;
redisTemplate.execute(new RedisCallback<Long>() {
public Long doInRedis(RedisConnection connection)
throws DataAccessException {
byte[] keyb = keyf.getBytes();
byte[] valueb = toByteArray(valuef);
connection.set(keyb, valueb);
if (liveTime > 0) {
connection.expire(keyb, liveTime);
}
return 1L;
}
});
}
public String obj2Str(Object key){
String keyStr = null;
if(key instanceof Integer){
keyStr = ((Integer)key).toString();
}else if(key instanceof Long){
keyStr = ((Long)key).toString();
}else {
keyStr = (String)key;
}
return keyStr;
}
/**
* 描述 : <Object轉byte[]>. <br>
* <p>
* <使用方法說明>
* </p>
*
* @param obj
* @return
*/
private byte[] toByteArray(Object obj) {
byte[] bytes = null;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
oos.flush();
bytes = bos.toByteArray();
oos.close();
bos.close();
} catch (IOException ex) {
ex.printStackTrace();
}
return bytes;
}
/**
* 描述 : <byte[]轉Object>. <br>
* <p>
* <使用方法說明>
* </p>
*
* @param bytes
* @return
*/
private Object toObject(byte[] bytes) {
Object obj = null;
try {
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
obj = ois.readObject();
ois.close();
bis.close();
} catch (IOException ex) {
ex.printStackTrace();
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
}
return obj;
}
@Override
public void evict(Object key) {
final String keyf = obj2Str(key);
redisTemplate.execute(new RedisCallback<Long>() {
public Long doInRedis(RedisConnection connection)
throws DataAccessException {
return connection.del(keyf.getBytes());
}
});
}
@Override
public void clear() {
redisTemplate.execute(new RedisCallback<String>() {
public String doInRedis(RedisConnection connection)
throws DataAccessException {
connection.flushDb();
return "ok";
}
});
}
@Override
public <T> T get(Object key, Class<T> type) {
ValueWrapper wrapper = get(key);
return wrapper == null ? null : (T) wrapper.get();
}
@Override
public ValueWrapper putIfAbsent(Object key, Object value) {
synchronized (key) {
ValueWrapper wrapper = get(key);
if (wrapper != null) {
return wrapper;
}
put(key, value);
return toWrapper(new Element(key, value));
}
}
private ValueWrapper toWrapper(Element element) {
return (element != null ? new SimpleValueWrapper(element.getObjectValue()) : null);
}
}
spring配置文件的相關配置如下:
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> <property name="maxIdle" value="${redis.pool.maxIdle}" /> <!-- 最大能夠保持idel狀態的對象數 --> <property name="maxTotal" value="${redis.pool.maxTotal}" /> <!-- 最大分配的對象數 --> <property name="testOnBorrow" value="${redis.pool.testOnBorrow}" /> <!-- 當調用borrow Object方法時,是否進行有效性檢查 --> </bean> <!-- jedisPool init --> <bean id="jedisPool" class="redis.clients.jedis.JedisPool"> <constructor-arg index="0" ref="jedisPoolConfig" /> <constructor-arg index="1" value="${redis.host}" type="String" /> <constructor-arg index="2" value="${redis.port}" type="int" /> </bean> <!-- jedis單機配置 --> <bean id="jedisConnFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" > <property name="hostName" value="${redis.host}" /> <property name="port" value="${redis.port1}" /> <property name="timeout" value="${redis.timeout}" /> <property name="poolConfig" ref="jedisPoolConfig" /> </bean> <bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer" /> <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" p:connectionFactory-ref="jedisConnFactory" p:keySerializer-ref="stringRedisSerializer" /> <!-- spring自己的緩存管理器 --> <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager"> <property name="caches"> <set> <bean class="com.lh.common.jedis.RdRedisCache" p:redis-template-ref="redisTemplate" p:name="sysCache"/> </set> </property> </bean> <!-- 啓用緩存註解功能,這個是必須的,否則註解不會生效,另外,該註解一定要聲明在spring主配置文件中才會生效 --> <cache:annotation-driven cache-manager="cacheManager" proxy-target-class="true" />
The end!