最近做的项目一直在使用memcached作为缓存来缓存各种数据,现在BOSS要在项目上加上缓存。并把任务交给我。便琢磨怎么解决这个问题。
看了很多文章,写的比较详尽靠谱的就是这篇了http://www.cnblogs.com/cczhoufeng/archive/2013/04/09/3009578.html,并在此基础之上结合自身项目做出了一些改动,在此分享出来。
该套框架的基本思路是:
利用Spring-AOP在项目的DAOImpl层做一个环绕切面
在方法上添加自定义注解实现细粒度的控制(然而需要修改其他DAO的实现类方法,并不是特别好的解决思路,希望有人能够想出更好的方法)
在环绕切面上使用CacheUtils中定义的对缓存中间件的操作方法
CacheUtils的主要功能就是在去访问数据库前先去缓存中查看是否有值,有则直接返回,没有数据则去数据库中查,然后在放入缓存中。而对于删除,修改,增加方法,则需要将缓存中的数据清空。
CacheUtils在通过反射拿到该次访问方法的具体信息,首先以该方法的包类路径+方法名字+参数的json 转哈希 获取到一个版本号KEY,由此去缓存中查询,如果没有则初始化版本号为1
然后在以包类路径+方法名字+参数的json+版本号字符串转哈希 获取到一个结果集KEY,再由此去缓存中查询结果,如果没有结果,则说明缓存中没有命中,需要去访问DB,然后将结果集合放入缓存。
配置文件切面 spring-datasource.xml
配置切面和缓存工具类的注册
<bean id="cacheUtils" class="com.demo.util.CacheUtils" /> <aop:config proxy-target-class="true"> <aop:aspect id="aspect" ref="cacheUtils"> <aop:pointcut id="cacheMgr" expression="execution(* com.demo.system.dao.*.*(..))"/> <aop:around method="doAround" pointcut-ref="cacheMgr"/> </aop:aspect> </aop:config>
添加两个自定义注解@Cache @Flush
package com.demo.util; import java.lang.annotation.*; /** * @Author by pikzas. * @Date 2016-11-10 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface Cache { int expireTime() default 60; }
package com.demo.util; import java.lang.annotation.*; /** * @Author by pikzas. * @Date 2016-11-10 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface Flush { }
工具类CacheUtils的实现
package com.demo.util; import com.demo.framework.page.ReflectUtil; import net.spy.memcached.MemcachedClient; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.aop.framework.AdvisedSupport; import org.springframework.beans.factory.annotation.Autowired; import java.lang.reflect.Method; /** * @Author by pikzas. * @Date 2016-11-07 */ //@Aspect //@Component public class CacheUtils { Log log = LogFactory.getLog(CacheUtils.class); @Autowired private MemcachedClient memcachedClient; public Object doAround(ProceedingJoinPoint call) { //返回最终结果 Object result = null; //定义版本号,默认为1 String version = "1"; String targetClassName = null; String versionKey = null; try { targetClassName=getCglibProxyTargetObject(call.getThis()).getClass().getName(); versionKey = targetClassName.hashCode()+""; log.debug(targetClassName); } catch (Exception e) { log.debug("获取AOP代理类的目标实现类异常!"); e.printStackTrace(); } Signature signature = call.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); String methodName = method.getName(); if(method.isAnnotationPresent(Cache.class)){ //实现类上有CACHE注解 说明要进入缓存 Cache cache = method.getAnnotation(Cache.class); if(cache!=null){ //获取注解中缓存过期时间 int expireTime = cache.expireTime(); //获取版本号 if(null != memcachedClient.get(versionKey)){ version = memcachedClient.get(versionKey).toString(); } //获取缓存key 包名+"."+方法名+参数json字符串+版本号 String cacheKey = targetClassName+"."+methodName+ JsonUtils.objectToJsonString(call.getArgs())+version; //获取方法名+参数key-value 的json +版本号 转 MD5 String key = cacheKey.hashCode()+""; //存入memcached的最终key值 result =memcachedClient.get(key); if(null == result){ //缓存中没有数据 try { result = call.proceed(); //放行 获取结果 if(version.equals("1")){ //第一个版本 应该将版本信息也放入缓存中 memcachedClient.set(targetClassName.hashCode()+"",expireTime,version); } if(null!=result){ memcachedClient.set(key,expireTime, result); } } catch (Throwable e) { e.printStackTrace(); } } else { //缓存中有数据 log.debug("***************************"+targetClassName+"."+methodName+" Get Data From Cache......"+"***************************"); return result; } } }else if(method.isAnnotationPresent(Flush.class)){ //实现类上有Flush注解 说明要更新缓存 //如果修改操作时 Flush flush = method.getAnnotation(Flush.class); if(flush!=null){ try { result = call.proceed(); }catch (Throwable e){ e.printStackTrace(); } //获取当前版本号 if(null != memcachedClient.get(versionKey)){ version = memcachedClient.get(versionKey).toString(); } //修改后,版本号+1 此处设定vkey缓存过期时间 memcachedClient.replace(versionKey,300, Integer.parseInt(version.toString()) + 1);//此处默认时间300秒 } }else{ //没有注解 什么都不做 try { result = call.proceed(); } catch (Throwable e) { e.printStackTrace(); } } return result; } //该方法用户获取当前方法的具体的实现类 private static Object getCglibProxyTargetObject(Object proxy) throws Exception { Object dynamicAdvisedInterceptor = ReflectUtil.getFieldValue(proxy, "CGLIB$CALLBACK_0"); Object target = ((AdvisedSupport) ReflectUtil.getFieldValue(dynamicAdvisedInterceptor, "advised")).getTargetSource().getTarget(); return target; } }
CacheUtils依赖的工具类 ReflectUtils
package com.demo.page; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; public class ReflectUtil { public static Object getFieldValue(Object object, String fieldName) { Field field = getDeclaredField(object, fieldName); if (field == null) throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + object + "]"); makeAccessible(field); Object result = null; try { result = field.get(object); } catch (IllegalAccessException e) { e.printStackTrace(); } return result; } }
CacheUtils依赖的工具类 JSONUtils
package com.demo.util; import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.databind.MappingJsonFactory; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.HashMap; import java.util.Map; import java.util.TimeZone; /** * json转化工具类 * * @author Administrator */ public class JsonUtils { private static Logger logger = LoggerFactory.getLogger(JsonUtils.class); /** * Object字符串转化为 json * * @return String json */ public static String objectToJsonString(Object obj) { return objectToJsonString(obj,""); } /** * Object字符串转化为 json * * @return String json */ public static String objectToJsonString(Object obj, String formatStr) { ObjectMapper objectMapper = null; String resultJson = null; try { objectMapper = new ObjectMapper(); objectMapper.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai")) ; if (!Function.isEmpty(formatStr)) { objectMapper.setDateFormat(new SimpleDateFormat(formatStr)); } else { objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd")); } resultJson = objectMapper.writeValueAsString(obj); } catch (JsonProcessingException e) { logger.error("Object to Json error", e); } return resultJson; } /** * 将string json 转化为Object * * @return Class obj */ public static <T> T getBeanFromJsonString(String json, Class<T> cls) { ObjectMapper objectMapper = null; T obj = null; try { objectMapper = new ObjectMapper(); obj = (T) objectMapper.readValue(json, cls); } catch (Exception e) { e.printStackTrace(); return null; } return obj; } /** * 解析json字符串方法 * * @param jsonText json字符串 * @param key * @return */ public static String parseJson(String jsonText, String key) { JsonFactory jsonFactory = new MappingJsonFactory(); JsonParser jsonParser = null;// Json解析器 HashMap<String, String> map = null; try { jsonParser = jsonFactory.createJsonParser(jsonText); jsonParser.nextToken();// 跳到结果集的开始 map = new HashMap<String, String>();// 结果集HashMap while (jsonParser.nextToken() != JsonToken.END_OBJECT) { jsonParser.nextToken(); // 跳转到Value map.put(jsonParser.getCurrentName(), jsonParser.getText()); // 将Json中的值装入Map中 } } catch (JsonParseException e) { logger.error("--json parser error--", e); } catch (IOException e) { logger.error("--json parser error--", e); } return map.get(key) == null ? null : map.get(key); } /** * 解析json字符串方法 * * @param jsonText json字符串 * @return */ public static Map<String, Object> parseJson(String jsonText) { if (jsonText == null || jsonText.equals("")) { return null; } JsonFactory jsonFactory = new MappingJsonFactory(); JsonParser jsonParser = null;// Json解析器 HashMap<String, Object> map = null; try { jsonParser = jsonFactory.createJsonParser(jsonText); jsonParser.nextToken();// 跳到结果集的开始 map = new HashMap<String, Object>();// 结果集HashMap while (jsonParser.nextToken() != JsonToken.END_OBJECT) { jsonParser.nextToken(); // 跳转到Value map.put(jsonParser.getCurrentName(), jsonParser.getText()); // 将Json中的值装入Map中 } } catch (Exception e) { } return map == null ? null : map; } /** * 返回 json串"{"resultCode":"resultCode","retultMsg":"resultMsg"}"; * * @return */ public static String getJsonResult(String resultCode, Object obj) { StringBuilder sb = new StringBuilder(); sb.append("{"resultCode":""); sb.append(resultCode); sb.append("","retultMsg":"); sb.append(objectToJsonString(obj)); sb.append("}"); return sb.toString(); } public static void main(String[] args) { // String answerJson = "{"id":10,"questionId":10,"userId":10,"answer":"ssss","isRight":1,"createTime":"2013-12-17"}"; // // Answer answer = getBeanFromJsonString(answerJson, Answer.class); // // String objectToJsonString = objectToJsonString(answer); // String parseJson = parseJson(answerJson, "questionId"); // System.out.println(Math.random()); } }
最后就是在你的DaoImpl具体的方法上添加@Cache或者是@Flush方法了。 然后跑起你的代码吧!
注意:在有些情况下,会怎么都不进入Spring的切面中,此时可能的原因容器冲突的原因:SpringMVC的容器依赖于Spring的容器,在SpringMVC的配置文件中最好将DAO层的注解@Repository排除(同理如果SpringAOP的切面配置在Service层,则将@Service注解排除掉),还有就是在实际应用中可能会出现一个问题就是,继承自父类BaseDaoImpl的XxxDaoImpl