使用方法攔截機制在不修改原邏輯基礎上爲 spring MVC 工程添加 Redis 緩存

首先,相關文件:連接: https://pan.baidu.com/s/1H-D2M4RfXWnKzNLmsbqiQQ 密碼: 5dzkjava

文件說明:linux

  redis-2.4.5-win32-win64.zip --windows程序包,無需安裝,直接執行redis-server.exe 啓動服務,執行redis-cli.exe啓動客戶端web

  redis-3.2.9.tar.gz --linux程序包,須要make安裝一下,而後執行src/redis-server 注意啓動時指定配置文件爲redis.confredis

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

 

1、redis 安裝數據庫

  一、windows 安裝與運行apache

    直接使用下載的程序包,解壓後得到文件夾(假定爲redis_home),進入redis_home/32bit(或者redis_home/64bit)目錄,運行redis-server.exe便可運行redis服務。經過查看運行窗口的日誌。windows

    運行時指定配置和日誌輸出文件:緩存

      cmd窗口運行redis-server.exe  ./redis.conf > ./redis.log服務器

    服務方式運行:

      使用redis-server.exe 運行須要在命令行窗口中,關閉窗口後程序也會跟着關閉,若是想要後臺一直默默的運行,能夠從官網下載msi版的安裝文件,安裝後會生成一個redis的服務,啓動服務便可

      二、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 方式,指定配置文件 , 指定日誌文件路徑

 三、運行後驗證系統是否正常運行

    進入程序目錄(和redis-server一個目錄),運行redis-client程序驗證:

    redis-cli -p 127.0.0.1 -a eas@1234      --    -p:指定redis服務host,-a指定密碼(若是有)

    set 'test' 'HelloWord!'   --存入數據

    get 'test'    --取出數據

    flushAll   --清空全部數據

    四、經過ip地址沒法鏈接問題

      redis-cli 客戶端驗證時,可能出現用ip地址沒法鏈接用localhost能夠鏈接的狀況,這是由於沒有配置容許經過ip外網訪問。

    打開redis.conf配置文件,作以下修改:

      註釋 bind 行配置;

      修改密碼 (去掉註釋 requirepass foobared並修改成requirepass eas@1234);

      修改protected-mode no;

    五、redis desktop Manager軟件

   可以使用Redis DeskTop Manager可視化軟件工具查看redis數據

 

2、redis緩存在項目中的使用

  一、使用攔截器方式讓redis介入原有的程序邏輯

    開發環境:Maven、spring MVC 架構的web工程

    需求: 當前數據庫由於多種因素查詢效率比較低下,須要使用redis緩存提交效率,可是以前的代碼開發已經比較多了不方便大規模修改代碼,但願儘量不修改以前的代碼。

    1.1 maven pom配置:

       

<!--引用spring-redis相關jar包-->
<
dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>1.6.1.RELEASE</version> </dependency> <!--引用jedis相關jar包-->
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.7.3</version> </dependency>
<!--spring aop相關包-->
<dependency>  
  <groupId>org.aspectj</groupId>  
  <artifactId>aspectjrt</artifactId>  
  <version>1.8.0</version>  
</dependency>  
<dependency>  
  <groupId>org.aspectj</groupId>  
  <artifactId>aspectjweaver</artifactId>  
  <version>1.8.0</version>  
</dependency> 

     1.2 spring bean配置

    

 
 
<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  
    ......
">
<!-- 配置connectionFactory,提供redis服務器相關鏈接參數 -->
<
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 >
<!-- 配置spring的redisTemplate,注入connectionFactory,指定key和value的序列化類 -->
<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 >
<!-- 配置redis工具類,注入redisTemplate -->
<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<Serializable, Object> 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<Serializable> 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<Serializable, Object> 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<Serializable, Object> 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<Serializable, Object> 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<Serializable, Object> getRedisTemplate() {  
104         return this.redisTemplate;  
105     }
106     public void setRedisTemplate(RedisTemplate<Serializable, Object> redisTemplate) {  
107         this.redisTemplate = redisTemplate;  
108     }  
109 }  

 

  以上2步爲項目添加了redis相關功能,java代碼中可使用redisUtil的相關方法操做緩存了。

  三、實現不修改原有代碼的前提下給現有的項目添加緩存邏輯

    其實單純的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 >
<!-- 須要加入緩存的類或方法  proxy-target-class="true"-->  
<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中編寫攔截方法後判斷是否有緩存有則取緩存沒有就調用原邏輯並加入緩存的邏輯。

 

 

 

3、其餘相關問題及報錯問題及解決辦法

  在實際工程中多多少少會有一些在按照本辦法修改後仍然報錯或攔截不起做用的狀況,此處僅列出小弟在使用中遇到的狀況,若有其餘狀況歡迎留言共同交流。

  一、<aop:aspectj-autoproxy proxy-target-class="true"/>配置編譯錯誤,提示元素「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  
    ......
">
View Code

  二、java.lang.ClassNotFoundException: org.aspectj.lang.annotation.Around 啓動報錯

    解決辦法:缺spring相關jar包,pom文件添加以下配置,也可手動引入相關包

    

<dependency>  
  <groupId>org.aspectj</groupId>  
  <artifactId>aspectjrt</artifactId>  
  <version>1.8.0</version>  
</dependency>  
<dependency>  
  <groupId>org.aspectj</groupId>  
  <artifactId>aspectjweaver</artifactId>  
  <version>1.8.0</version>  
</dependency> 
View Code
相關文章
相關標籤/搜索