一、概述
在学习Spring AOP+注解的方式实现缓存的获取,你需要掌握几个知识点
1、为什么要用AOP+注解的方式实现缓存的获取
2、你需要了解Spring框架的搭建
3、你需要了解Spring如何做到获取方法参数的参数名
4、你需要了解反射的概念
5、你需要了解注解的基本知识
以上所列的2、4、5条都可以很快了解,但是对第3条,你需要好好学习下了,我就选择1、3来说下我的理解,如果有错误,请谅解,毕竟我水平不高。
1、为什么要用AOP+注解的方式实现缓存的获取?
首先使用AOP,我理解就是为了解耦合,如果我们在代码实现中,业务逻辑代码和缓存的操作掺杂在一起,这样会产生耦合,并且你会发现大量的缓存操作代码冗余,
而且灵活性也不高(如果我换了一个缓存框架,那么每个涉及到的缓存代码都需要修改)
使用注解是因为比较方便。
所以选择使用AOP+注解的方式实现缓存的获取
2、Spring如何做到获取方法参数的参数名
请参考我的博客http://blog.csdn.net/zx2612/article/details/74090658
二、缘由
为什么要写这篇文章?
最近在工作中,同事实现了这个功能,所以为了学习,我也选择回家写一次,看的再多,不如做一次。
三、实现
实现的基本原理:利用Spring AOP(其实是使用AspectJ,将方法上的注解作为切点,将通知织入),然后定义了一个@CacheParam注解,用来动态获取缓存的key,和动态控制是否清除缓存,使用@CacheManage来标识那个方法应该被拦截。对于注解的处理有点像SpringMVC的@RequestMapping和@PathVariable(当然我水平有限,如果存在错误和不足,请指教)
代码如下:
1、定义注解
/**
* 指定某个方法参数的名称
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheParam {
String value() default "";
}
/**
* 标识在方法上面,用于动态操作缓存
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheManage {
/**
* 缓存的key
* */
String key();
/**
* 缓存组件的配置文件
* 可能需要从多个缓存组件中获取数据
*/
String configFile() default "cache.conf";
boolean isClearCache() default false;
String clearCacheFlag() default "";
}
2、定义基本的缓存操作接口
/**
* 使用注解方式操作缓存需要自己实现该接口的方法
* 因为缓存组件有很多种,操作的Api也不一样
*/
public interface CacheOperate {
public Object get(String key,String configFile);
public Object update(String key,Object value,String configFile);
public void del(String key,String configFile);
public boolean existCache(String key,String configFile);
}
3、实现工具类
/**
* 缓存反射工具类
*/
public class CacheUtil {
private static final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
/**
* 从method的参数列表中检测指定的CacheParam注解的value属性值,如果为空则使用Spring封装好的ParameterNameDiscoverer,去读取参数的名称
* <p>
* 对于参数名称的获取,如果编译成Class文件时带上了参数名称信息,则可以正常获取,否则只有类似arg0,arg1这样的信息
* <p>
* Spring获取参数名称至少有如下几种方式:
* 1、通过jdk8提供的getParameters方法
* 2、通过ASM直接解析Class文件,从局部变量表中读取
*
* @param method
* @param type
* @param startIndex
* @return
*/
public static String getParameterName(Method method, Class type, int startIndex) {
String paramName = null;
Annotation[][] annotationArrays = method.getParameterAnnotations();
for (int index = startIndex; index < annotationArrays.length; index++) {
Annotation[] annotations = annotationArrays[index];
for (Annotation a : annotations) {
if (a.getClass().isAssignableFrom(type)) {
CacheParam cacheParam = CacheParam.class.cast(a);
paramName = cacheParam.value();
if (paramName == null || "".equals(paramName)) {
paramName = parameterNameDiscoverer.getParameterNames(method)[index];
}
break;
}
}
}
return paramName;
}
public static String getParameterName(Method method, Class type) {
return getParameterName(method, type, 0);
}
public static Map<String, Object> getAllParamters(Method method, Class type, Object[] args) {
Map<String, Object> paramMap = new HashMap<String, Object>();
Annotation[][] annotationArrays = method.getParameterAnnotations();
for (int index = 0; index < annotationArrays.length; index++) {
Annotation[] annotations = annotationArrays[index];
for (Annotation a : annotations) {
if (type.isAssignableFrom(a.getClass())) {
CacheParam cacheParam = CacheParam.class.cast(a);
String paramName = cacheParam.value();
if (paramMap.containsKey(paramName)) {
throw new IllegalStateException("method:" + method.getName() + "参数列表中的@CacheParam定义的属性值重复");
}
if (paramName == null || "".equals(paramName)) {
paramName = parameterNameDiscoverer.getParameterNames(method)[index];
}
paramMap.put(paramName, args[index]);
break;
}
}
}
return paramMap;
}
public static <T> T getTargetBean(ApplicationContext ac, Class<T> type) {
return ac.getBean(type);
}
public static boolean isPlaceholder(String value) {
value = StringUtils.trimAllWhitespace(value);
return value != null && value.startsWith("{") && value.endsWith("}");
}
public static String getCacheKey(String key) {
return key.replaceAll("[{}]", "");
}
public static <T> T checkAndCastKeyValue(String key, Class<T> targetType, Map<String, Object> paramValues, boolean isRequired) {
String keyTemp = key;
if (CacheUtil.isPlaceholder(keyTemp)) {
keyTemp = CacheUtil.getCacheKey(keyTemp);
}
Object keyObj = paramValues.get(keyTemp);
if (keyObj == null) {
if (isRequired) {
throw new IllegalStateException("无@CacheParam(" + key + ")对应的参数");
} else {
return null;
}
}
if (!keyObj.getClass().isAssignableFrom(targetType)) {
throw new IllegalStateException("方法中@CacheParam(" + key + ")对应的参数值必须为"+targetType+"类型");
}
return targetType.cast(keyObj);
}
}
4、定义切面
/**
* 使用注解的方式获取缓存
* 可以动态指定某个缓存的key
* 可以动态指定是否要清空某个缓存
* <p>
* 动态指定的实现方式类似于SpringMVC中的RequestMapping和PathVariable
*/
@Component
@Aspect
public class CacheAspact {
@Resource
private ApplicationContext ac;
@Around("@annotation(xyz.zhangxing.spring.extend.CacheManage)")
public Object aspact(ProceedingJoinPoint joinPoint) throws Throwable {
Signature s = joinPoint.getSignature();
MethodSignature ms = (MethodSignature) s;
Method method = ms.getMethod();
Map<String, Object> paramValues = CacheUtil.getAllParamters(method, CacheParam.class, joinPoint.getArgs());
CacheManage cacheManage = method.getAnnotation(CacheManage.class);
CacheOperate cacheOperate = CacheUtil.getTargetBean(ac, CacheOperate.class);
String key = CacheUtil.checkAndCastKeyValue(cacheManage.key(), String.class, paramValues, true);
String configFile = cacheManage.configFile();
Boolean clearCacheFlag = CacheUtil.checkAndCastKeyValue(cacheManage.clearCacheFlag(), Boolean.class, paramValues, false);
if (clearCacheFlag == null) {
clearCacheFlag = cacheManage.isClearCache();
}
Object result = null;
//如果需要清除缓存,或者缓存不存在
if (clearCacheFlag||!cacheOperate.existCache(key,configFile)) {
result= joinPoint.proceed();
cacheOperate.update(key,result,configFile);
}else{
result= cacheOperate.get(key,configFile);
}
return result;
}
}
5、测试
@Component
public class TestCacheOperate implements CacheOperate {
private Map<String, Object> map = new ConcurrentHashMap<String, Object>();
public Object get(String key, String configFile) {
return map.get(key);
}
public Object update(String key, Object value, String configFile) {
if (value == null) {
return null;
}
return map.put(key, value);
}
public void del(String key, String configFile) {
map.remove(key);
}
public boolean existCache(String key, String configFile) {
return map.containsKey(key) ? get(key, configFile) != null : false;
}
}
@Component("test")
public class TestAspact {
@CacheManage(key = "{name}",clearCacheFlag = "{clearFlag}")
public String test(String value,@CacheParam("name") String name,@CacheParam("clearFlag") boolean clearFlag) {
return value;
}
}
public class Test {
public static void main(String[] args) {
ApplicationContext ac=new ClassPathXmlApplicationContext("classpath:spring.conf.xml");
TestAspact ta= ac.getBean("test",TestAspact.class);
System.out.println(ta.test("第一次的值","key",false));
System.out.println(ta.test("第二次的值","key",false));
System.out.println(ta.test("第三次的值","key",true));
System.out.println(ta.test("第四次的值","key",false));
}
}
结果如下:
第一次的值
第一次的值
第三次的值
第三次的值