天天看點

SSM 整合redis,使用AOP實作緩存過濾器

前段時間寫了一篇SSM三大架構的整合教程,

今天就順便在這個基礎上繼續整合下redis吧。

 第一步,加入相關依賴包,pom.xml:

<!--redis-->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>1.6.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.7.3</version>
        </dependency>      

第二步,Redis的 相關配置參數redis.properties:

redis.host=127.0.0.1
redis.port=6379
redis.password=12345
redis.maxIdle=100
redis.maxWait=1000
redis.testOnBorrow=true
redis.timeout=100000
defaultCacheExpireTime=3600      

第三步,applicationContext-Redis.xml:

這裡說明下,如果你不分開單獨一個redis的配置檔案也可以。我目前單獨分開了,是以我是web.xml裡面加載讀取的是spring-root-context.xml檔案,而spring-root-context.xml檔案内再去加載讀取其他的配置檔案,

SSM 整合redis,使用AOP實作緩存過濾器

 好,繼續看我們的applicationContext-Redis.xml,

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">

<!--引入Redis配置檔案-->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
        <list>
            <value>classpath*:redis.properties</value>
        </list>
    </property>
    <property name="ignoreUnresolvablePlaceholders" value="true" />
</bean>

<!-- jedis 連接配接池配置 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
    <property name="maxIdle" value="${redis.maxIdle}"/>
    <property name="maxWaitMillis" value="${redis.maxWait}"/>
    <property name="testOnBorrow" value="${redis.testOnBorrow}"/>
</bean>
<!-- redis連接配接工廠 -->
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
    <property name="poolConfig" ref="poolConfig"/>
    <property name="port" value="${redis.port}"/>
    <property name="hostName" value="${redis.host}"/>
    <property name="password" value="${redis.password}"/>
    <property name="timeout" value="${redis.timeout}"></property>
</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.GenericJackson2JsonRedisSerializer"/>
    </property>

    <property name="hashKeySerializer">
        <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
    </property>
    <property name="hashValueSerializer">
        <bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
    </property>

    <!--開啟事務  -->
    <property name="enableTransactionSupport" value="true"></property>

</bean>

<!-- 緩存攔截器配置 -->
<bean id="methodCacheInterceptor" class="com.springmvc.interceptor.MethodCacheInterceptor">
    <property name="redisUtil" ref="redisUtil"/>
    <property name="defaultCacheExpireTime" value="${defaultCacheExpireTime}"/>
    <!-- 禁用緩存的類名清單 -->
    <property name="targetNamesList">
        <list>
            <value></value>
        </list>
    </property>
    <!-- 禁用緩存的方法名清單 -->
    <property name="methodNamesList">
        <list>
            <value></value>
        </list>
    </property>
</bean>
<bean id="redisUtil" class="com.springmvc.util.RedisUtil">
    <property name="redisTemplate" ref="redisTemplate"/>
</bean>
<!--配置切面攔截方法 -->
<aop:config proxy-target-class="true">
    <aop:pointcut id="controllerMethodPointcut"
                  expression="execution(* com.springmvc.service.impl.*.select*(..))"/>
    <aop:advisor advice-ref="methodCacheInterceptor" pointcut-ref="controllerMethodPointcut"/>
</aop:config>
</beans>      

OK,到了這裡配置的XML基本完畢,這時候你可以看到配置檔案裡面有些地方顯示紅色了,不慌,是因為缺少了 

RedisUtil.java (對應redis的資料相關存儲讀取操作工具類 )

MethodCacheInterceptor.java(緩存過濾器,可以看到配置檔案裡面,我們采取了AOP方式配置切面進行攔截)

那麼接下來加入RedisUtil.java:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Component
public final class RedisUtil {
    @Autowired
    private RedisTemplate redisTemplate;

