使用方法拦截机制在不修改原逻辑基础上为 spring MVC 工程添加 Redis 缓存


首先,相关文件:链接: https://pan.baidu.com/s/1H-D2M4RfXWnKzNLmsbqiQQ 密码: 5dzk

文件说明:

  redis-2.4.5-win32-win64.zip --windows程序包,无需安装,直接执行redis-server.exe 启动服务,执行redis-cli.exe启动客户端

  redis-3.2.9.tar.gz --linux程序包,需要make安装一下,然后执行src/redis-server 注意启动时指定配置文件为redis.conf

  RedisUtil.java --redis工具类
  spring-redis-caching-example-master --spring-redis示例

一、redis 安装

  1、windows 安装与运行

    直接使用下载的程序包,解压后获得文件夹(假定为redis_home),进入redis_home/32bit(或者redis_home/64bit)目录,运行redis-server.exe即可运行redis服务。通过查看运行窗口的日志。

    运行时指定配置和日志输出文件:

      cmd窗口运行redis-server.exe  ./redis.conf > ./redis.log

    服务方式运行:

      使用redis-server.exe 运行需要在命令行窗口中,关闭窗口后程序也会跟着关闭,如果想要后台一直默默的运行,可以从官网下载msi版的安装文件,安装后会生成一个redis的服务,启动服务即可

      2、Linux 下安装与运行

     假定安装到 ..../program下:

     将redis-3.2.9.tar.gz安装包cp到program/redis/目录下 ----> 解压 tar -xzf redis-3.2.9.tar.gz --->进入解压后的目录 cd redis-3.2.9  ---->  make(安装命令);

     运行(假定redis根目录为redisHome):

      cd redisHome/src

      nohup redis-server ../redis.conf > ../../../logs/redis-log.txt &    ---- nohup 方式,指定配置文件 , 指定日志文件路径

 3、运行后验证系统是否正常运行

    进入程序目录(和redis-server一个目录),运行redis-client程序验证:

    redis-cli -p 127.0.0.1 -a eas@1234      --    -p:指定redis服务host,-a指定密码(如果有)

    set 'test' 'HelloWord!'   --存入数据

    get 'test'    --取出数据

    flushAll   --清空所有数据

    4、通过ip地址无法连接问题

      redis-cli 客户端验证时,可能出现用ip地址无法连接用localhost可以连接的情况,这是因为没有配置允许通过ip外网访问。

    打开redis.conf配置文件,做如下修改:

      注释 bind 行配置;

      修改密码 (去掉注释 requirepass foobared并修改为requirepass eas@1234);

      修改protected-mode no;

    5、redis desktop Manager软件

   可使用Redis DeskTop Manager可视化软件工具查看redis数据

二、redis缓存在项目中的使用

  1、使用拦截器方式让redis介入原有的程序逻辑

    开发环境:Maven、spring MVC 架构的web工程

    需求: 当前数据库因为多种因素查询效率比较低下,需要使用redis缓存提交效率,但是之前的代码开发已经比较多了不方便大规模修改代码,希望尽可能不修改之前的代码。

    1.1 maven pom配置:


<
dependency> <groupId>org.springframework.datagroupId> <artifactId>spring-data-redisartifactId> <version>1.6.1.RELEASEversion> dependency>
<dependency> <groupId>redis.clientsgroupId> <artifactId>jedisartifactId> <version>2.7.3version> dependency>
  
  org.aspectj  
  aspectjrt  
  1.8.0  
  
  
  org.aspectj  
  aspectjweaver  
  1.8.0  
 

     1.2 spring bean配置



<
bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" > <property name="poolConfig" ref="poolConfig" /> <property name="port" value="6379" /> <property name="hostName" value="10.80.13.10" /> <property name="password" value="eas@1234" /> <property name="timeout" value="100" /> bean >

<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" > <property name="connectionFactory" ref="connectionFactory" /> <property name="keySerializer" > <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" /> property> <property name="valueSerializer" > <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" /> property> bean >

