spring-boot-2.0.3之redis缓存实现,不是你想的那样哦!
前言
开心一刻
小白问小明:“你前面有一个5米深的坑,里面没有水,如果你跳进去后该怎样出来了?”小明:“躺着出来呗,还能怎么出来?”小白:“为什么躺着出来?”小明:“5米深的坑,还没有水,跳下去不死就很幸运了,残是肯定会残的,不躺着出来,那能怎么出来?”小白:“假设没死也没残呢?”小明:“你当我超人了? 那也简单,把脑子里的水放出来就可以漂出来了。”小白:“你脑子里有这么多水吗?”小明:“我脑子里没那么多水我跳下去干嘛?”
路漫漫其修远兮,吾将上下而求索!
github:
码云(gitee):
springboot 1.x到2.x变动的内容还是挺多的,而2.x之间也存在细微的差别,本文不讲这些差别(具体差别我也不知道,汗......),只讲1.5.9与2.0.3的redis缓存配置的区别
springboot1.5.9缓存配置
工程实现
1.x系列配置应该都差不多,下面我们看看1.5.9中,springboot集成redis的缓存实现
pom.xml
<?xml version="1.0" encoding="utf-8"?> <project xmlns="http://maven.apache.org/pom/4.0.0" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance" xsi:schemalocation="http://maven.apache.org/pom/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelversion>4.0.0</modelversion> <groupid>com.lee</groupid> <artifactid>spring-boot159.cache</artifactid> <version>1.0-snapshot</version> <properties> <java.version>1.8</java.version> </properties> <parent> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-parent</artifactid> <version>1.5.9.release</version> </parent> <dependencies> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-web</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-data-redis</artifactid> </dependency> </dependencies> </project>
application.yml
server: port: 8888 spring: #redis配置 redis: database: 0 host: 127.0.0.1 port: 6379 password: # 连接超时时间(毫秒) timeout: 2000 pool: # 连接池最大连接数(使用负值表示没有限制) max-active: 8 # 连接池最大阻塞等待时间(使用负值表示没有限制) max-wait: -1 # 连接池中的最大空闲连接 max-idle: 8 # 连接池中的最小空闲连接 min-idle: 0 cache: type: redis cache: expire-time: 180
rediscacheconfig.java
package com.lee.cache.config; import com.fasterxml.jackson.annotation.jsonautodetect; import com.fasterxml.jackson.annotation.propertyaccessor; import com.fasterxml.jackson.databind.objectmapper; import org.springframework.beans.factory.annotation.value; import org.springframework.cache.cachemanager; import org.springframework.cache.annotation.cachingconfigurersupport; import org.springframework.cache.annotation.enablecaching; import org.springframework.cache.interceptor.keygenerator; 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.serializer.jackson2jsonredisserializer; /** * 必须继承cachingconfigurersupport,不然此类中生成的bean不会生效(没有替换掉默认生成的,只是一个普通的bean) * springboot默认生成的缓存管理器和redistemplate支持的类型很有限,根本不满足我们的需求,会抛出如下异常: * org.springframework.cache.interceptor.simplekey cannot be cast to java.lang.string */ @configuration @enablecaching public class rediscacheconfig extends cachingconfigurersupport { @value("${cache.expire-time:180}") private int expiretime; // 配置key生成器,作用于缓存管理器管理的所有缓存 // 如果缓存注解(@cacheable、@cacheevict等)中指定key属性,那么会覆盖此key生成器 @bean public keygenerator keygenerator() { return (target, method, params) -> { stringbuilder sb = new stringbuilder(); sb.append(target.getclass().getname()); sb.append(method.getname()); for (object obj : params) { sb.append(obj.tostring()); } return sb.tostring(); }; } // 缓存管理器管理的缓存都需要有对应的缓存空间,否则抛异常:no cache could be resolved for 'builder... @bean public cachemanager cachemanager(redistemplate redistemplate) { rediscachemanager rcm = new rediscachemanager(redistemplate); rcm.setdefaultexpiration(expiretime); //设置缓存管理器管理的缓存的过期时间, 单位:秒 return rcm; } @bean public redistemplate<string, string> redistemplate(redisconnectionfactory factory) { stringredistemplate template = new stringredistemplate(factory); jackson2jsonredisserializer jackson2jsonredisserializer = new jackson2jsonredisserializer(object.class); objectmapper om = new objectmapper(); om.setvisibility(propertyaccessor.all, jsonautodetect.visibility.any); om.enabledefaulttyping(objectmapper.defaulttyping.non_final); jackson2jsonredisserializer.setobjectmapper(om); template.setvalueserializer(jackson2jsonredisserializer); template.afterpropertiesset(); return template; } }
cacheserviceimpl.java
package com.lee.cache.service.impl; import com.lee.cache.model.user; import com.lee.cache.service.icacheservice; import org.springframework.beans.factory.annotation.autowired; import org.springframework.cache.annotation.cacheable; import org.springframework.data.redis.core.redistemplate; import org.springframework.stereotype.service; import org.springframework.util.stringutils; import java.util.arraylist; import java.util.list; /** * 若未配置@cacheconfig(cachenames = "hello"), 则@cacheable一定要配置value,相当于指定缓存空间 * 否则会抛异常:no cache could be resolved for 'builder... * * 若@cacheconfig(cachenames = "hello") 与 @cacheable(value = "123")都配置了, 则@cacheable(value = "123")生效 * * 当然@cacheconfig还有一些其他的配置项,cacheable也有一些其他的配置项 */ @service public class cacheserviceimpl implements icacheservice { @autowired private redistemplate<string, string> redistemplate; @override @cacheable(value = "test") // key用的自定义的keygenerator public string getname() { system.out.println("getname, no cache now..."); return "brucelee"; } @override @cacheable(value = "user", key = "methodname + '_' + #p0", unless = "#result.size() <= 0") // key会覆盖掉keygenerator public list<user> listuser(int pagenum, int pagesize) { system.out.println("listuser no cache now..."); list<user> users = new arraylist<>(); users.add(new user("zhengsan", 22)); users.add(new user("lisi", 20)); system.out.println("==========="); return users; } /** * 缓存不是缓存管理器管理,那么不受缓存管理器的约束 * 缓存管理器中的配置不适用与此 * 这里相当于我们平时直接通过redis-cli操作redis * @return */ @override public string getusername() { string username = redistemplate.opsforvalue().get("username"); if (!stringutils.isempty(username)) { return username; } system.out.println("getusername, no cache now..."); redistemplate.opsforvalue().set("username", "username"); return "username"; } }
上述只讲了几个主要的文件,更多详情请点
redis 怎样保存cache
大家一定要把工程仔细看一遍,不然下面出现的一些名称会让我们感觉不知从哪来的;
工程中的缓存分两种:缓存管理器管理的缓存(也就是一些列注解实现的缓存)、redistemplate操作的缓存
缓存管理器管理的缓存
会在redis中增加2条数据,一个是类型为 zset 的 缓存名~keys , 里面存放了该缓存所有的key, 另一个是对应的key,值为序列化后的json;缓存名~keys可以理解成缓存空间,与我们平时所说的具体的缓存是不一样的。另外对缓存管理器的一些设置(全局过期时间等)都会反映到缓存管理器管理的所有缓存上;上图中的http://localhost:8888/getname和http://localhost:8888/listuser?pagenum=1&pagesize=3对应的是缓存管理器管理的缓存。
redistemplate操作的缓存
会在redis中增加1条记录,key - value键值对,与我们通过redis-cli操作缓存一样;上图中的http://localhost:8888/getusername对应的是redistemplate操作的缓存。
spring2.0.3缓存配置
工程实现
pom.xml
<?xml version="1.0" encoding="utf-8"?> <project xmlns="http://maven.apache.org/pom/4.0.0" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance" xsi:schemalocation="http://maven.apache.org/pom/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelversion>4.0.0</modelversion> <groupid>com.lee</groupid> <artifactid>spring-boot-cache</artifactid> <version>1.0-snapshot</version> <parent> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-parent</artifactid> <version>2.0.3.release</version> </parent> <dependencies> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-web</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-data-redis</artifactid> </dependency> <dependency> <groupid>org.apache.commons</groupid> <artifactid>commons-pool2</artifactid> </dependency> <dependency> <groupid>org.apache.commons</groupid> <artifactid>commons-lang3</artifactid> </dependency> </dependencies> </project>
application.yml
server: port: 8889 spring: #redis配置 redis: database: 0 host: 127.0.0.1 port: 6379 password: lettuce: pool: # 接池最大连接数(使用负值表示没有限制) max-active: 8 # 连接池最大阻塞等待时间(使用负值表示没有限制) max-wait: -1ms # 连接池中的最小空闲连接 max-idle: 8 # 连接池中的最大空闲连接 min-idle: 0 # 连接超时时间 timeout: 2000ms cache: type: redis cache: test: expire-time: 180 name: test default: expire-time: 200
缓存定制:rediscachemanagerconfig.java
package com.lee.cache.config; import org.springframework.beans.factory.annotation.value; import org.springframework.cache.cachemanager; import org.springframework.context.annotation.bean; import org.springframework.context.annotation.configuration; import org.springframework.data.redis.cache.rediscacheconfiguration; import org.springframework.data.redis.cache.rediscachemanager; import org.springframework.data.redis.connection.redisconnectionfactory; import org.springframework.data.redis.serializer.genericjackson2jsonredisserializer; import org.springframework.data.redis.serializer.redisserializationcontext; import org.springframework.data.redis.serializer.stringredisserializer; import java.time.duration; import java.util.hashmap; import java.util.hashset; import java.util.map; import java.util.set; /** * 进行缓存管理的定制 * 可以不配置,采用springboot默认的也行 */ @configuration public class rediscachemanagerconfig { @value("${cache.default.expire-time:1800}") private int defaultexpiretime; @value("${cache.test.expire-time:180}") private int testexpiretime; @value("${cache.test.name:test}") private string testcachename; //缓存管理器 @bean public cachemanager cachemanager(redisconnectionfactory lettuceconnectionfactory) { rediscacheconfiguration defaultcacheconfig = rediscacheconfiguration.defaultcacheconfig(); // 设置缓存管理器管理的缓存的默认过期时间 defaultcacheconfig = defaultcacheconfig.entryttl(duration.ofseconds(defaultexpiretime)) // 设置 key为string序列化 .serializekeyswith(redisserializationcontext.serializationpair.fromserializer(new stringredisserializer())) // 设置value为json序列化 .serializevalueswith(redisserializationcontext.serializationpair.fromserializer(new genericjackson2jsonredisserializer())) // 不缓存空值 .disablecachingnullvalues(); set<string> cachenames = new hashset<>(); cachenames.add(testcachename); // 对每个缓存空间应用不同的配置 map<string, rediscacheconfiguration> configmap = new hashmap<>(); configmap.put(testcachename, defaultcacheconfig.entryttl(duration.ofseconds(testexpiretime))); rediscachemanager cachemanager = rediscachemanager.builder(lettuceconnectionfactory) .cachedefaults(defaultcacheconfig) .initialcachenames(cachenames) .withinitialcacheconfigurations(configmap) .build(); return cachemanager; } }
此类可不用配置,就用spring-boot自动配置的缓存管理器也行,只是在缓存的可阅读性上会差一些。有兴趣的朋友可以删除此类试试。
cacheserviceimpl.java
package com.lee.cache.service.impl; import com.lee.cache.model.user; import com.lee.cache.service.icacheservice; import org.apache.commons.lang3.stringutils; import org.springframework.beans.factory.annotation.autowired; import org.springframework.cache.annotation.cacheconfig; import org.springframework.cache.annotation.cacheable; import org.springframework.data.redis.core.redistemplate; import org.springframework.stereotype.service; import java.util.arraylist; import java.util.list; /** * 若未配置@cacheconfig(cachenames = "hello"), 则@cacheable一定要配置value * 若@cacheconfig(cachenames = "hello") 与 @cacheable(value = "123")都配置了, 则@cacheable(value = "123") 生效 * * 当然@cacheconfig还有一些其他的配置项,cacheable也有一些其他的配置项 */ @service public class cacheserviceimpl implements icacheservice { @autowired private redistemplate<string, string> redistemplate; @override @cacheable(value = "test", key = "targetclass + '_' + methodname") public string getname() { system.out.println("getname, no cache now..."); return "brucelee"; } @override @cacheable(value = "user", key = "targetclass + ':' + methodname + '_' + #p0", unless = "#result.size() <= 0") public list<user> listuser(int pagenum, int pagesize) { system.out.println("listuser no cache now..."); list<user> users = new arraylist<>(); users.add(new user("zhengsan", 22)); users.add(new user("lisi", 20)); return users; } /** * 缓存不是缓存管理器管理,那么不受缓存管理器的约束 * 缓存管理器中的配置不适用与此 * 这里相当于我们平时直接通过redis-cli操作redis * @return */ @override public string getusername() { string username = redistemplate.opsforvalue().get("username"); if (stringutils.isnotempty(username)) { return username; } system.out.println("getusername, no cache now..."); redistemplate.opsforvalue().set("username", "username"); return "username"; } }
更多详情请点
redis 怎样保存cache
我们来看图说话,看看缓存在redis中是如何保存的
工程中的缓存分两种:缓存管理器管理的缓存(也就是一些列注解实现的缓存)、redistemplate操作的缓存
缓存管理器管理的缓存
会在redis中增加1条数据,key是以缓存空间开头的字符串(缓存空间名::缓存key),值为序列化后的json;上图中的http://localhost:8889/getname和http://localhost:8889/listuser?pagenum=1&pagesize=3对应的是缓存管理器管理的缓存。
redistemplate操作的缓存
会在redis中增加1条记录,key - value键值对,与我们通过redis-cli操作缓存一样;上图中的http://localhost:8889/getusername对应的是redistemplate操作的缓存。
总结
1、有时候我们引入spring-boot-starter-cache这个starter只是为了快速添加缓存依赖,目的是引入spring-context-support;如果我们的应用中中已经有了spring-context-support,那么我们无需再引入spring-boot-starter-cache,例如我们的应用中依赖了spring-boot-starter-web,而spring-boot-starter-web中又有spring-context-support依赖,所以我们无需再引入spring-boot-starter-cache。
2、supported cache providers,讲了支持的缓存类型以及默认情况下的缓存加载方式,可以通读下。
3、只要我们引入了redis依赖,并将redis的连接信息配置正确,springboot(2.0.3)根据我们的配置会给我们生成默认的缓存管理器和redistemplate;我们也可以自定义我们自己的缓存管理器来替换掉默认的,只要我们自定义了缓存管理器和redistemplate,那么springboot的默认生成的会替换成我们自定义的。
4、缓存管理器对缓存的操作也是通过redistemplate实现的,只是进行了统一的管理,并且能够减少我么的代码量,我们可以将更多的精力放到业务处理上。
5、redis-cli -h 127.0.0.1 -p 6379 -a 123456与redis-cli -a 123456两种方式访问到的数据完全不一致,好像操作不同的库一样! 这个需要注意,有空我回头看看这两者到底有啥区别,有知道的朋友可以留个言。
最后缅怀一下:金庸走了,再无江湖;ig捧杯了,再无lol!感谢ig成就了我的完美谢幕,让我的青春少了一份遗憾,谢谢!
参考
上一篇: 曹操杀华佗的真相:曹操与华佗的恩怨
下一篇: 六款补肾养颜粥 女性不会担心变“黄脸婆”