每一次实践得出结论,得出的对过往理论的印证,都是一次悟道,其收益远大于争论和抱怨。
技术是一件比较客观的事,正确与错误,其实就摆在哪里,意见不统一,写段代码试验一下就好了,一段代码印证不了的时候,就多写几段。
挺简单的一个案例,通过SpringAOP和注解,使用Guava缓存。代码如下:
GuavaCache.java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GuavaCache {
/**
* group : 一个group代表一个cache,不传或者""则使用 class+method 作为cache名字
* @return
*/
public String group() default "";
/**
* key : 注意,所有参数必须实现GuavaCacheInterface接口,如果不实现,则会用toString()的MD5作为Key
* @return
*/
public String key() default "";
/**
* 过期时间,缺省30秒
* @return
*/
public long timeout() default 30;
/**
* 缓存最大条目,缺省10000
* @return
*/
public long size() default 10000;
/**
* 是否打印日志
* @return
*/
public boolean debug() default false;
}
GuavaInterface.java
/**
* 使用GuavaCache注解时,如果传入参数是对象,则必须实现这个类
*
*/
public interface GuavaCacheInterface {
public String getCacheKey();
}
GuavaCacheProcessor.java
import java.lang.reflect.Method;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.concurrent.TimeUnit;
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;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Maps;
/**
* GuavaCache注解处理器
*
*/
@Component
@Aspect
public class GuavaCacheProcessor {
private static final Logger logger = LoggerFactory.getLogger(GuavaCacheProcessor.class);
private static Map> cacheMap = Maps.newConcurrentMap();
@Around("execution(* *(..)) && @annotation(guavaCache)")
public Object aroundMethod(ProceedingJoinPoint pjd, GuavaCache guavaCache) throws Throwable {
Cache cache = getCache(pjd, guavaCache);
String key = getKey(pjd);
boolean keyisnull = (null == key) || ("".equals(key));
if(guavaCache.debug()) {
logger.info("GuavaCache key : {} begin", key);
}
Object result = null;
if(!keyisnull) {
result = cache.getIfPresent(key);
if(result != null) {
return result;
}
}
try {
result = pjd.proceed();
if(!keyisnull) {
cache.put(key, result);
}
} catch (Exception e) {
throw e;
}
if(guavaCache.debug()) {
logger.info("GuavaCache key : {} end", key);
}
return result;
}
/**
* 获取Cache
* @param pjd
* @param guavaCache
* @return
*/
private Cache getCache(ProceedingJoinPoint pjd, GuavaCache guavaCache) {
String group = guavaCache.group();
if(group == null || "".equals(group)) {
MethodSignature signature = (MethodSignature) pjd.getSignature();
Method method = signature.getMethod();
Class> clazz = method.getDeclaringClass();
group = clazz.getName();
}
Cache cache = cacheMap.get(group);
if(cache == null) {
cache = CacheBuilder.newBuilder()
.maximumSize(guavaCache.size())
.expireAfterWrite(guavaCache.timeout(), TimeUnit.SECONDS)
.build();
cacheMap.put(group, cache);
}
return cache;
}
/**
* 获取Key:方法名+getCacheKey方法(如果没有,则用toString())的MD5值
* @param pjd
* @return
*/
private String getKey(ProceedingJoinPoint pjd) {
StringBuilder sb = new StringBuilder();
MethodSignature signature = (MethodSignature) pjd.getSignature();
Method method = signature.getMethod();
sb.append(method.getName());
for(Object param : pjd.getArgs()) {
if(GuavaCacheInterface.class.isAssignableFrom(param.getClass())) {
sb.append(((GuavaCacheInterface)param).getCacheKey());
} else {
if(!param.getClass().isPrimitive()) {
return null;
}
sb.append(param.toString());
}
}
String key = md5(sb.toString());
return key;
}
/**
* 进行MD5加密
*
* @param info
* 要加密的信息
* @return String 加密后的字符串
*/
private static String md5(String info) {
byte[] digesta = null;
try {
// 得到一个md5的消息摘要
MessageDigest alga = MessageDigest.getInstance("MD5");
// 添加要进行计算摘要的信息
alga.update(info.getBytes());
// 得到该摘要
digesta = alga.digest();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
// 将摘要转为字符串
String rs = byte2hex(digesta);
return rs;
}
/**
* 将二进制转化为16进制字符串
*
* @param b
* 二进制字节数组
* @return String
*/
private static String byte2hex(byte[] b) {
String hs = "";
String stmp = "";
for (int n = 0; n < b.length; n++) {
stmp = (java.lang.Integer.toHexString(b[n] & 0XFF));
if (stmp.length() == 1) {
hs = hs + "0" + stmp;
} else {
hs = hs + stmp;
}
}
return hs.toUpperCase();
}
}
当bean在applicationContext.xml中通过
把对于AOP的定义
监测Service类和注解处理类(通过在构造函数增加System.out),发现类被加载两次,疑惑了一下,加载两次?
看applicationContext.xml,配置正常;看servlet-context.xml,居然扫描的不仅仅是controller类,而是把所有的类都扫描了一遍,这就解释了,为什么
因为根据web.xml的加载顺序:context-param>listener>filter>servlet,servlet-context.xml是最后加载的,spring又扫描了一遍,如果不写
这个配置,配置在哪个文件中,就会对哪个文件scan的类生效。
看似诡异的问题,更大的可能是我们了解的不够透彻,不够深入;如果规范一点,诡异的问题往往都不出现。
Spring AOP,在同一个类中,嵌套注解时,只对最外层的注解生效,这个有很多文章,解释的很清楚了,可以参考:
http://blog.csdn.net/ldwtill/article/details/22061421
https://www.oschina.net/question/504281_235673
当前SpringAOP的实现,在同一个类中,是不支持嵌套注解的,说不定在以后会实现。
对于嵌套注解,如果一定需要,可以通过aspectJ通过LTW解决,这个我试验过,确实可以解决,但是成本较高,首先jvm需要增加-javaagent,然后项目中的单元测试之类,都要加这个参数,所以如果不是一定需要,就不要这样做了,参考文章:
http://sexycoding.iteye.com/blog/1062372
http://blog.csdn.net/scorpio3k/article/details/6745443
http://blog.csdn.net/qyongkang/article/details/7799603
对于嵌套注解,还有一种解决办法,也是通过aspectJ,在编译期解决,这个连编译器都换了,成本太高,没有去实践。
self字段解决办法:http://strongant.iteye.com/blog/2146152
备注:
以上,一次知识总结,没有放过,没有看似解决了,就这样吧,找出了根本问题,很好。