    // ============================Common=============================
    /**
     * 功能描述:根據key 設定過期時間
     * @param key
     * @param time
     * @return
     */
    public boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 功能描述:根據key 擷取過期時間
     *
     * @param key 鍵,不能為null
     * @return 時間(s) 傳回0 代表永久有效
     * @author Jesson
     * @date 2018/8/27 10:10
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 功能描述:判斷key是否存在
     *
     * @param key
     * @return true 存在 false不存在
     * @author Jesson
     * @date 2018/8/27 10:12
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 功能描述:删除緩存
     *
     * @param key 可為多個
     * @return
     * @author Jesson
     * @date 2018/8/27 10:13
     */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }
    // ============================String=============================

    /**
     * 普通緩存擷取
     *
     * @param key 鍵
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通緩存放入
     *
     * @param key   鍵
     * @param value 值
     * @return true成功 false失敗
     */
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }

    /**
     * 普通緩存放入并設定時間
     *
     * @param key   鍵
     * @param value 值
     * @param time  時間(秒) time要大于0 如果time小于等于0 将設定無限期
     * @return true成功 false 失敗
     */
    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 遞增
     *
     * @param key   鍵
     * @param delta 要增加幾(大于0)
     * @return
     */
    public long incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("遞增因子必須大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 遞減
     *
     * @param key   鍵
     * @param delta 要減少幾(小于0)
     * @return
     */
    public long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("遞減因子必須大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }

    // ================================Map=================================

    /**
     * HashGet
     *
     * @param key  鍵 不能為null
     * @param item 項 不能為null
     * @return 值
     */
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 擷取hashKey對應的所有鍵值
     *
     * @param key 鍵
     * @return 對應的多個鍵值
     */
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     *
     * @param key 鍵
     * @param map 對應多個鍵值
     * @return true 成功 false 失敗
     */
    public boolean hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * HashSet 并設定時間
     *
     * @param key  鍵
     * @param map  對應多個鍵值
     * @param time 時間(秒)
     * @return true成功 false失敗
     */
    public boolean hmset(String key, Map<String, Object> map, long time) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一張hash表中放入資料,如果不存在将建立
     *
     * @param key   鍵
     * @param item  項
     * @param value 值
     * @return true 成功 false失敗
     */
    public boolean hset(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一張hash表中放入資料,如果不存在将建立
     *
     * @param key   鍵
     * @param item  項
     * @param value 值
     * @param time  時間(秒) 注意:如果已存在的hash表有時間,這裡将會替換原有的時間
     * @return true 成功 false失敗
     */
    public boolean hset(String key, String item, Object value, long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除hash表中的值
     *
     * @param key  鍵 不能為null
     * @param item 項 可以使多個 不能為null
     */
    public void hdel(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }

    /**
     * 判斷hash表中是否有該項的值
     *
     * @param key  鍵 不能為null
     * @param item 項 不能為null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    /**
     * hash遞增 如果不存在,就會建立一個 并把新增後的值傳回
     *
     * @param key  鍵
     * @param item 項
     * @param by   要增加幾(大于0)
     * @return
     */
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }

    /**
     * hash遞減
     *
     * @param key  鍵
     * @param item 項
     * @param by   要減少記(小于0)
     * @return
     */
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }

    // ============================set=============================

    /**
     * 根據key擷取Set中的所有值
     *
     * @param key 鍵
     * @return
     */
    public Set<Object> sGet(String key) {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根據value從一個set中查詢,是否存在
     *
     * @param key   鍵
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, Object value) {
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将資料放入set緩存
     *
     * @param key    鍵
     * @param values 值 可以是多個
     * @return 成功個數
     */
    public long sSet(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 将set資料放入緩存
     *
     * @param key    鍵
     * @param time   時間(秒)
     * @param values 值 可以是多個
     * @return 成功個數
     */
    public long sSetAndTime(String key, long time, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0) {
                expire(key, time);
            }
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 擷取set緩存的長度
     *
     * @param key 鍵
     * @return
     */
    public long sGetSetSize(String key) {
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 移除值為value的
     *
     * @param key    鍵
     * @param values 值 可以是多個
     * @return 移除的個數
     */
    public long setRemove(String key, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    // ===============================list=================================

    /**
     * 擷取list緩存的内容
     *
     * @param key   鍵
     * @param start 開始
     * @param end   結束 0 到 -1代表所有值
     * @return
     */
    public List<Object> lGet(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 擷取list緩存的長度
     *
     * @param key 鍵
     * @return
     */
    public long lGetListSize(String key) {
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 通過索引 擷取list中的值
     *
     * @param key   鍵
     * @param index 索引 index>=0時, 0 表頭,1 第二個元素,依次類推;index<0時,-1,表尾,-2倒數第二個元素,依次類推
     * @return
     */
    public Object lGetIndex(String key, long index) {
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 将list放入緩存
     *
     * @param key   鍵
     * @param value 值
     * @return
     */
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入緩存
     *
     * @param key   鍵
     * @param value 值
     * @param time  時間(秒)
     * @return
     */
    public boolean lSet(String key, Object value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入緩存
     *
     * @param key   鍵
     * @param value 值
     * @return
     */
    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入緩存
     *
     * @param key   鍵
     * @param value 值
     * @param time  時間(秒)
     * @return
     */
    public boolean lSet(String key, List<Object> value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根據索引修改list中的某條資料
     *
     * @param key   鍵
     * @param index 索引
     * @param value 值
     * @return
     */
    public boolean lUpdateIndex(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 移除N個值為value
     *
     * @param key   鍵
     * @param count 移除多少個
     * @param value 值
     * @return 移除的個數
     */
    public long lRemove(String key, long count, Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 功能描述:擷取所有鍵值
     *
     */
    public Set<String> getKeys(String pattern) {
        return redisTemplate.keys(pattern);
    }



    public void setRedisTemplate(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public RedisTemplate getRedisTemplate() {
        return redisTemplate;
    }
}      

然後是, MethodCacheInterceptor.java:

import com.springmvc.util.RedisUtil;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import java.util.List;

/**
 * @Author : JCccc
 * @CreateTime : 2019/8/1
 * @Description :
 **/


/**
 * Redis緩存過濾器
 */
public class MethodCacheInterceptor implements MethodInterceptor {
    private RedisUtil redisUtil;
    private List<String> targetNamesList; // 禁用緩存的類名清單
    private List<String> methodNamesList; // 禁用緩存的方法清單
    private String defaultCacheExpireTime; // 緩存預設的過期時間

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Object value = null;

        String targetName = invocation.getThis().getClass().getName();
        String methodName = invocation.getMethod().getName();
        if (!isAddCache(targetName, methodName)) {
            // 跳過緩存傳回結果
            return invocation.proceed();
        }
        Object[] arguments = invocation.getArguments();
        String key = getCacheKey(targetName, methodName, arguments);
        try {
            // 判斷是否有緩存
            if (redisUtil.hasKey(key)) {
                return redisUtil.get(key);
            }
            // 寫入緩存
            value = invocation.proceed();
            if (value != null) {
                final String tkey = key;
                final Object tvalue = value;
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        redisUtil.set(tkey, tvalue, Long.parseLong(defaultCacheExpireTime));
                    }
                }).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
            if (value == null) {
                return invocation.proceed();
            }
        }
        return value;
    }

    /**
     * 是否加入緩存
     *
     * @return
     */
    private boolean isAddCache(String targetName, String methodName) {
        boolean flag = true;
        if (targetNamesList.contains(targetName)
                || methodNamesList.contains(methodName) || targetName.contains("$$EnhancerBySpringCGLIB$$")) {
            flag = false;
        }
        return flag;
    }

    /**
     * 建立緩存key
     *
     * @param targetName
     * @param methodName
     * @param arguments
     */
    private String getCacheKey(String targetName, String methodName,
                               Object[] arguments) {
        StringBuffer sbu = new StringBuffer();
        sbu.append(targetName).append("_").append(methodName);
        if ((arguments != null) && (arguments.length != 0)) {
            for (int i = 0; i < arguments.length; i++) {
                sbu.append("_").append(arguments[i]);
            }
        }
        return sbu.toString();
    }

    public void setRedisUtil(RedisUtil redisUtil) {
        this.redisUtil = redisUtil;
    }

    public void setTargetNamesList(List<String> targetNamesList) {
        this.targetNamesList = targetNamesList;
    }

    public void setMethodNamesList(List<String> methodNamesList) {
        this.methodNamesList = methodNamesList;
    }

    public void setDefaultCacheExpireTime(String defaultCacheExpireTime) {
        this.defaultCacheExpireTime = defaultCacheExpireTime;
    }
}      

OK,整合全部完成,接下來可以進行測試下,看看是什麼效果。

在進行測試之前,先慣例說下流程:

我們是想将某些方法從mysql資料庫查詢資料,然後又想理由redis緩存技術。那麼就是,在查資料之前,先要去redis資料庫裡面看下,是否對應的緩存資料存在,如果存在就沒必要去mysql資料庫查詢了,直接取出資料傳回即可;如果不存在,則需要去mysql資料查詢出相關資料,然後順便也存入redis緩存中(這樣下次就能直接從緩存讀取)。

 正因為我們想實作上述的流程邏輯,是以我們利用AOP進行了緩存過濾器,也就是在過濾器配置的相關方法,我們都會進行AOP切面攔截,目前我在代碼裡面配置的是我的service層裡面的實作類impl裡面帶有‘select’的方法,

SSM 整合redis,使用AOP實作緩存過濾器

好了,一下子沒忍住又啰嗦了很多,我們來開始測試,

随便在一個controller裡面寫個測試接口,調用一個查詢的方法:

@Autowired
    RedisUtil redisUtil;

    @RequestMapping(value = "/getMessageBoardData", produces = "application/json; charset=utf-8")
    public String getMessageBoardData() {
        
        Messageboard messageboard =  messageboardServiceImpl.selectByPrimaryKey(5);
        return messageboard.toString();


    }      

 OK,我們項目跑起來,然後先看看我們的Redis資料庫(沒有任何值):

SSM 整合redis,使用AOP實作緩存過濾器

然後我們調用接口, 可以看到已經查出相關資料了,而且看看控制台,是執行了MYSQL語句查詢的,因為我們原先的redis裡面啥資料都沒緩存:

SSM 整合redis,使用AOP實作緩存過濾器
SSM 整合redis,使用AOP實作緩存過濾器

然後緊接着,我們去看看本地的redis資料庫,發現多了一個存入的值:

SSM 整合redis,使用AOP實作緩存過濾器

沒錯,這個正是我們的緩存過濾器存進去的(TTL是指緩存資料的存在時長,機關秒,使我們自己設定的時長,可以回過頭看看代碼)。 

是以我們繼續調用剛剛的接口,再查一次,發現資料一樣是查詢出來了,但是控制台沒有新的執行SQL語句日志列印:

SSM 整合redis,使用AOP實作緩存過濾器

 沒錯,這就是因為每次調用查詢方法的時候,緩存過濾器都在啟作用,第一次已經把資料存進redis了,是以第二次查詢的時候,就直接從redis取出緩存資料了,就是緩存過濾器的這段代碼:

SSM 整合redis,使用AOP實作緩存過濾器

OK,測試完畢。

PS:以下補充一些調用Redis.java對資料的存入,查詢,删除等接口測試,畢竟可能有些小夥伴整合redis就是單純想存一些資料和查詢一下。

@RequestMapping(value = "/operationRedisData", produces = "application/json; charset=utf-8")
    public void operationRedisData() {
           //簡單舉例調用下 redisUtil 裡面的一些方法,其餘都可以自己調用
           redisUtil.set("test1","JCccc");
           redisUtil.set("test2",123456);
           Map map =new HashMap();
           map.put("A","a");
           map.put("B","b");
           redisUtil.hmset("TEST-MAP",map);

        System.out.println(redisUtil.get("test2")+" \n  "+redisUtil.hget("TEST-MAP","A"));
    }      

 調用接口,看看redis資料庫,存入了相關的值,控制台也列印出了相關的查詢值:

繼續閱讀