SpringBoot(10-Cache缓存使用) | 总字数: 5.1k | 阅读时长: 22分钟 | 浏览量: | 
SpringBoot 整合 Cache 
注解及方法 
名称 
解释 
 
 
Cache 
缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache 等 
 
CacheManager 
缓存管理器,管理各种缓存(cache)组件,用于指定使用哪个缓存管理器,非必需,只有当有多个时才需要使用 
 
@Cacheable 
主要针对方法配置,能够根据方法的请求参数对其进行缓存 
 
@CacheEvict 
清空缓存 
 
@CachePut 
保证方法被调用,又希望结果被缓存。 与@Cacheable 区别在于是否每次都调用方法,常用于更新 
 
@EnableCaching 
开启基于注解的缓存,用于指定 key 生成器,非必需。若需要指定一个自定义的 key 生成器,我们需要去实现 org.springframework.cache.interceptor.KeyGenerator 接口,并使用该参数来指定。需要注意的是:该参数与 key 是互斥的 
 
keyGenerator 
缓存数据时 key 生成策略 
 
serialize 
缓存数据时 value 序列化策略 
 
@CacheConfig 
统一配置本类的缓存注解的属性 
 
 
Cache 
缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache 等;
具体意义:每一个 Cache 中存储一个缓存信息,比如说:用户信息缓存(user_cache)、商品信息缓存(product_cache)…,然后每一个 cache 中又会用具体的缓存 key 做具体区分,如:用户信息缓存中,根据用户 id 区分,那么用户 id 就是 user_cache 中的 key;product_cache 中又以商品 id 作为 key 区分具体缓存记录;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 public  interface  Cache  {    String getName () ;     Object getNativeCache () ;     @Nullable      ValueWrapper get (Object key) ;     @Nullable      <T> T get (Object key, @Nullable  Class<T> type) ;     @Nullable      <T> T get (Object key, Callable<T> valueLoader) ;     void  put (Object key, @Nullable  Object value) ;     @Nullable      default  ValueWrapper putIfAbsent (Object key, @Nullable  Object value)  {         ValueWrapper  existingValue  =  get(key);         if  (existingValue == null ) {             put(key, value);         }         return  existingValue;     }     void  evict (Object key) ;     default  boolean  evictIfPresent (Object key)  {         evict(key);         return  false ;     }     void  clear () ;     default  boolean  invalidate ()  {         clear();         return  false ;     }     @FunctionalInterface      interface  ValueWrapper  {         @Nullable          Object get () ;     }     @SuppressWarnings("serial")      class  ValueRetrievalException  extends  RuntimeException  {         @Nullable          private  final  Object key;         public  ValueRetrievalException (@Nullable  Object key, Callable<?> loader, Throwable ex)  {             super (String.format("Value for key '%s' could not be loaded using '%s'" , key, loader), ex);             this .key = key;         }         @Nullable          public  Object getKey ()  {             return  this .key;         }     } } 
CacheManager 
缓存管理器,管理各个缓存(Cache)组件,针对不同的业务场景,可以定义多个不同的 CacheManager 管理具体 cache 数据;
比如说:我们可以通过 CacheManager 加载各个 cache,并且可以初始化各个 cache 的参数设置(如过期时间等),我们缓存数据需要指定 cache manager 跟具体被其管理的 cache,这样才可以正确缓存
1 2 3 4 5 6 7 8 public  interface  CacheManager  {           Cache getCache (String name) ;               Collection<String> getCacheNames () ; } 
@EnableCaching 
开启基于注解的缓存,属于 spring4.x 之后的 spring 缓存注解;
@CacheConfig 
在类上使用@CacheConfig 统一的配置缓存的信息,包括指定 cacheManager、具体 cache 等;
1 2 3 4 5 6 7 8 9 10 11 12 13 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public  @interface  CacheConfig {    String[] cacheNames() default  {};     String keyGenerator ()  default  "" ;     String cacheManager ()  default  "" ;     String cacheResolver ()  default  "" ; } 
@Cacheable 
主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,具体缓存的 key 可以指定;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public  @interface  Cacheable {         @AliasFor("cacheNames")      String[] value() default  {};          @AliasFor("value")      String[] cacheNames() default  {};               String key ()  default  "" ;               String keyGenerator ()  default  "" ;               String cacheManager ()  default  "" ;               String cacheResolver ()  default  "" ;               String condition ()  default  "" ;                String unless ()  default  "" ;               boolean  sync ()  default  false ; } 
@CachePut 
保证方法调用,又能将结果缓存,可以用于刷新缓存,同样 key 需要指定,需要跟@Cacheable 中制定的 key 对应保持一致,同样是将方法返回结果进行保存,所以同样需要跟@Cacheable 方法中的结果返回类型一致;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public  @interface  CachePut {    @AliasFor("cacheNames")      String[] value() default  {};     @AliasFor("value")      String[] cacheNames() default  {};     String key ()  default  "" ;     String keyGenerator ()  default  "" ;     String cacheManager ()  default  "" ;     String cacheResolver ()  default  "" ;     String condition ()  default  "" ;     String unless ()  default  "" ; } 
@CacheEvict 
针对指定的 key,清空缓存;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public  @interface  CacheEvict {         @AliasFor("cacheNames")      String[] value() default  {};     @AliasFor("value")      String[] cacheNames() default  {};     String key ()  default  "" ;     String keyGenerator ()  default  "" ;     String cacheManager ()  default  "" ;     String cacheResolver ()  default  "" ;               boolean  allEntries ()  default  false ;          boolean  beforeInvocation ()  default  false ; } 
参数信息 
@Cacheable/@CachePut/@CacheEvict 主要的参数 
名称 
解释 
 
 
value 
缓存的名称,在 spring 配置文件中定义,必须指定至少一个 例如: @Cacheable(value =”mycache”) 或者 @Cacheable(value ={”cache1”,”cache2”}) 
 