<bean id="redisUtil" class="com.sunline.common.utils.RedisUtil" > <property name="redisTemplate" ref="redisTemplate" /> bean >

   RedisUtil.java代码:

  1 package com.sunline.common.utils;  
  2   
  3 import java.io.Serializable;  
  4 import java.util.Set;  
  5 import java.util.concurrent.TimeUnit;
  6 import org.springframework.data.redis.core.RedisTemplate;  
  7 import org.springframework.data.redis.core.ValueOperations;  
  8   
  9 /** 
 10  * redis cache 工具类 
 11  */  
 12 public final class RedisUtil {  
 13     /*private Logger logger = Logger.getLogger(RedisUtil.class);  */
 14     private RedisTemplate redisTemplate;
 15   
 16     /** 
 17      * 批量删除对应的value 
 18      * @param keys 
 19      */  
 20     public void remove(final String... keys) {  
 21         for (String key : keys) {  
 22             remove(key);  
 23         }  
 24     }  
 25   
 26     /** 
 27      * 批量删除key 
 28      * @param pattern 
 29      */  
 30     public void removePattern(final String pattern) {  
 31         Set keys = redisTemplate.keys(pattern);  
 32         if (keys.size() > 0)  
 33             redisTemplate.delete(keys);  
 34     }  
 35   
 36     /** 
 37      * 删除对应的value 
 38      * @param key 
 39      */  
 40     public void remove(final String key) {  
 41         if (exists(key)) {  
 42             redisTemplate.delete(key);  
 43         }  
 44     }  
 45   
 46     /** 
 47      * 判断缓存中是否有对应的value 
 48      * @param key 
 49      * @return 
 50      */  
 51     public boolean exists(final String key) {  
 52         return redisTemplate.hasKey(key);  
 53     }  
 54   
 55     /** 
 56      * 读取缓存 
 57      * @param key 
 58      * @return 
 59      */  
 60     public Object get(final String key) {  
 61         Object result = null;  
 62         ValueOperations operations = redisTemplate.opsForValue();  
 63         result = operations.get(key);  
 64         return result;  
 65     }  
 66   
 67     /** 
 68      * 写入缓存 
 69      * @param key 
 70      * @param value 
 71      * @return 
 72      */  
 73     public boolean set(final String key, Object value) {  
 74         boolean result = false;  
 75         try {  
 76             ValueOperations operations = redisTemplate.opsForValue();  
 77             operations.set(key, value);  
 78             result = true;  
 79         } catch (Exception e) {  
 80             e.printStackTrace();  
 81         }  
 82         return result;  
 83     }  
 84   
 85     /** 
 86      * 写入缓存 
 87      * @param key 
 88      * @param value 
 89      * @return 
 90      */  
 91     public boolean set(final String key, Object value, Long expireTime) {  
 92         boolean result = false;  
 93         try {  
 94             ValueOperations operations = redisTemplate.opsForValue();  
 95             operations.set(key, value);  
 96             redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);  
 97             result = true;  
 98         } catch (Exception e) {  
 99             e.printStackTrace();  
100         }  
101         return result;  
102     }  
103     public RedisTemplate getRedisTemplate() {  
104         return this.redisTemplate;  
105     }
106     public void setRedisTemplate(RedisTemplate redisTemplate) {  
107         this.redisTemplate = redisTemplate;  
108     }  
109 }  

  以上2步为项目添加了redis相关功能,java代码中可以使用redisUtil的相关方法操作缓存了。

  3、实现不修改原有代码的前提下给现有的项目添加缓存逻辑

    其实单纯的spring MVC工程要实现缓存可以使用Spring 的缓存注解相关逻辑既可,简单方便。

    但是现在要求不修改原有代码的情况下给现有项目添加缓存机制。考虑使用spring的方法拦截机制,对相关已有的访问数据库的方法进行拦截,拦截后判断,对符合要求的方法进行缓存,调用方法时先判断缓存是否存在,如存在直接取缓存,如不存在调用原方法逻辑并把结果存入缓存。

    spring 添加配置:


<aop:aspectj-autoproxy proxy-target-class="true"/>

<bean id="methodCacheInterceptor" class="com.sunline.common.utils.MethodCacheInterceptor" >  
         <property name="redisUtil" ref="redisUtil" />  
bean >
  
<bean id="methodCachePointCut"  class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">  
    <property name="advice" ref="methodCacheInterceptor" />  
    <property name="patterns" >  
         <list>
             <value>org\.springframework\.jdbc\.core\.JdbcTemplate\.*queryFor.*value >
         list>  
    property>
