SpringMVC Memcached 搭建WEB项目缓存框架

  最近做的项目一直在使用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>
View Code

  添加两个自定义注解@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;
}
View Code
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 {
}
View Code

  工具类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;
    }


}
View Code

    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;
     }
}
View Code

  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());

    }

}
View Code

  最后就是在你的DaoImpl具体的方法上添加@Cache或者是@Flush方法了。 然后跑起你的代码吧!

注意:在有些情况下,会怎么都不进入Spring的切面中,此时可能的原因容器冲突的原因:SpringMVC的容器依赖于Spring的容器,在SpringMVC的配置文件中最好将DAO层的注解@Repository排除(同理如果SpringAOP的切面配置在Service层,则将@Service注解排除掉),还有就是在实际应用中可能会出现一个问题就是,继承自父类BaseDaoImpl的XxxDaoImpl

 

转载于:https://www.cnblogs.com/Pikzas/p/6048865.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值