key 
缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合,例如:@Cacheable(value =”testcache”, key =”#id”) 
 
condition 
缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存/清除缓存,例如:@Cacheable(value =”testcache”, condition =”#userName.length()> 2”) 
 
unless 
否定缓存,当条件结果为 TRUE 时,就不会缓存。 @Cacheable(value =”testcache”, unless =”#userName.length()> 2”) 
 
allEntries (@CacheEvict ) 
是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存,例如:@CachEvict(value =”testcache”, allEntries = true) 
 
beforeInvocation (@CacheEvict) 
是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存,例如: @CachEvict(value =”testcache”,beforeInvocation = true) 
 
 
SpEl 上下文数据 
Spring Cache 提供了一些供我们使用的 SpEL 上下文数据,下表直接摘自 Spring 官方文档:
名称 
位置 
描述 
示例 
 
 
methodName 
root 对象 
当前被调用的方法名 
#root.methodname 
method 
root 对象 
当前被调用的方法 
#root.method.name 
target 
root 对象 
当前被调用的目标对象实例 
#root.target 
targetClass 
root 对象 
当前被调用的目标对象的类 
#root.targetClass 
args 
root 对象 
当前被调用的方法的参数列表 
#root.args[0] 
caches 
root 对象 
当前方法调用使用的缓存列表 
#root.caches[0].name 
Argument Name 
执行上下文 
当前被调用的方法的参数,如 findArtisan(Artisan artisan), 可以通过#artsian.id  获得参数 
#artsian.id 
result 
执行上下文 
方法执行后的返回值(仅当方法执行后的判断有效,如 unless cacheEvict 的 beforeInvocation = false) 
#result 
 
要使用 root 对象的属性作为 key 时我们也可以将“#root”省略,因为 Spring 默认使用的就是 root 对象的属性,如:@Cacheable(key = “targetClass+methodName+#p0”) 
使用方法参数时我们可以直接使用“#参数名”或者“#p 参数 index”,如:@Cacheable(value = “users”, key = “#id”),@Cacheable(value = “user”, key = “#p0”) 
 
CacheManger 缓存类型 
针对不同的缓存技术,需要实现不同的 cacheManager,Spring 定义了如下的 cacheManger 实现。
CacheManger 
描述 
 
 
SimpleCacheManager 
使用简单的 Collection 来存储缓存,主要用于测试 
 
ConcurrentMapCacheManager 
使用 ConcurrentMap 作为缓存技术(默认),需要显式的删除缓存,无过期机制 
 
NoOpCacheManager 
仅测试用途,不会实际存储缓存 
 
EhCacheCacheManager 
使用 EhCache 作为缓存技术,以前在 hibernate 的时候经常用 
 
GuavaCacheManager 
使用 google guava 的 GuavaCache 作为缓存技术(1.5 版本已不建议使用) 
 
CaffeineCacheManager 
是使用 Java8 对 Guava 缓存的重写,spring5(springboot2)开始用 Caffeine 取代 guava 
 
HazelcastCacheManager 
使用 Hazelcast 作为缓存技术 
 
JCacheCacheManager 
使用 JCache 标准的实现作为缓存技术,如 Apache Commons JCS 
 
RedisCacheManager 
使用 Redis 作为缓存技术 
 
 
常规的 SpringBoot 已经为我们自动配置了 EhCache、Collection、Guava、ConcurrentMap 等缓存,默认使用 ConcurrentMapCacheManager。SpringBoot 的 application.properties 配置文件,使用 spring.cache 前缀的属性进行配置。
ConcurrentMap Cache 
Spring boot 默认使用的是 SimpleCacheConfiguration,即使用 ConcurrentMapCacheManager 来实现缓存,ConcurrentMapCache 实质是一个 ConcurrentHashMap 集合对象 java 内置,所以无需引入其他依赖,也没有额外的配置
ConcurrentMapCache 的自动装配声明在 SimpleCacheConfiguration 中,如果需要也可对它进行额外的装配
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(CacheManager.class) @Conditional(CacheCondition.class) class  SimpleCacheConfiguration  {    @Bean      ConcurrentMapCacheManager cacheManager (CacheProperties cacheProperties,              CacheManagerCustomizers cacheManagerCustomizers)  {        ConcurrentMapCacheManager  cacheManager  =  new  ConcurrentMapCacheManager ();         List<String> cacheNames = cacheProperties.getCacheNames();         if  (!cacheNames.isEmpty()) {             cacheManager.setCacheNames(cacheNames);         }         return  cacheManagerCustomizers.customize(cacheManager);     } } 
application.properties 配置 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 spring.cache.type =spring.cache.cache-names = spring.cache.ehcache.config =spring.cache.infinispan.config = spring.cache.jcache.config = spring.cache.jcache.provider =spring.caffeine.spec = 
基本使用 
1 2 3 4 <dependency >   <groupId > org.springframework.boot</groupId >    <artifactId > spring-boot-starter-cache</artifactId >  </dependency > 
1 2 spring.cache.type =simple 
1 2 3 4 5 6 7 @SpringBootApplication @EnableCaching   public  class  DemoApplication {    public  static  void  main (String[] args)  {         SpringApplication.run(DemoApplication.class, args);     } } 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 @Service @CacheConfig(cacheNames = "user") public  class  UserServiceImpl  implements  UserService  {    @Autowired      private  UserMapper userMapper;          @CachePut(key = "#user.id.toString()",value = "user")      @Override      public  void  add (User user) {         userMapper.insert(user);     }          @Cacheable(key = "#id.toString()",value = "user")      @Override      public  User findById (Long id) {         User  user  =  userMapper.selectById(id);         return  user;     }          @CacheEvict(key = "#id.toString()",value = "user")      @Override      public  void  deleteById (Long id) {         userMapper.deleteById(id);     } } 
不足:多是根据请求参数命名 key,根据返回值设置 value,在很多情况下,想在方法内部进行命名和操作有一定的限制
坑点 
整合 redis 实现 cache 方法后,在使用@Cacheable 缓存对象为空时会报错
解决方法:
设置结果为空时不缓存,直接加上 unless ="#result == null " 就好了 
 
1 2 3 4 @Cacheable(cacheNames = "cache:getCustRange", key = "#root.args[0]['custId'] + ''", unless="#result == null") public  CustRangeVo getCustRange (Map<String, Object> map)  {    return  custMapper.getCustRange(map); } 
设置允许缓存为 null 值 
 
只要把 RedisCacheConfiguration 的 cacheNullValues 设置为 true,就可以缓存 null 值了
SpringBoot 整合 EHCache、Cache 
EhCache 是一个纯 Java 的进程内缓存框架,具有快速、精干等特点,是 Hibernate 中默认 CacheProvider
Ehcache 是一种广泛使用的开源 Java 分布式缓存,主要面向通用缓存,Java EE 和轻量级容器。它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个 gzip 缓存 servlet 过滤器,支持 REST 和 SOAP api 等特点。
1 2 3 4 <dependency >     <groupId > net.sf.ehcache</groupId >      <artifactId > ehcache</artifactId >  </dependency > 
1 2 3 4 spring.cache.type =ehcache  spring.cache.ehcache.config =classpath:/ehcache.xml  
EhCache 的配置文件 ehcache.xml 只需要放到类路径下面,SpringBoot 会自动扫描
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <ehcache >          <diskStore  path ="java.io.tmpdir"  />           <defaultCache  maxElementsInMemory ="10000"  eternal ="false"                     timeToIdleSeconds ="600"  timeToLiveSeconds ="600"  overflowToDisk ="true"  />          <cache  name ="myCache"  maxElementsInMemory ="10000"  eternal ="false"                     timeToIdleSeconds ="120"  timeToLiveSeconds ="600"  overflowToDisk ="true"  /> </ehcache > 
SpringBoot 会为我们自动配置 EhCacheCacheManager  这个 Bean,如果想自定义设置一些个性化参数时,通过 Java Config 形式配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Configuration   @EnableCaching public  class  CacheConfig  {         @Bean        public  CacheManager cacheManager ()  {           return  new  EhCacheCacheManager (ehCacheCacheManager().getObject());       }          @Bean        public  EhCacheManagerFactoryBean ehCacheCacheManager ()  {           EhCacheManagerFactoryBean  cmfb  =  new  EhCacheManagerFactoryBean ();           cmfb.setConfigLocation(new  ClassPathResource ("ehcache.xml" ));           cmfb.setShared(true );           return  cmfb;       }   } 
SpringBoot 整合 Redis、Cache 
基本使用 
1 2 3 4 5 6 7 8 <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 > 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 spring:   datasource:      url:  jdbc:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai      driver-class-name:  com.mysql.cj.jdbc.Driver      username:  root      password:  123456    redis:      database:  0      port:  6379      lettuce:        pool:          max-active:  20          max-wait:  -1          max-idle:  10          min-idle:  0      timeout:  1000      host:  localhost  mybatis-plus:   configuration:      log-impl:  org.apache.ibatis.logging.stdout.StdOutImpl  
1 2 3 4 5 6 7 8 @SpringBootApplication @MapperScan("com.example.springboot_cache_redis.dao") @EnableCaching public  class  SpringbootCacheRedisApplication  {    public  static  void  main (String[] args)  {         SpringApplication.run(SpringbootCacheRedisApplication.class, args);     } } 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 @Configuration public  class  RedisConfig  {    @Bean      public  RedisTemplate<String,Object> redisTemplate (RedisConnectionFactory factory) {         RedisTemplate<String,Object> redisTemplate=new  RedisTemplate <>();         redisTemplate.setConnectionFactory(factory);         StringRedisSerializer  stringRedisSerializer  =  new  StringRedisSerializer ();         GenericJackson2JsonRedisSerializer  genericJackson2JsonRedisSerializer  =  new  GenericJackson2JsonRedisSerializer ();         redisTemplate.setKeySerializer(stringRedisSerializer);         redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);         redisTemplate.setHashKeySerializer(stringRedisSerializer);         redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);         return   redisTemplate;     }     @Bean      public  CacheManager cacheManager (RedisTemplate<String,Object> redisTemplate) {         RedisCacheConfiguration redisCacheConfiguration=                 RedisCacheConfiguration.defaultCacheConfig()                                                  .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getStringSerializer()))                                                  .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()))                                                  .disableCachingNullValues()                                                  .entryTtl(Duration.ofHours(1 ));         RedisCacheManager redisCacheManager=                 RedisCacheManager.RedisCacheManagerBuilder                                                  .fromConnectionFactory(redisTemplate.getConnectionFactory())                                                  .cacheDefaults(redisCacheConfiguration)                                                  .transactionAware()                         .build();         return  redisCacheManager;     } } 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Service @CacheConfig(cacheNames = "user") public  class  UserServiceImpl  implements  UserService  {    @Autowired      private  UserMapper userMapper;     @Override      @CachePut(key = "#user.id.toString()")      public  void  add (User user) {         userMapper.insert(user);     }     @Override      @Cacheable(key = "#id.toString()")      public  User selectById (Long id) {         User  user  =  userMapper.selectById(id);         return  user;     }     @Override      @CacheEvict(key = "#id.toString()")      public  void  deleteById (Long id) {         userMapper.deleteById(id);     } } 
自定义 CacheManager 
为什么要配置自定义 CacheManager? 
当导入 Redis 后,SpringBoot 不再使用默认的 SimpleCacheManager,因为 Redis 的配置类,会提前注入 redisCacheManager,能够缓存,但是使用的是 JDK 内置的序列化器,可视化很差
为了能让 redis 使用我们指定的序列化器,必须自定义 cacheManager
SpringBoot 整合 Redis、Caffeine 
基本使用 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <dependency >     <groupId > com.github.ben-manes.caffeine</groupId >      <artifactId > caffeine</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 > 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 spring:   datasource:      url:  jdbc:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai      driver-class-name:  com.mysql.cj.jdbc.Driver      username:  root      password:  123456    redis:      database:  0      port:  6379      lettuce:        pool:          max-active:  20          max-wait:  -1          max-idle:  10          min-idle:  0      timeout:  1000      host:  localhost  mybatis-plus:   configuration:      log-impl:  org.apache.ibatis.logging.stdout.StdOutImpl  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Configuration public  class  LocalCacheConfiguration  {      @Bean("localCacheManager")      public  Cache<String, Object> localCacheManager ()  {         return  Caffeine.newBuilder()                                  .expireAfterWrite(5 , TimeUnit.SECONDS)                                  .initialCapacity(50 )                                  .maximumSize(500 )             	                 .recordStats()                 .build();     } } 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Configuration public  class  RedisConfig  {    @Bean      public  RedisTemplate<String,Object> redisTemplate (RedisConnectionFactory factory) {         RedisTemplate<String,Object> redisTemplate=new  RedisTemplate <>();         redisTemplate.setConnectionFactory(factory);         StringRedisSerializer  stringRedisSerializer  =  new  StringRedisSerializer ();         GenericJackson2JsonRedisSerializer  genericJackson2JsonRedisSerializer  =  new  GenericJackson2JsonRedisSerializer ();         redisTemplate.setKeySerializer(stringRedisSerializer);         redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);         redisTemplate.setHashKeySerializer(stringRedisSerializer);         redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);         return   redisTemplate;     } } 
1 2 3 4 5 6 7 8 9 public  interface  UserService  {    void  add (User user) ;       User getById (String id) ;       User update (User user) ;       void  deleteById (String id) ; } 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 @Service public  class  UserServiceImpl  implements  UserService  {         private  static  HashMap<String, User> userMap = new  HashMap <>();     private  final  RedisTemplate<String, Object> redisTemplate;     private  final  Cache<String, Object> caffeineCache;       @Autowired      public  UserServiceImpl (RedisTemplate<String, Object> redisTemplate,                             @Qualifier("localCacheManager")  Cache<String, Object> caffeineCache)  {        this .redisTemplate = redisTemplate;         this .caffeineCache = caffeineCache;     }       static  {         userMap.put("1" , new  User ("1" , "zhangsan" ));         userMap.put("2" , new  User ("2" , "lisi" ));         userMap.put("3" , new  User ("3" , "wangwu" ));         userMap.put("4" , new  User ("4" , "zhaoliu" ));     }       @Override      public  void  add (User user)  {                  caffeineCache.put(user.getId(), user);                  redisTemplate.opsForValue().set(user.getId(), JSON.toJSONString(user), 20 , TimeUnit.SECONDS);                  userMap.put(user.getId(), user);     }       @Override      public  User getById (String id)  {                  Object  o  =  caffeineCache.getIfPresent(id);         if  (Objects.nonNull(o)) {             System.out.println("从Caffeine中查询到数据..." );             return  (User) o;         }                    String  jsonString  =  (String) redisTemplate.opsForValue().get(id);         User  user  =  JSON.parseObject(jsonString, User.class);         if  (Objects.nonNull(user)) {             System.out.println("从Redis中查询到数据..." );                          caffeineCache.put(user.getId(), user);             return  user;         }                    user = userMap.get(id);         if  (Objects.nonNull(user)) {                          caffeineCache.put(user.getId(), user);                          redisTemplate.opsForValue().set(user.getId(), JSON.toJSONString(user), 20 , TimeUnit.SECONDS);         }         System.out.println("从数据库中查询到数据..." );         return  user;     }       @Override      public  User update (User user)  {         User  oldUser  =  userMap.get(user.getId());         oldUser.setName(user.getName());                  userMap.put(oldUser.getId(), oldUser);                  caffeineCache.put(oldUser.getId(), oldUser);                  redisTemplate.opsForValue().set(oldUser.getId(), JSON.toJSONString(oldUser), 20 , TimeUnit.SECONDS);         return  oldUser;     }       @Override      public  void  deleteById (String id)  {                  userMap.remove(id);                  caffeineCache.invalidate(id);                  redisTemplate.delete(id);     } } 
自定义 CacheManager 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @Data @ConfigurationProperties("com.example.cache") public  class  CacheProperties  {    private  Map<String, CacheProperty> managers;     @Data      public  static  class  CacheProperty  {                  private  Integer initialCapacity;                  private  Integer maximumSize;                  private  Integer expireAfterAccess;                  private  List<String> cacheNames;     } } 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 @Slf4j @Configuration @EnableConfigurationProperties({CacheProperties.class}) public  class  CaffeineAutoConfiguration  implements  ApplicationContextAware  {    private  ApplicationContext context;     @Resource      private  CacheProperties cacheProperties;     @Override      public  void  setApplicationContext (@Nullable  ApplicationContext context)  throws  BeansException {         this .context = context;     }     @Bean      @Primary      public  CaffeineCacheManager caffeineCacheManager ()  {         return  new  CaffeineCacheManager ();     }     @PostConstruct      public  void  buildCacheManagers ()  {         Map<String, CacheProperties.CacheProperty> propertyMap = this .cacheProperties.getManagers();         if  (CollectionUtils.isEmpty(propertyMap)) {             log.info("未配置 CacheManager" );             return ;         }         DefaultListableBeanFactory  beanFactory  =  (DefaultListableBeanFactory) this .context.getAutowireCapableBeanFactory();         for  (Map.Entry<String, CacheProperties.CacheProperty> entry : propertyMap.entrySet()) {             BeanDefinitionBuilder  beanDefinitionBuilder  =  BeanDefinitionBuilder.genericBeanDefinition(CaffeineCacheManager.class);             beanDefinitionBuilder.addPropertyValue("cacheNames" , entry.getValue().getCacheNames());             Caffeine<Object, Object> caffeine = this .buildCaffeine(entry.getValue());             beanDefinitionBuilder.addPropertyValue("caffeine" , caffeine);             beanFactory.registerBeanDefinition(entry.getKey(), beanDefinitionBuilder.getBeanDefinition());         }     }     private  Caffeine<Object, Object> buildCaffeine (CacheProperties.CacheProperty property)  {         return  Caffeine.newBuilder()                 .initialCapacity(property.getInitialCapacity())                 .maximumSize((long ) property.getMaximumSize())                 .expireAfterAccess((long ) property.getExpireAfterAccess(), TimeUnit.SECONDS)                 .recordStats();     } }