bean >

  以上配置了一个方法拦截类和一个方法拦截策略bean,只有符合list中的规则的方法才会被拦截然后送到methodCacheInterceptor类中进行拦截后的缓存处理。

  MethodCacheInterceptor 代码:

  1 package com.sunline.common.utils;  
  2   
  3 import java.io.File;  
  4 import java.io.FileInputStream;  
  5 import java.io.InputStream;  
  6 import java.util.ArrayList;  
  7 import java.util.List;  
  8 import java.util.Properties;  
  9   
 10 import org.aopalliance.intercept.MethodInterceptor;  
 11 import org.aopalliance.intercept.MethodInvocation;  
 12 import org.apache.log4j.Logger;
 13 
 14 import javassist.expr.Instanceof;  
 15   
 16   
 17 public class MethodCacheInterceptor implements MethodInterceptor {  
 18     private Logger logger = Logger.getLogger(MethodCacheInterceptor.class);  
 19     private RedisUtil redisUtil; 
 20     private Long defaultCacheExpireTime = 1000*60*60*12L; // 缓存默认的过期时间 一天
 21   
 22     @Override  
 23     public Object invoke(MethodInvocation invocation) throws Throwable {  
 24         Object value = null;  
 25         long s = System.currentTimeMillis();//start time
 26         String targetName = invocation.getThis().getClass().getName();  
 27         String methodName = invocation.getMethod().getName();  
 28        
 29         Object[] arguments = invocation.getArguments();
 30         String key = getCacheKey(targetName, methodName, arguments);  
 31         System.out.println(targetName + "." + methodName + "-----SQL-----"+arguments[0].toString());
 32         try {
 33             // 判断是否有缓存  
 34             if (redisUtil.exists(key)) {
 35                 value = redisUtil.get(key);
 36                 System.out.println("----获取缓存---key="+key + "----耗时:"+(System.currentTimeMillis()-s)+"ms");
 37                 return value;  
 38             }
 39             // 写入缓存  
 40             value = invocation.proceed();  
 41             if (value != null) {  
 42                 final String tkey = key;  
 43                 final Object tvalue = value;  
 44                 new Thread(new Runnable() {  
 45                     @Override  
 46                     public void run() {  
 47                         redisUtil.set(tkey, tvalue, defaultCacheExpireTime);  
 48                     }  
 49                 }).start();  
 50             }  
 51         } catch (Exception e) {  
 52             e.printStackTrace();  
 53             if (value == null) {  
 54                 return invocation.proceed();  
 55             }  
 56         }
 57         System.out.println("----数据库查询----耗时:"+(System.currentTimeMillis()-s)+"ms");
 58         
 59         return value;  
 60     }  
 61   
 62     /** 
 63      * 是否加入缓存 
 64      *  
 65      * @return 
 66      */  
 67     private boolean isAddCache(String targetName, String methodName) {  
 68         boolean flag = true;  
 69         if (targetNamesList.contains(targetName)  
 70                 || methodNamesList.contains(methodName)) {  
 71             flag = false;  
 72         }
 73         return flag;  
 74     }  
 75   
 76     /** 
 77      * 创建缓存key 
 78      * 
 79      * @param targetName 
 80      * @param methodName 
 81      * @param arguments 
 82      */  
 83     private String getCacheKey(String targetName, String methodName,  
 84             Object[] arguments) {  
 85         StringBuffer sbu = new StringBuffer();  
 86         if(targetName.contains("JdbcTemplate") && methodName.contains("query") && arguments.length == 1 ){
 87             if(arguments[0] instanceof String){
 88                 return MD5Utils.encryptMD5((String)arguments[0]);
 89             }
 90         }
 91         sbu.append(targetName).append("_").append(methodName);  
 92         if ((arguments != null) && (arguments.length != 0)) {  
 93             for (int i = 0; i < arguments.length; i++) {  
 94                 sbu.append("_").append(arguments[i]);  
 95             }  
 96         }  
 97         return sbu.toString();  
 98     }  
 99   
100     public void setRedisUtil(RedisUtil redisUtil) {  
101         this.redisUtil = redisUtil;  
102     }  
103 }  

  方法需要实现接口MethodInterceptor,在方法invoke中编写拦截方法后判断是否有缓存有则取缓存没有就调用原逻辑并加入缓存的逻辑。

三、其他相关问题及报错问题及解决办法

  在实际工程中多多少少会有一些在按照本办法修改后仍然报错或拦截不起作用的情况,此处仅列出小弟在使用中遇到的情况,如有其他情况欢迎留言共同交流。

  1、配置编译错误,提示元素“aop:aspectj-autoproxy”的前缀“aop”未绑定

    解决办法:检查spring配置文件顶部的beans属性,添加如下配置:

    ps:同类的其他如"前缀xxx未绑定"同理应该都可以用改办法解决

<beans 
    ......
    xmlns:aop="http://www.springframework.org/schema/aop"  
    ......
xsi:schemaLocation="
    ......
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd  
    ......
">

  2、java.lang.ClassNotFoundException: org.aspectj.lang.annotation.Around 启动报错

    解决办法:缺spring相关jar包,pom文件添加如下配置,也可手动引入相关包

<dependency>  
  <groupId>org.aspectjgroupId>  
  <artifactId>aspectjrtartifactId>  
  <version>1.8.0version>  
dependency>  
<dependency>  
  <groupId>org.aspectjgroupId>  
  <artifactId>aspectjweaverartifactId>  
  <version>1.8.0version>  
dependency>