zoukankan      html  css  js  c++  java
  • 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

    言之有物
  • 相关阅读:
    MVC4中常用的短句及配置归结(部分)
    结合EF5.0讲MVC4(四)将我们的程序改成数据库优先模式
    结合EF5.0讲MVC4(二)为先前程序添加查询及主外键关系
    【译】《Pro ASP.NET MVC4 4th Edition》第二章(一)
    XtraReport应用(1)(XtraReport From File)
    结合EF5.0讲MVC4(一)创建一个MVC4应用程序
    Scrum实际应用(一)
    结合EF5.0讲MVC4(三)为我们的程序添加过滤器
    C# LINQ详解(一)
    如何在 Windows Server 2008 上打开 SQL Server 防火墙端口
  • 原文地址:https://www.cnblogs.com/Pikzas/p/6048865.html
Copyright © 2011-2022 走看看