您好,登錄后才能下訂單哦!
這篇文章主要介紹springboot中如何自定義兩級(jí)緩存,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!
我們?cè)谑褂胹pringboot的時(shí)候,總是需要去訪問(wèn)數(shù)據(jù)庫(kù),如果過(guò)于頻繁的訪問(wèn),會(huì)給數(shù)據(jù)庫(kù)造成很大的壓力。這期間添加緩存,就是為了減輕了springboot訪問(wèn)數(shù)據(jù)庫(kù)時(shí)的壓力。
工作中用到了springboot的緩存,使用起來(lái)挺方便的,直接引入redis或者ehcache這些緩存依賴包和相關(guān)緩存的starter依賴包,然后在啟動(dòng)類中加入@EnableCaching注解,然后在需要的地方就可以使用@Cacheable和@CacheEvict使用和刪除緩存了。這個(gè)使用很簡(jiǎn)單,相信用過(guò)springboot緩存的都會(huì)玩,這里就不再多說(shuō)了。美中不足的是,springboot使用了插件式的集成方式,雖然用起來(lái)很方便,但是當(dāng)你集成ehcache的時(shí)候就是用ehcache,集成redis的時(shí)候就是用redis。如果想兩者一起用,ehcache作為本地一級(jí)緩存,redis作為集成式的二級(jí)緩存,使用默認(rèn)的方式據(jù)我所知是沒(méi)法實(shí)現(xiàn)的(如果有高人可以實(shí)現(xiàn),麻煩指點(diǎn)下我)。畢竟很多服務(wù)需要多點(diǎn)部署,如果單獨(dú)選擇ehcache可以很好地實(shí)現(xiàn)本地緩存,但是如果在多機(jī)之間共享緩存又需要比較費(fèi)時(shí)的折騰,如果選用集中式的redis緩存,因?yàn)槊看稳?shù)據(jù)都要走網(wǎng)絡(luò),總感覺(jué)性能不會(huì)太好。
為了不要侵入springboot原本使用緩存的方式,這里自己定義了兩個(gè)緩存相關(guān)的注解,如下
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Cacheable {
String value() default "";
String key() default "";
//泛型的Class類型
Class<?> type() default Exception.class;
}
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheEvict {
String value() default "";
String key() default "";
}
如上兩個(gè)注解和spring中緩存的注解基本一致,只是去掉了一些不常用的屬性。說(shuō)到這里,不知道有沒(méi)有朋友注意過(guò),當(dāng)你在springboot中單獨(dú)使用redis緩存的時(shí)候,Cacheable和CacheEvict注解的value屬性,實(shí)際上在redis中變成了一個(gè)zset類型的值的key,而且這個(gè)zset里面還是空的,比如@Cacheable(value="cache1",key="key1"),正常情況下redis中應(yīng)該是出現(xiàn)cache1 -> map(key1,value1)這種形式,其中cache1作為緩存名稱,map作為緩存的值,key作為map里的鍵,可以有效的隔離不同的緩存名稱下的緩存。但是實(shí)際上redis里確是cache1 -> 空(zset)和key1 -> value1,兩個(gè)獨(dú)立的鍵值對(duì),試驗(yàn)得知不同的緩存名稱下的緩存完全是共用的,如果有感興趣的朋友可以去試驗(yàn)下,也就是說(shuō)這個(gè)value屬性實(shí)際上是個(gè)擺設(shè),鍵的唯一性只由key屬性保證。我只能認(rèn)為這是spring的緩存實(shí)現(xiàn)的bug,或者是特意這么設(shè)計(jì)的,(如果有知道啥原因的歡迎指點(diǎn))。
回到正題,有了注解還需要有個(gè)注解處理類,這里我使用aop的切面來(lái)進(jìn)行攔截處理,原生的實(shí)現(xiàn)其實(shí)也大同小異。切面處理類如下:
import com.xuanwu.apaas.core.multicache.annotation.CacheEvict;
import com.xuanwu.apaas.core.multicache.annotation.Cacheable;
import com.xuanwu.apaas.core.utils.JsonUtil;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.json.JSONArray;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 多級(jí)緩存切面
* @author rongdi
*/
@Aspect
@Component
public class MultiCacheAspect {
private static final Logger logger = LoggerFactory.getLogger(MultiCacheAspect.class);
@Autowired
private CacheFactory cacheFactory;
//這里通過(guò)一個(gè)容器初始化監(jiān)聽(tīng)器,根據(jù)外部配置的@EnableCaching注解控制緩存開(kāi)關(guān)
private boolean cacheEnable;
@Pointcut("@annotation(com.xuanwu.apaas.core.multicache.annotation.Cacheable)")
public void cacheableAspect() {
}
@Pointcut("@annotation(com.xuanwu.apaas.core.multicache.annotation.CacheEvict)")
public void cacheEvict() {
}
@Around("cacheableAspect()")
public Object cache(ProceedingJoinPoint joinPoint) {
//得到被切面修飾的方法的參數(shù)列表
Object[] args = joinPoint.getArgs();
// result是方法的最終返回結(jié)果
Object result = null;
//如果沒(méi)有開(kāi)啟緩存,直接調(diào)用處理方法返回
if(!cacheEnable){
try {
result = joinPoint.proceed(args);
} catch (Throwable e) {
logger.error("",e);
}
return result;
}
// 得到被代理方法的返回值類型
Class returnType = ((MethodSignature) joinPoint.getSignature()).getReturnType();
// 得到被代理的方法
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
// 得到被代理的方法上的注解
Cacheable ca = method.getAnnotation(Cacheable.class);
//獲得經(jīng)過(guò)el解析后的key值
String key = parseKey(ca.key(),method,args);
Class<?> elementClass = ca.type();
//從注解中獲取緩存名稱
String name = ca.value();
try {
//先從ehcache中取數(shù)據(jù)
String cacheValue = cacheFactory.ehGet(name,key);
if(StringUtils.isEmpty(cacheValue)) {
//如果ehcache中沒(méi)數(shù)據(jù),從redis中取數(shù)據(jù)
cacheValue = cacheFactory.redisGet(name,key);
if(StringUtils.isEmpty(cacheValue)) {
//如果redis中沒(méi)有數(shù)據(jù)
// 調(diào)用業(yè)務(wù)方法得到結(jié)果
result = joinPoint.proceed(args);
//將結(jié)果序列化后放入redis
cacheFactory.redisPut(name,key,serialize(result));
} else {
//如果redis中可以取到數(shù)據(jù)
//將緩存中獲取到的數(shù)據(jù)反序列化后返回
if(elementClass == Exception.class) {
result = deserialize(cacheValue, returnType);
} else {
result = deserialize(cacheValue, returnType,elementClass);
}
}
//將結(jié)果序列化后放入ehcache
cacheFactory.ehPut(name,key,serialize(result));
} else {
//將緩存中獲取到的數(shù)據(jù)反序列化后返回
if(elementClass == Exception.class) {
result = deserialize(cacheValue, returnType);
} else {
result = deserialize(cacheValue, returnType,elementClass);
}
}
} catch (Throwable throwable) {
logger.error("",throwable);
}
return result;
}
/**
* 在方法調(diào)用前清除緩存,然后調(diào)用業(yè)務(wù)方法
* @param joinPoint
* @return
* @throws Throwable
*
*/
@Around("cacheEvict()")
public Object evictCache(ProceedingJoinPoint joinPoint) throws Throwable {
// 得到被代理的方法
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
//得到被切面修飾的方法的參數(shù)列表
Object[] args = joinPoint.getArgs();
// 得到被代理的方法上的注解
CacheEvict ce = method.getAnnotation(CacheEvict.class);
//獲得經(jīng)過(guò)el解析后的key值
String key = parseKey(ce.key(),method,args);
//從注解中獲取緩存名稱
String name = ce.value();
// 清除對(duì)應(yīng)緩存
cacheFactory.cacheDel(name,key);
return joinPoint.proceed(args);
}
/**
* 獲取緩存的key
* key 定義在注解上,支持SPEL表達(dá)式
* @return
*/
private String parseKey(String key,Method method,Object [] args){
if(StringUtils.isEmpty(key)) return null;
//獲取被攔截方法參數(shù)名列表(使用Spring支持類庫(kù))
LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
String[] paraNameArr = u.getParameterNames(method);
//使用SPEL進(jìn)行key的解析
ExpressionParser parser = new SpelExpressionParser();
//SPEL上下文
StandardEvaluationContext context = new StandardEvaluationContext();
//把方法參數(shù)放入SPEL上下文中
for(int i=0;i<paraNameArr.length;i++){
context.setVariable(paraNameArr[i], args[i]);
}
return parser.parseExpression(key).getValue(context,String.class);
}
//序列化
private String serialize(Object obj) {
String result = null;
try {
result = JsonUtil.serialize(obj);
} catch(Exception e) {
result = obj.toString();
}
return result;
}
//反序列化
private Object deserialize(String str,Class clazz) {
Object result = null;
try {
if(clazz == JSONObject.class) {
result = new JSONObject(str);
} else if(clazz == JSONArray.class) {
result = new JSONArray(str);
} else {
result = JsonUtil.deserialize(str,clazz);
}
} catch(Exception e) {
}
return result;
}
//反序列化,支持List<xxx>
private Object deserialize(String str,Class clazz,Class elementClass) {
Object result = null;
try {
if(clazz == JSONObject.class) {
result = new JSONObject(str);
} else if(clazz == JSONArray.class) {
result = new JSONArray(str);
} else {
result = JsonUtil.deserialize(str,clazz,elementClass);
}
} catch(Exception e) {
}
return result;
}
public void setCacheEnable(boolean cacheEnable) {
this.cacheEnable = cacheEnable;
}
}
上面這個(gè)界面使用了一個(gè)cacheEnable變量控制是否使用緩存,為了實(shí)現(xiàn)無(wú)縫的接入springboot,必然需要受到原生@EnableCaching注解的控制,這里我使用一個(gè)spring容器加載完成的監(jiān)聽(tīng)器,然后在監(jiān)聽(tīng)器里找到是否有被@EnableCaching注解修飾的類,如果有就從spring容器拿到MultiCacheAspect對(duì)象,然后將cacheEnable設(shè)置成true。這樣就可以實(shí)現(xiàn)無(wú)縫接入springboot,不知道朋友們還有沒(méi)有更加優(yōu)雅的方法呢?歡迎交流!監(jiān)聽(tīng)器類如下
import com.xuanwu.apaas.core.multicache.CacheFactory;
import com.xuanwu.apaas.core.multicache.MultiCacheAspect;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* 用于spring加載完成后,找到項(xiàng)目中是否有開(kāi)啟緩存的注解@EnableCaching
* @author rongdi
*/
@Component
public class ContextRefreshedListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// 判斷根容器為Spring容器,防止出現(xiàn)調(diào)用兩次的情況(mvc加載也會(huì)觸發(fā)一次)
if(event.getApplicationContext().getParent()==null){
//得到所有被@EnableCaching注解修飾的類
Map<String,Object> beans = event.getApplicationContext().getBeansWithAnnotation(EnableCaching.class);
if(beans != null && !beans.isEmpty()) {
MultiCacheAspect multiCache = (MultiCacheAspect)event.getApplicationContext().getBean("multiCacheAspect");
multiCache.setCacheEnable(true);
}
}
}
}
實(shí)現(xiàn)了無(wú)縫接入,還需要考慮多點(diǎn)部署的時(shí)候,多點(diǎn)的ehcache怎么和redis緩存保持一致的問(wèn)題。在正常應(yīng)用中,一般redis適合長(zhǎng)時(shí)間的集中式緩存,ehcache適合短時(shí)間的本地緩存,假設(shè)現(xiàn)在有A,B和C服務(wù)器,A和B部署了業(yè)務(wù)服務(wù),C部署了redis服務(wù)。當(dāng)請(qǐng)求進(jìn)來(lái),前端入口不管是用LVS或者nginx等負(fù)載軟件,請(qǐng)求都會(huì)轉(zhuǎn)發(fā)到某一個(gè)具體服務(wù)器,假設(shè)轉(zhuǎn)發(fā)到了A服務(wù)器,修改了某個(gè)內(nèi)容,而這個(gè)內(nèi)容在redis和ehcache中都有,這時(shí)候,A服務(wù)器的ehcache緩存,和C服務(wù)器的redis不管控制緩存失效也好,刪除也好,都比較容易,但是這時(shí)候B服務(wù)器的ehcache怎么控制失效或者刪除呢?一般比較常用的方式就是使用發(fā)布訂閱模式,當(dāng)需要?jiǎng)h除緩存的時(shí)候在一個(gè)固定的通道發(fā)布一個(gè)消息,然后每個(gè)業(yè)務(wù)服務(wù)器訂閱這個(gè)通道,收到消息后刪除或者過(guò)期本地的ehcache緩存(最好是使用過(guò)期,但是redis目前只支持對(duì)key的過(guò)期操作,沒(méi)辦法操作key下的map里的成員的過(guò)期,如果非要強(qiáng)求用過(guò)期,可以自己加時(shí)間戳自己實(shí)現(xiàn),不過(guò)用刪除出問(wèn)題的幾率也很小,畢竟加緩存的都是讀多寫(xiě)少的應(yīng)用,這里為了方便都是直接刪除緩存)??偨Y(jié)起來(lái)流程就是更新某條數(shù)據(jù),先刪除redis中對(duì)應(yīng)的緩存,然后發(fā)布一個(gè)緩存失效的消息在redis的某個(gè)通道中,本地的業(yè)務(wù)服務(wù)去訂閱這個(gè)通道的消息,當(dāng)業(yè)務(wù)服務(wù)收到這個(gè)消息后去刪除本地對(duì)應(yīng)的ehcache緩存,redis的各種配置如下
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xuanwu.apaas.core.multicache.subscriber.MessageSubscriber;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class MultiCacheConfig {
@Bean
public CacheManager cacheManager(RedisTemplate redisTemplate) {
RedisCacheManager rcm = new RedisCacheManager(redisTemplate);
//設(shè)置緩存過(guò)期時(shí)間(秒)
Map<String, Long> expires = new HashMap<>();
expires.put("ExpOpState",0L);
expires.put("ImpOpState",0L);
rcm.setExpires(expires);
rcm.setDefaultExpiration(600);
return rcm;
}
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
StringRedisTemplate template = new StringRedisTemplate(factory);
StringRedisSerializer redisSerializer = new StringRedisSerializer();
template.setValueSerializer(redisSerializer);
template.afterPropertiesSet();
return template;
}
/**
* redis消息監(jiān)聽(tīng)器容器
* 可以添加多個(gè)監(jiān)聽(tīng)不同話題的redis監(jiān)聽(tīng)器,只需要把消息監(jiān)聽(tīng)器和相應(yīng)的消息訂閱處理器綁定,該消息監(jiān)聽(tīng)器
* 通過(guò)反射技術(shù)調(diào)用消息訂閱處理器的相關(guān)方法進(jìn)行一些業(yè)務(wù)處理
* @param connectionFactory
* @param listenerAdapter
* @return
*/
@Bean
public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
//訂閱了一個(gè)叫redis.uncache的通道
container.addMessageListener(listenerAdapter, new PatternTopic("redis.uncache"));
//這個(gè)container 可以添加多個(gè) messageListener
return container;
}
/**
* 消息監(jiān)聽(tīng)器適配器,綁定消息處理器,利用反射技術(shù)調(diào)用消息處理器的業(yè)務(wù)方法
* @param receiver
* @return
*/
@Bean
MessageListenerAdapter listenerAdapter(MessageSubscriber receiver) {
//這個(gè)地方 是給messageListenerAdapter 傳入一個(gè)消息接受的處理器,利用反射的方法調(diào)用“handle”
return new MessageListenerAdapter(receiver, "handle");
}
}
消息發(fā)布類如下:
import com.xuanwu.apaas.core.multicache.CacheFactory;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class MessageSubscriber {
private static final Logger logger = LoggerFactory.getLogger(MessageSubscriber.class);
@Autowired
private CacheFactory cacheFactory;
/**
* 接收到redis訂閱的消息后,將ehcache的緩存失效
* @param message 格式為name_key
*/
public void handle(String message){
logger.debug("redis.ehcache:"+message);
if(StringUtils.isEmpty(message)) {
return;
}
String[] strs = message.split("#");
String name = strs[0];
String key = null;
if(strs.length == 2) {
key = strs[1];
}
cacheFactory.ehDel(name,key);
}
}
具體操作緩存的類如下:
import com.xuanwu.apaas.core.multicache.publisher.MessagePublisher;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.io.InputStream;
/**
* 多級(jí)緩存切面
* @author rongdi
*/
@Component
public class CacheFactory {
private static final Logger logger = LoggerFactory.getLogger(CacheFactory.class);
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private MessagePublisher messagePublisher;
private CacheManager cacheManager;
public CacheFactory() {
InputStream is = this.getClass().getResourceAsStream("/ehcache.xml");
if(is != null) {
cacheManager = CacheManager.create(is);
}
}
public void cacheDel(String name,String key) {
//刪除redis對(duì)應(yīng)的緩存
redisDel(name,key);
//刪除本地的ehcache緩存,可以不需要,訂閱器那里會(huì)刪除
// ehDel(name,key);
if(cacheManager != null) {
//發(fā)布一個(gè)消息,告訴訂閱的服務(wù)該緩存失效
messagePublisher.publish(name, key);
}
}
public String ehGet(String name,String key) {
if(cacheManager == null) return null;
Cache cache=cacheManager.getCache(name);
if(cache == null) return null;
cache.acquireReadLockOnKey(key);
try {
Element ele = cache.get(key);
if(ele == null) return null;
return (String)ele.getObjectValue();
} finally {
cache.releaseReadLockOnKey(key);
}
}
public String redisGet(String name,String key) {
HashOperations<String,String,String> oper = redisTemplate.opsForHash();
try {
return oper.get(name, key);
} catch(RedisConnectionFailureException e) {
//連接失敗,不拋錯(cuò),直接不用redis緩存了
logger.error("connect redis error ",e);
return null;
}
}
public void ehPut(String name,String key,String value) {
if(cacheManager == null) return;
if(!cacheManager.cacheExists(name)) {
cacheManager.addCache(name);
}
Cache cache=cacheManager.getCache(name);
//獲得key上的寫(xiě)鎖,不同key互相不影響,類似于synchronized(key.intern()){}
cache.acquireWriteLockOnKey(key);
try {
cache.put(new Element(key, value));
} finally {
//釋放寫(xiě)鎖
cache.releaseWriteLockOnKey(key);
}
}
public void redisPut(String name,String key,String value) {
HashOperations<String,String,String> oper = redisTemplate.opsForHash();
try {
oper.put(name, key, value);
} catch (RedisConnectionFailureException e) {
//連接失敗,不拋錯(cuò),直接不用redis緩存了
logger.error("connect redis error ",e);
}
}
public void ehDel(String name,String key) {
if(cacheManager == null) return;
Cache cache = cacheManager.getCache(name);
if(cache != null) {
//如果key為空,直接根據(jù)緩存名刪除
if(StringUtils.isEmpty(key)) {
cacheManager.removeCache(name);
} else {
cache.remove(key);
}
}
}
public void redisDel(String name,String key) {
HashOperations<String,String,String> oper = redisTemplate.opsForHash();
try {
//如果key為空,直接根據(jù)緩存名刪除
if(StringUtils.isEmpty(key)) {
redisTemplate.delete(name);
} else {
oper.delete(name,key);
}
} catch (RedisConnectionFailureException e) {
//連接失敗,不拋錯(cuò),直接不用redis緩存了
logger.error("connect redis error ",e);
}
}
}
工具類如下
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.*;
public class JsonUtil {
private static ObjectMapper mapper;
static {
mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
false);
}
/**
* 將對(duì)象序列化成json
*
* @param obj 待序列化的對(duì)象
* @return
* @throws Exception
*/
public static String serialize(Object obj) throws Exception {
if (obj == null) {
throw new IllegalArgumentException("obj should not be null");
}
return mapper.writeValueAsString(obj);
}
/**
帶泛型的反序列化,比如一個(gè)JSONArray反序列化成List<User>
*/
public static <T> T deserialize(String jsonStr, Class<?> collectionClass,
Class<?>... elementClasses) throws Exception {
JavaType javaType = mapper.getTypeFactory().constructParametrizedType(
collectionClass, collectionClass, elementClasses);
return mapper.readValue(jsonStr, javaType);
}
/**
* 將json字符串反序列化成對(duì)象
* @param src 待反序列化的json字符串
* @param t 反序列化成為的對(duì)象的class類型
* @return
* @throws Exception
*/
public static <T> T deserialize(String src, Class<T> t) throws Exception {
if (src == null) {
throw new IllegalArgumentException("src should not be null");
}
if("{}".equals(src.trim())) {
return null;
}
return mapper.readValue(src, t);
}
}
具體使用緩存,和之前一樣只需要關(guān)注@Cacheable和@CacheEvict注解,同樣也支持spring的el表達(dá)式。而且這里的value屬性表示的緩存名稱也沒(méi)有上面說(shuō)的那個(gè)問(wèn)題,完全可以用value隔離不同的緩存,例子如下
@Cacheable(value = "bo",key="#session.productVersionCode+''+#session.tenantCode+''+#objectcode")
@CacheEvict(value = "bo",key="#session.productVersionCode+''+#session.tenantCode+''+#objectcode")
附上主要的依賴包
"org.springframework.boot:spring-boot-starter-redis:1.4.2.RELEASE",
'net.sf.ehcache:ehcache:2.10.4',
"org.json:json:20160810"
以上是“springboot中如何自定義兩級(jí)緩存”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對(duì)大家有幫助,更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。