SpringCache 整合 Redis,這才是優雅的快取解決方案!
快取可以說是加速服務響應速度的一種非常有效並且簡單的方式。在快取領域,有很多知名的框架,如EhCache 、Guava、HazelCast等。
Redis作為key-value型資料庫,由於他的這一特性,Redis也成為一種流行的資料快取工具。
在傳統方式下對於快取的處理程式碼是非常臃腫的。
例如:我們要把一個查詢函式加入快取功能,大致需要三步。
-
在函式執行前,我們需要先檢查快取中是否存在資料,如果存在則返回快取資料
-
如果不存在,就需要在資料庫的資料查詢出來。
-
最後把資料存放在快取中,當下次呼叫此函式時,就可以直接使用快取資料,減輕了資料庫壓力。
那麼實現上面的三步需要多少程式碼呢?下面是一個示例:
img
上圖中的紅色部分都是模板程式碼,真正與這個函式有關的程式碼卻只佔了1/5,對於所有需要實現快取功能的函式,都需要加上臃腫的模板程式碼。可謂是一種極不優雅的解決方案。
那麼如何讓臃腫的程式碼重回清新的當初呢?
AOP不就是專門解決這種模板式程式碼的最佳方案嗎,幸運的是我們不需要再自己實現切面了,SpringCache已經為我們提供好了切面,我們只需要進行簡單的配置,就可以重回當初了,像下面這樣:
img
只需要加一個註解就可以了,對於原來的程式碼連改都不需要改,是不是已經躍躍欲試了?
對於配置SpringCache只需要三步:
第一步:加入相關依賴:
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>1.6.0.RELEASE</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.3.2</version> </dependency>
第二步:配置SpringCache,Redis連線等資訊
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:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:cache="http://www.springframework.org/schema/cache" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.2.xsd"> <!-- 配置檔案載入 --> <context:property-placeholder location="classpath:*.properties"/> <cache:annotation-driven cache-manager="cacheManager"/> <!-- redis連線池 --> <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> <!-- 連線工廠 --> <bean id="JedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:host-name="${redis.host}" p:port="${redis.port}" p:password="${redis.pass}" p:pool-config-ref="poolConfig"/> <!-- redis模板 --> <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"> <property name="connectionFactory" ref="JedisConnectionFactory" /> </bean> <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager"> <property name="caches"> <set> <!-- 這裡可以配置多個redis --> <bean class="com.cky.rest.utils.RedisCache"> <property name="redisTemplate" ref="redisTemplate" /> <property name="name" value="content"/> <!-- name對應的名稱要在類或方法的註解中使用 --> </bean> </set> </property> </bean> </beans>
redis.properties檔案:
# Redis settings
# server IP
redis.host=192.168.100.55
# server port
redis.port=6379
# server pass
redis.pass=
# use dbIndex
redis.database=0
#max idel instance of jedis
redis.maxIdle=300
#if wait too long ,throw JedisConnectionException
redis.maxWait=3000
#if true,it will validate before borrow jedis instance,what you get instance is all usefull
redis.testOnBorrow=true
第三步,編寫Cache介面實現類
Spring 對於快取只是提供了抽象的介面,並且通過介面來呼叫功能,沒有具體的實現類,所以需要我們自己實現具體的操作。
在上面配置中可知,每個實現類都會注入一個redisTemplate例項,我們就可以通過redisTemplate來操作redis
package com.cky.rest.utils;
import java.io.Serializable;
import org.apache.commons.lang3.SerializationUtils;
import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
public class RedisCache implements Cache {
private RedisTemplate<String, Object> redisTemplate;
private String name;
@Override
public void clear() {
System.out.println("-------快取清理------");
redisTemplate.execute(new RedisCallback<String>() {
@Override
public String doInRedis(RedisConnection connection) throws DataAccessException {
connection.flushDb();
return "ok";
}
});
}
@Override
public void evict(Object key) {
System.out.println("-------快取刪除------");
final String keyf=key.toString();
redisTemplate.execute(new RedisCallback<Long>() {
@Override
public Long doInRedis(RedisConnection connection) throws DataAccessException {
return connection.del(keyf.getBytes());
}
});
}
@Override
public ValueWrapper get(Object key) {
System.out.println("------快取獲取-------"+key.toString());
final String keyf = key.toString();
Object object = null;
object = redisTemplate.execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
byte[] key = keyf.getBytes();
byte[] value = connection.get(key);
if (value == null) {
System.out.println("------快取不存在-------");
return null;
}
return SerializationUtils.deserialize(value);
}
});
ValueWrapper obj=(object != null ? new SimpleValueWrapper(object) : null);
System.out.println("------獲取到內容-------"+obj);
return obj;
}
@Override
public void put(Object key, Object value) {
System.out.println("-------加入快取------");
System.out.println("key----:"+key);
System.out.println("key----:"+value);
final String keyString = key.toString();
final Object valuef = value;
final long liveTime = 86400;
redisTemplate.execute(new RedisCallback<Long>() {
@Override
public Long doInRedis(RedisConnection connection) throws DataAccessException {
byte[] keyb = keyString.getBytes();
byte[] valueb = SerializationUtils.serialize((Serializable) valuef);
connection.set(keyb, valueb);
if (liveTime > 0) {
connection.expire(keyb, liveTime);
}
return 1L;
}
});
}
@Override
public <T> T get(Object arg0, Class<T> arg1) {
// TODO Auto-generated method stub
return null;
}
@Override
public String getName() {
return this.name;
}
@Override
public Object getNativeCache() {
return this.redisTemplate;
}
@Override
public ValueWrapper putIfAbsent(Object arg0, Object arg1) {
// TODO Auto-generated method stub
return null;
}
public RedisTemplate<String, Object> getRedisTemplate() {
return redisTemplate;
}
public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void setName(String name) {
this.name = name;
}
}
在配置過程中曾經出現過兩次錯誤:
-
Xxxx.ClassNotFoundException 最後發現是jar下載不完整,把maven本地倉庫的對應jar包資料夾刪除完從新下載就好了
-
Xxxx.MethodNotFoundException 這種情況是版本不對,換成第一步中的版本就可以了
SpringCache中常見註解的使用:
「@Cacheable註解」
最常用的註解,會把被註解方法的返回值快取。工作原理是:首先在快取中查詢,如果沒有執行方法並快取結果,然後返回資料。此註解的快取名必須指定,和cacheManager中的caches中的某一個Cache的name值相對應。可以使用value或cacheNames指定。
如果沒有指定key屬性,spring會使用預設的主鍵生成器產生主鍵。也可以自定義主鍵,在key中可以使用SpEL表示式。如下:
@Cacheable(cacheNames=”content”,key=”#user.userId”)
Public User getUser(User user){
xxxxx
}
可以使用condition屬性,來給快取新增條件,如下:
@Cacheable(cacheNames=”content”,key=”#user.userId”,condition=”#user.age<40”)
Public User getUser(User user){xxxxx}
「@CachePut註解」
先執行方法,然後將返回值放回快取。可以用作快取的更新。
「@CacheEvict註解」
該註解負責從快取中顯式移除資料,通常快取資料都有有效期,當過期時資料也會被移除。
此註解多了兩個屬性:
allEntries是否移除所有快取條目。
beforeInvocation:在方法呼叫前還是呼叫後完成移除操作。true/false