SpringCache入门

Spring 3.1 配置cache详解 spring的cache方案需要相同参数调用同一个方法在数据一致的情况下返回结果也应该是一致的。 使用spring cache只需要完成两部分: ·缓存声明:在方法上加上相应缓存注解和相应策略 ·configuration:定义缓存位置和具体保存策略 (spring cache并不是完全由spring提供,和transaction一样,只是对第三方框架进行上层封装)。 第一分部:缓存声明 阅读详情

1. 简单介绍

Spring Cache 是 Spring 提供的一整套的缓存解决方案。虽然它本身并没有提供缓存的实现,但是它提供了一整套的接口和代码规范、配置、注解等,这样它就可以整合各种缓存方案了,比如 Redis、Ehcache,我们也就不用关心操作缓存的细节。

Spring 3.1 开始定义了 org.springframework.cache.Cache 和 org.springframework.cache.CacheManager 接口,位于org.springframework:spring-context包下,来统一不同的缓存技术,并支持使用注解来简化我们开发。

1.png

Cache 接口它包含了缓存的各种操作方式,同时还提供了各种xxxCache缓存的实现,比如 RedisCache 针对Redis,EhCacheCache 针对 EhCache,ConcurrentMapCache 针对 ConCurrentMap等等…

使用SpringCache可以在调用方法的时候,通过检查对应指定参数是否已经被调用过?

  • 如果之前就被调用过,则就会从缓存中取出之前调用的结果
  • 如果之前没有被调用过,则调用该方法,并缓存对应的结果,下一次直接从缓存中取结果

SpringCache主要提供了以下几个注解帮助我们完成缓存的各种操作:

注解名称注解描述
@Cacheable在调用方法之前,首先去缓存中获取方法的方法值,如果可以找到,则返回缓存中的值,否则就执行该方法,并将返回值放到缓存中去。
@CachePut在方法调用之前不会去缓存中找,而选择直接执行方法,并将执行后的方法返回值放到缓存中去
@Caching能够同时应用多个缓存注解
@CacheEvict清理缓存中的一个或者多个记录
@CacheConfig在类级别共享相同的缓存的配置

2. 快速使用

根据SpringBoot的版本自定义选择使用在pom文件引入相关依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

在application.yml文件中配置选择的缓存类型,其中,SpringCache支持多种缓存中间件作为框架的缓存,如下图所示:

2.png

其中,依次分别为:

  • cache2K:SpringBoot2.7新增加的本地缓存解决方案
  • caffine:Caffeine 是一种高性能的缓存库,基于 Google Guava
  • couchbase:CouchBase是一款非关系型JSON文档数据库
  • ehcache:
  • generic:由泛型机制和 static 组合实现的泛型缓存机制
  • hazelcast:一个高度可扩展的数据分发和集群平台,可用于实现分布式数据存储、数据缓存
  • infinispan:分布式的集群缓存系统
  • jcache:JCache 作为缓存,它是 JSR107 规范中提到的缓存规范
  • none:没有缓存
  • redis:使用redis作为缓存
  • simple:内存作为缓存

这里我们选择熟悉的Redis作为缓存:

spring:
  cache:
    type: redis

同时,注意在pom引入redis的对应依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

然后,在application.yml文件中配置redis相关参数:

spring:
  redis:
    host: xxx.xxx.xxx.xxx
    port: 6379
    password: xxxxxx

最后,我们需要在启动类启用SpringCache:

@SpringBootApplication
@EnableCaching
public class CacheDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(CacheDemoApplication.class, args);
    }

}

上面配置以后,我们就可以使用SpringCache进行缓存的获取/更新/删除等操作了,下面通过一个简单的案例来测试一下SpringCache的使用:

简单定义一个/test/cache/get接口:

@GetMapping("/cache/get")
public String getCache() {
  return testService.getCache();
}

定义对应的Service,使用@Cacheable注解,定义缓存名称为“test”

@Cacheable({"test"})
public String getCache() {
  return "Cache Test Data";
}

此时,第一次调用该接口的时候,由于缓存中不存在,会直接执行该方法,然后将执行结果放到redis缓存中去,通过postman调用该接口发现结果是我们想要的“Cache Test Data”:

3.png

同时,检查Redis的缓存数据,发现确实增加了对应的缓存数据,其中缓存的key为“test::SimpleKey []”:

4.png

接下来,我们再一次调用该接口,由于此时Redis缓存中已经存在该Key了,就会直接从缓存中获取对应值了。这里可以通过在getCache方法中打断点,debug走一遍,就会发现走的是缓存。这里我们创建另外一个接口,同样使用该缓存key,但是方法本身返回的内容为“Cache Test02 Data”,调用该接口,会发现返回给我们的是缓存中的值,而并非方法的返回值,也就证明了确实走了缓存这一结论。

@GetMapping("/cache/get2")
public String getCache02() {
  return testService.getCache02();
}

@Cacheable({"test"})
public String getCache02() {
  return "Cache Test02 Data";
}

5.png

然后,我们使用@CacheEvict进行缓存的删除操作:

@DeleteMapping("/cache/delete/key")
public String deleteCacheKey() {
  testService.deleteCacheKey();
  return "delete CacheKey success";
}

@CacheEvict({"test"})
public void deleteCacheKey() {
  System.out.println("删除缓存");
}

执行该请求,发现Redis的缓存被删除干净…

总结:

我们会发现,当我们没有指定参数的话,默认我们缓存的后缀名为SimpleKey[]。缓存的value值默认使用jdk序列化机制,将序列化后的数据存到redis。同时缓存过期时间默认value为-1,即永不过期。

当然我们可以自定义参数来设置缓存的key值,同时也可以设置缓存的过期时间等,在高级使用进行讲解。

3. 高级使用

3.1 自定义配置Config

上面的简单快速上手使用的是SpringCache默认的配置,当然我们可以自定义配置:

首先,在application.yml文件中配置对应的自定义信息:

#SpringCache自定义配置
cache:
  redis:
    #缓存key前缀
    keyPrefix: ${spring.application.name}
    #是否开启缓存控制,防止缓存穿透
    cacheNullValues: true
    #缓存过期时间:3600s
    timeToLive: 3600000
    #是否使用key前缀
    useKeyPrefix: true

并创建对应的Properties类:

@Component
@Data
@ConfigurationProperties(prefix = "cache")
public class CacheProperties {
    //redis缓存配置信息
    private Redis redis;
    @Data
    public static class Redis {
        private String keyPrefix;
        private Boolean cacheNullValues;
        private Long timeToLive;
        private Boolean useKeyPrefix;
    }
}

然后,定义SpringCache的配置类,这里直接将启动Cache的注解改标注在该类上:

@Configuration
@EnableConfigurationProperties(CacheProperties.class)
@EnableCaching
public class MyCacheConfig {

    @Resource
    private CacheProperties cacheProperties;

    @Bean
    public RedisCacheConfiguration redisCacheConfiguration() {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
        config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
        CacheProperties.Redis redisProperties = cacheProperties.getRedis();
        if (!redisProperties.getUseKeyPrefix()) {
            config = config.disableKeyPrefix();
        }
        if (redisProperties.getKeyPrefix() != null) {
            config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());
        }
        if (!redisProperties.getCacheNullValues()) {
            config = config.disableCachingNullValues();
        }
        if (redisProperties.getTimeToLive() != null) {
            config = config.entryTtl(Duration.ofMillis(redisProperties.getTimeToLive()));
        }
        return config;
    }
}

此时,我们再执行上面的接口方法,发现此时SpringCache的配置已经对应发生改变了:

6.png

3.2 自定义key&keyGenerator

首先我们通过一个例子了解一下key,在上面的案例当中,虽然我们配置了Key的前缀,但是发现上面的后缀SimpleKey[]依旧存在,这是由于其使用默认的key生成策略,这里我们先通过手动配置,自定义指定key的值。配置需要使用SpEL表达式。我们在Service层方法上指定对应的缓存key:

@Cacheable(value = {"test"}, key = "#root.method.name")

再测试发现,redis中的key发生了对应的变化,此时的key为对应的方法名:

7.png

同时,我们还可以指定key为其他内容,通过SpEL表达式来实现,其中SpEL的规则存在以下:

SpEL表达式描述信息
#root.args传递给缓存方法的参数,形式为数组类型
#root.caches该方法执行时对应的缓存,形式为数组类型
#root.target目标对象
#root.targetClass目标对象的类
#root.method缓存的方法
#root.methodName缓存方法的名字,也可以写成#root.method.name
#result方法调用的返回值(不能够用在@Cacheable注解上)
@Argument任意的方法参数名
…

通过上面的案例,我们能够自己去手动设置key的值,那么这里就引入了key和keyGenerator的概念:

KeyGenerator:

当我们再声明注解@Cacheable的时候,如果不指定key参数,那么该缓存下的key就会使用KeyGenerator根据参数自动生成,此时Spring默认使用的是SimpleKeyGenenrator,在SpringBoot自动化配置之下,该配置会自动注入,也就是上面我们得到的SimpleKey[]。该配置的规则为:

  • 如果该缓存方法没有参数,返回 SimpleKey.[]
  • 如果该缓存方法有一个参数,返回该参数的实例
  • 如果该缓存方法有多个参数,返回一个包含所有参数的 SimpleKey
key:

官方更推荐我们手动指定key参数,这里就可以使用SpEL表示式来进行key的设置

注意:官方说 key 和 keyGenerator 参数是互斥的,同时指定两个会导致异常

3.3 自定义条件Conditon&Unless

我们除了可以自定义设置缓存的key,还是可以设置缓存的自定义条件,这里列举@Cacheable和@CachePut共有的一些自定义属性

属性描述
value设置缓存的名称
keySpEL表达式,设置自定义缓存的key
conditionSpEL表达式,如果得到的结果为fasle,则缓存不应用到方法调用上,即无法从缓存中获取数据
unlessSpEL表达式,如果得到的结果为true,则返回值不放到缓存中去
//1. 如果返回结果中包含"Cache",则不会将其放到缓存中去 
@Cacheable(value = {"test"}, key = "#root.method.name", unless = "#result.contains('Cache')")

//2. 如果方法名不包含”CacheName“,则不能够从缓存中获取,得执行该缓存方法,获取返回值
@Cacheable(value = {"test"}, key = "#root.method.name", condition = "#root.methodName.contains('CacheName')")

这里发现condition和unless都能决定是否进行缓存,那么同时指定这两个参数就会存在不同的结果:

conditonunless结果
false-一定不会缓存
truetrue不会缓存
truefalse缓存

@CacheEvict注解在方法调用时不会向缓存中添加任何内容,但是会从缓存中移除之前的缓存结果,除了上面的属性外,还有几种属性:

  • allEntries:如果为true的话,特定缓存的所有条目全部都被移除
  • beforeInvocation:如果为true的话,方法调用之前就会移除条目;否则,方法调用成功以后再移除条目

3.4 自定义配置管理和解析CacheManager&CacheResolver

上面的自定义配置我们简单设置了缓存的默认配置信息。但是其实在项目中我们经常不仅仅使用到一种缓存策略,可能存在多种,那么就要配置多种缓存,同时在使用@Cacheable等缓存注解的时候,需要指定使用的是哪种缓存,也可以使用哪种缓存解析器对其进行解析等。。。这便引入了我们的CacheManager和CacheResolver:

CacheManager:

CacheManager称为缓存管理器,是用来管理(检索)一类缓存的。通常来讲,缓存管理器是与缓存组件类型相关联的。我们知道,spring 缓存抽象的目的是为使用不同缓存组件类型提供统一的访问接口,以向开发者屏蔽各种缓存组件的差异性。那么 CacheManager 就是承担了这种屏蔽的功能。spring 为其支持的每一种缓存的组件类型提供了一个默认的 manager,如:RedisCacheManager 管理 redis 相关的缓存的检索、EhCacheManager 管理 ehCache 相关的缓存等。

CacherResolver:

CacheResolver称为缓存解析器,是用来管理缓存管理器的。CacheResolver 保持一个 cacheManager 的引用,并通过它来检索缓存。CacheResolver 与 CacheManager 的关系有点类似于 KeyGenerator 跟 key。spring 默认提供了一个 SimpleCacheResolver,开发者可以自定义并通过 @Bean 来注入自定义的解析器,以实现更灵活的检索。

实际生产中,一般情况下我们只会配置一种缓存,此时我们就不用显式化指定cacheManager或者cacheResolver。但是Spring允许我们的系统同时配置多种缓存组件,这种情况下,我们需要指定@Cacheable的cacheManager或者cacheResolver参数

3.5 自定义sync 同步参数

当我们在高并发请求的情况下,对于同一个缓存方法,可能会同时进行计算,访问数据库,这样就无法达到缓存高效的目的。在这种情况下,我们可以使用sync = true开启同步模式,它就会告诉底层的缓存提供者将缓存的入口锁住,当有n个线程过来时,只会有一个线程进行数据库访问,将计算结果放入缓存,同时将缓存信息同步到其他的n -1个线程,这样就能减少n - 1次的数据访问,有效地避免了缓存击穿问题。

4. 原理剖析

参考博客地址:https://blog.youkuaiyun.com/weixin_37943811/article/details/116462248

首先,SpringCache的一切的开始来源于@EnabCaching注解,该注解即帮助我们启动了Springcache功能,我们点开该注解:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({CachingConfigurationSelector.class})
public @interface EnableCaching {
    //是否直接代理目标类,默认为false
    boolean proxyTargetClass() default false;
		//同时的类型,默认为代理类型
    AdviceMode mode() default AdviceMode.PROXY;
		//顺序
    int order() default Integer.MAX_VALUE;
}

在该注解中引入了CachingConfigurationSelector类,进入该类查看其实现的逻辑:

public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
    public CachingConfigurationSelector() {
    }

   //核心方法,就是根据传入的AdviceMode类型选择对应的配置类型,默认是代理PROXY
    public String[] selectImports(AdviceMode adviceMode) {
        switch (adviceMode) {
            case PROXY:
                return this.getProxyImports();
            case ASPECTJ:
                return this.getAspectJImports();
            default:
                return null;
        }
    }

    private String[] getProxyImports() {
        List<String> result = new ArrayList(3);
        //当使用PROXY模式的时候,需要引入AutoProxyRegistrar和ProxyCachingConfiguration两个配置
        result.add(AutoProxyRegistrar.class.getName());
        result.add(ProxyCachingConfiguration.class.getName());
        //jsr107是关于java关于缓存的规范提案,使用的话需要引入javax.cache相关包,了解即可
        if (jsr107Present && jcacheImplPresent) {
            result.add("org.springframework.cache.jcache.config.ProxyJCacheConfiguration");
        }

        return StringUtils.toStringArray(result);
    }

    private String[] getAspectJImports() {
        List<String> result = new ArrayList(2);
        result.add("org.springframework.cache.aspectj.AspectJCachingConfiguration");
        if (jsr107Present && jcacheImplPresent) {
            result.add("org.springframework.cache.aspectj.AspectJJCacheConfiguration");
        }

        return StringUtils.toStringArray(result);
    }

    static {
        ClassLoader classLoader = CachingConfigurationSelector.class.getClassLoader();
        jsr107Present = ClassUtils.isPresent("javax.cache.Cache", classLoader);
        jcacheImplPresent = ClassUtils.isPresent("org.springframework.cache.jcache.config.ProxyJCacheConfiguration", classLoader);
    }
}

这个类做的事情其实很简答,基于不同的配置模式加载不同的配置类,我们默认使用的PROXY模式,那么除了缓存规范的ProxyJCacheConfiguration以外,还需要引入AutoProxyRegistrar和ProxyCachingConfiguration,这里重点来看一下这两个类干了些什么事??

ProxyCachingConfiguration -缓存配置及工作流程

查看该类的结构图,如下所示:

8.png

观察该结构图,可知该类继承了AbstractCachingConfiguration,那我们先来看看看它的父类AbstractCachingConfiguration做了些啥?

  • 解析注解元数据
  • 装配缓存组件
public abstract class AbstractCachingConfiguration implements ImportAware {
    @Nullable
    protected AnnotationAttributes enableCaching;
    @Nullable
    protected Supplier<CacheManager> cacheManager;
    @Nullable
    protected Supplier<CacheResolver> cacheResolver;
    @Nullable
    protected Supplier<KeyGenerator> keyGenerator;
    @Nullable
    protected Supplier<CacheErrorHandler> errorHandler;

    public AbstractCachingConfiguration() {
    }

  //获取出注解元数据,将其进行解析成AnnotationAttributes
    public void setImportMetadata(AnnotationMetadata importMetadata) {
        this.enableCaching = AnnotationAttributes.fromMap(importMetadata.getAnnotationAttributes(EnableCaching.class.getName()));
        if (this.enableCaching == null) {
            throw new IllegalArgumentException("@EnableCaching is not present on importing class " + importMetadata.getClassName());
        }
    }

  //将用户的自定义缓存组件进行装配,得到cacheManager,cacheResolver,keyGenerator,errorHandler
    @Autowired
    void setConfigurers(ObjectProvider<CachingConfigurer> configurers) {
        Supplier<CachingConfigurer> configurer = () -> {
            List<CachingConfigurer> candidates = (List)configurers.stream().collect(Collectors.toList());
            if (CollectionUtils.isEmpty(candidates)) {
                return null;
            } else if (candidates.size() > 1) {
                throw new IllegalStateException(candidates.size() + " implementations of CachingConfigurer were found when only 1 was expected. Refactor the configuration such that CachingConfigurer is implemented only once or not at all.");
            } else {
                return (CachingConfigurer)candidates.get(0);
            }
        };
        this.useCachingConfigurer(new CachingConfigurerSupplier(configurer));
    }

    protected void useCachingConfigurer(CachingConfigurerSupplier cachingConfigurerSupplier) {
        this.cacheManager = cachingConfigurerSupplier.adapt(CachingConfigurer::cacheManager);
        this.cacheResolver = cachingConfigurerSupplier.adapt(CachingConfigurer::cacheResolver);
        this.keyGenerator = cachingConfigurerSupplier.adapt(CachingConfigurer::keyGenerator);
        this.errorHandler = cachingConfigurerSupplier.adapt(CachingConfigurer::errorHandler);
    }
}

然后,我们来看看类ProxyCachingConfiguration本身具体实现:

public class ProxyCachingConfiguration extends AbstractCachingConfiguration {
    @Bean(
        name = {"org.springframework.cache.config.internalCacheAdvisor"}
    )
   //添加了下面的Bean:cacheOperationSource和cacheInterceptor
    @Role(2)
    public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor(CacheOperationSource cacheOperationSource, CacheInterceptor cacheInterceptor) {
        BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
        advisor.setCacheOperationSource(cacheOperationSource);
        advisor.setAdvice(cacheInterceptor);
        if (this.enableCaching != null) {
            advisor.setOrder((Integer)this.enableCaching.getNumber("order"));
        }

        return advisor;
    }

   //注册了AnnotationCacheOperationSource
    @Bean
    @Role(2)
    public CacheOperationSource cacheOperationSource() {
        return new AnnotationCacheOperationSource();
    }
	 //注册CacheInterceptor拦截器,并将父类装配的缓存组件设置进去,同时设置cacheOperationSource
    @Bean
    @Role(2)
    public CacheInterceptor cacheInterceptor(CacheOperationSource cacheOperationSource) {
        CacheInterceptor interceptor = new CacheInterceptor();
        interceptor.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager);
        interceptor.setCacheOperationSource(cacheOperationSource);
        return interceptor;
    }
}
AnnotationCacheOperationSource

其中,我们首先来看一下注册的Bean:AnnotationCacheOperationSource,该类继承自类AbstractFallbackCacheOperationSource,而该父类又实现了接口CacheOperationSource,其中父类中的

getCacheOperations方法是将目标方法中的缓存注解解析成了缓存操作的集合:

public Collection<CacheOperation> getCacheOperations(Method method, @Nullable Class<?> targetClass) {
  if (method.getDeclaringClass() == Object.class) {
    return null;
  } else {
    //这里的getCacheKey是将mehtod和class对象进行整合得到,如果缓存中已经存在缓存集合,则直接返回,否则就需要进行计算(computeCacheOperations方法)得到对应的缓存集合
    Object cacheKey = this.getCacheKey(method, targetClass);
    Collection<CacheOperation> cached = (Collection)this.attributeCache.get(cacheKey);
    if (cached != null) {
      return cached != NULL_CACHING_ATTRIBUTE ? cached : null;
    } else {
      	Collection<CacheOperation> cacheOps = this.computeCacheOperations(method, targetClass);      
        this.attributeCache.put(cacheKey, cacheOps);
      } else {
        this.attributeCache.put(cacheKey, NULL_CACHING_ATTRIBUTE);
      }
      return cacheOps;
    }
  }
}

再来看看类AnnotationCacheOperationSource本身的实现:

//默认构造器指定使用SpringCacheAnnotationParser作为解析器
public AnnotationCacheOperationSource(boolean publicMethodsOnly) {
  this.publicMethodsOnly = publicMethodsOnly;
  this.annotationParsers = Collections.singleton(new SpringCacheAnnotationParser());
}
//通过SpringCacheAnnotationParser的parseCacheAnnotations方法进行解析操作
@Nullable
protected Collection<CacheOperation> findCacheOperations(Class<?> clazz) {
  return this.determineCacheOperations((parser) -> {
    return parser.parseCacheAnnotations(clazz);
  });
}

进入AnnotationCacheOperationSource,看看它是如何解析的??

代码过长,可以自行点进去看看,这里就是将每一个注解解析为了对应的操作CacheOperation。

CacheInterceptor

再来看看CacheInterceptor,它实现了Advice接口,并继承了CacheAspectSupport

9.png

看一下该拦截器的实现:

public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {
    public CacheInterceptor() {
    }

    @Nullable
    public Object invoke(final MethodInvocation invocation) throws Throwable {
        Method method = invocation.getMethod();
        CacheOperationInvoker aopAllianceInvoker = () -> {
            try {
                return invocation.proceed();
            } catch (Throwable var2) {
                throw new CacheOperationInvoker.ThrowableWrapper(var2);
            }
        };
        Object target = invocation.getThis();
        Assert.state(target != null, "Target must not be null");

        try {
          //通过父类CacheAspectSupport进行方法的执行
            return this.execute(aopAllianceInvoker, target, method, invocation.getArguments());
        } catch (CacheOperationInvoker.ThrowableWrapper var6) {
            throw var6.getOriginal();
        }
    }
}

父类CacheAspectSupport实现了SmartInitializingSingleton接口,在单例初始化后容器会调用afterSingletonsInstantiated方法,检查是否存在合适的CacheManager,并将initialized状态设置为true。

public void afterSingletonsInstantiated() {
  if (this.getCacheResolver() == null) {
    Assert.state(this.beanFactory != null, "CacheResolver or BeanFactory must be set on cache aspect");

    try {
      this.setCacheManager((CacheManager)this.beanFactory.getBean(CacheManager.class));
    } catch (NoUniqueBeanDefinitionException var2) {
      throw new IllegalStateException("No CacheResolver specified, and no unique bean of type CacheManager found. Mark one as primary or declare a specific CacheManager to use.", var2);
    } catch (NoSuchBeanDefinitionException var3) {
      throw new IllegalStateException("No CacheResolver specified, and no bean of type CacheManager found. Register a CacheManager bean or remove the @EnableCaching annotation from your configuration.", var3);
    }
  }

  this.initialized = true;
}

接着看该类的核心方法execute:

protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
  if (this.initialized) {
    Class<?> targetClass = this.getTargetClass(target);
    //获取CacheOperationSource缓存注解操作集合,将其构造到CacheOperationContexts上下文对象中
    CacheOperationSource cacheOperationSource = this.getCacheOperationSource();
    if (cacheOperationSource != null) {
      Collection<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass);
      if (!CollectionUtils.isEmpty(operations)) {
        //重载execute方法,并构建CacheOperationContexts对象,参数即为缓存注解操作集合
        return this.execute(invoker, method, new CacheOperationContexts(operations, method, args, target, targetClass));
      }
    }
  }

  return invoker.invoke();
}

execute(invoker, method, new CacheOperationContexts(operations, method, args, target, targetClass)):缓存操作的核心逻辑方法

第一步:检查是否为同步操作(@Cacheable(sync = true)):

  • 如果是切满足条件,则调用缓存获取逻辑并将结果返回
  • 如果不是则返回业务逻辑,即return this.invokeOperation(invoker);

第二步:执行@CacheEvict的前置清除操作,即在方法调用前清除缓存

第三步:检查@Cacheable是否命中缓存:

  • 如果没有命中缓存,则需要将其放到CachePutRequest列表中,然后检查是否命中且没有需要更新的缓存:
    • 如果满足条件,则返回结果,并将结果进行缓存
    • 如果不满足条件,则需要访问数据库等操作,进行业务查询,将结果返回,并将结果进行缓存

第四步:@CachePut操作:

将@CachePut和@Cacheable未命中缓存的操作同步到缓存中

第五步:执行@CacheEvict的后置清除操作,即在方法调用结束清除缓存

private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
  if (contexts.isSynchronized()) {
    CacheOperationContext context = (CacheOperationContext)contexts.get(CacheableOperation.class).iterator().next();
    
    if (!this.isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
      return this.invokeOperation(invoker);
    }

    Object key = this.generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
    Cache cache = (Cache)context.getCaches().iterator().next();

    try {
      return this.wrapCacheValue(method, this.handleSynchronizedGet(invoker, key, cache));
    } catch (Cache.ValueRetrievalException var10) {
      ReflectionUtils.rethrowRuntimeException(var10.getCause());
    }
  }

  this.processCacheEvicts(contexts.get(CacheEvictOperation.class), true, CacheOperationExpressionEvaluator.NO_RESULT);
  Cache.ValueWrapper cacheHit = this.findCachedItem(contexts.get(CacheableOperation.class));
  List<CachePutRequest> cachePutRequests = new ArrayList();
  if (cacheHit == null) {
    this.collectPutRequests(contexts.get(CacheableOperation.class), CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
  }

  Object returnValue;
  Object cacheValue;
  if (cacheHit != null && !this.hasCachePut(contexts)) {
    cacheValue = cacheHit.get();
    returnValue = this.wrapCacheValue(method, cacheValue);
  } else {
    returnValue = this.invokeOperation(invoker);
    cacheValue = this.unwrapReturnValue(returnValue);
  }

  this.collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
  Iterator var8 = cachePutRequests.iterator();

  while(var8.hasNext()) {
    CachePutRequest cachePutRequest = (CachePutRequest)var8.next();
    cachePutRequest.apply(cacheValue);
  }

  this.processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
  return returnValue;
}
AutoProxyRegistrar - 缓存代理的注册装配

AutoProxyRegistrar实现了ImportBeanDefinitionRegistrar接口,registerBeanDefinitions会从启用缓存注解@EnableCaching提取属性,然后手动注册自动代理创建器:

public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  boolean candidateFound = false;
  Set<String> annTypes = importingClassMetadata.getAnnotationTypes();
  Iterator var5 = annTypes.iterator();

  while(var5.hasNext()) {
    String annType = (String)var5.next();
    AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);
    if (candidate != null) {
      Object mode = candidate.get("mode");
      Object proxyTargetClass = candidate.get("proxyTargetClass");
      if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() && Boolean.class == proxyTargetClass.getClass()) {
        candidateFound = true;
        if (mode == AdviceMode.PROXY) {
          AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
          if ((Boolean)proxyTargetClass) {
            AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            return;
          }
        }
      }
    }
  }

  if (!candidateFound && this.logger.isInfoEnabled()) {
    String name = this.getClass().getSimpleName();
    this.logger.info(String.format("%s was imported but no annotations were found having both 'mode' and 'proxyTargetClass' attributes of type AdviceMode and boolean respectively. This means that auto proxy creator registration and configuration may not have occurred as intended, and components may not be proxied as expected. Check to ensure that %s has been @Import'ed on the same class where these annotations are declared; otherwise remove the import of %s altogether.", name, name, name));
  }
}

后面的源码这里不展开分析,大致流程就是在Bean的创建过程中织入增强逻辑,即自动生成代理类并将缓存的逻辑织入进去。而后检查目标类和方法上是否有缓存相关注解(@Cacheable,@CachePut,@CacheEvict等),如果有说明增强器对目标代理类适用,然后找到合适的增强器列表在APC中调用createProxy创建代理工厂,这里选择是否需要直接代理目标类,然后装配增强器,接着调用JdkDynamicAopProxy或者CglibAopProxy创建代理。

缓存配置

在SpringBoot中我们只需要引入缓存的依赖,使用@EnableCaching即可开启缓存功能,这一切源于spring-boot-autoconfigure,其中的cache包中定义了开启缓存配置的各种Configuration:

10.png

spring cache(一)介绍&注解式编程 项目中使用最多的缓存技术就是Redis,用Redis就可以实现了,为什么需要使用spring cache?先看下我们使用缓存步骤:(1)查寻缓存中是否存在数据,如果存在则直接返回结果(2)如果不存在则查询数据库,查询出结果后将结果存入缓存并返回结果(3)数据更新时,先更新数据库(4)然后更新缓存,或者直接删除缓存可以看到逻辑都差不多,这样就出现大量重复的代码,而且缓存与业务耦合较深。则解决了这个问题,它利用AOP。 阅读详情

相关推荐

SpringCache介绍及入门案例

是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能,大大简化我们在业务中操作缓存的代码。SpringCache只是提供了一层抽象,底层可以切换不同的cache实现。具体就是通过接口来统一不同的缓存技术。CacheManager是Spring提供的各种缓存技术抽象接口。描述使用EhCache作为缓存技术使用Google的GuavaCache作为缓存技术使用Redis作为缓存技术。...

weixin_52028697的博客 672

SpringCache详解

SpringCache详解

weixin_42972832的博客 3万+

【SpringCache】快速入门 通俗易懂

查询的时候缓存有就查询缓存 更新和删除又要更新缓存保持一致性 不妨试试SpringCache 加个注解就搞定 不用再写缓存代码

少年你真的要止步于此嘛。 1324

SpringCache

提供基本的Cache抽象,方便切换各种底层Cache;通过注解Cache可以实现类似于事务一样,缓存逻辑透明的应用到我们的业务代码上,且只需要更少的代码就可以完成;提供事务回滚时也自动回滚缓存;支持比较复杂的缓存逻辑;Spring Cache就是一个缓存框架。它利用了AOP(将缓存逻辑与服务逻辑解耦),实现了基于注解的缓存功能(声明式缓存),并且进行了合理的抽象,业务代码不用关心底层是使用了什么缓存框架,只需要简单地加一个注解,就能实现缓存功能了。

luosuss的博客 5333

STM32单片机远程升级

stm32远程升级

fang_yang_wa的博客 1万+

从零搭建YOLOv2硬件加速器:基于PYNQ-Z2的Vivado HLS与Jupyter Notebook实战

本文详细介绍了如何从零搭建YOLOv2硬件加速器,基于PYNQ-Z2开发板,结合Vivado HLS与Jupyter Notebook实现高效目标检测。通过硬件加速,YOLOv2推理速度提升8倍,功耗降低80%。文章涵盖硬件准备、Vivado HLS工程配置、IP核集成、PYNQ部署及常见问题解决,为开发者提供完整实战指南。

weixin_29188997的博客 1008

spring cache基础篇

Spring从3.1开始定义了org.springframework.cache.Cache和org.springframework.cache.CacheManager接口来统一不同的缓存技术;并支持使用JSR-107注解简化我们开发; 提供支持多种缓存的实现。 主要接口有两个: org.springframework.cache.Cache:用于定义缓存的各种操作 org.springframework.cache.CacheManager:用于管理各个cache缓存组件 常...

weixin_43679491的博客 2155

YOLOv5添加注意力机制的具体步骤

本文主要介绍了 本文在此篇博客的基础上向YOLOv5-5.0版 本文主要包括以下内容一、CBAM注意力机制添加(1)修改yolov5s主干网络(2)在common.py中添加可调用的CBAM模块(3)向yolo.py文件添加CBAMC3判断语句二、SE注意力机制添加 本文以yolov5s网络为例 一、CBAM注意力机制添加 注意力机制可以添加在backbone,Neck,Head等部分,接下来本文以backbone主干网络为例 (1)修改yolov5s主干网络 1.在yolov5-5.0工程文件夹下,找到m

thy0000的博客 2万+

SpringCache的介绍和入门案例

Spring Cache是Spring框架提供的一个缓存抽象层,用于在应用程序中实现缓存的功能。它通过在方法执行前检查缓存中是否已经存在所需数据,如果存在则直接返回缓存中的数据,如果不存在则执行方法体,将方法的返回值存储到缓存中,以便后续的请求可以直接从缓存中获取数据。

rqz__的博客 580

开源收银系统_大型收银系统源码_OctShop

开源收银系统正成为大型连锁企业数字化转型的关键工具。相比传统闭源系统,开源方案具有成本低(年均降本60%+)、定制灵活(支持多业态协同)、自主可控等优势,能有效解决高并发、多门店数据同步等痛点。核心价值在于:1)分布式架构适配规模化扩张;2)模块化设计实现个性化开发;3)开放接口打通ERP/CRM等系统。典型案例显示,连锁商超通过源码二次开发实现库存实时同步,补货效率提升50%;餐饮集团统一多业态收银流程后会员复购率增长35%。选型需关注源码成熟度、技术兼容性和安全合规,建议采用"开源+服务商&q

OctShop全栈工程师 938

机器人控制仿真软件:MATLAB Simulink Control System Toolbox_(1).MATLAB基础知识

MATLAB(Matrix Laboratory)是由MathWorks公司开发的一种高性能数值计算和可视化软件。它广泛应用于科学计算、工程设计、信号处理、控制系统设计、图像处理等领域。强大的矩阵运算能力:MATLAB的核心是矩阵运算,能够高效处理大规模的数值数据。丰富的工具箱:MATLAB提供了大量的工具箱,如Control System Toolbox、Simulink、Signal Processing Toolbox等,这些工具箱覆盖了多个应用领域。易于使用的图形界面。

kkchenjj的博客 905

SpringCache 入门

SpringCache入门

weixin_48764706的博客 392

rk3576 点亮 LCD(eDP)

RK3576 处理器集成了对 **eDP (Embedded DisplayPort)** 接口的支持,主要用于驱动嵌入式或笔记本电脑的内置屏幕。支持 **eDP 1.3 版本协议**, 最大可支持 ****4K@120Hz**。

weixin_35723192的博客 353

SpringCache快速入门

文章目录SpringCache缓存框架简介使用加入依赖配置文件SpringCache常用注解@Cacheable@CachePut@CacheEvict@Caching@CacheConfigSpringBoot+MyBatisPlus+H2+SpringCache+Redis整合使用依赖引入h2,初始化SQL脚本yml配置主启动类CacheConfig类KeyGeneratorEntity类MapperServiceController SpringCache缓存框架 简介 官方地址https://sp

qq_44073614的博客 1879

【100%通过率】2024华为OD机试真题E卷 C++语言 实现【流浪地球】

流浪地球计划在赤道上均匀部署了N个转向发动机,按位置顺序编号为0~N-1。 1) 初始状态下所有的发动机都是未启动状态; 2) 发动机起动的方式分为“手动启动”和“关联启动”两种方式; 3) 如果在时刻1一个发动机被启动,下一个时刻2与之相邻的两个发动机就会被“关联启动”; 4) 如果准备启动某个发动机时,它已经被启动了,则什么都不用做; 5) 发动机0与发动机N-1是相邻; 地球联合政府准备挑选某些发动机在某些时刻进行“手动启动”,当然最终所有的发动机都会被启动。 哪些发动机最晚被启动呢?

MISAYAONE的博客 4210

DC/DC开关电源学习笔记(十三)Buck-Boost升降压电路工作原理

Buck-Boost电路是一种能够实现输出电压高于或低于输入电压的DC-DC变换器,且输出电压极性与输入电压相反。它结合了Buck(降压)和Boost(升压)两种拓扑的特点,广泛应用于需要灵活电压调整的场合,如电池供电设备、LED驱动和工业控制系统。本文将从电路结构、工作原理、关键公式推导及实际应用等方面进行详细分析。

小幽余生不加糖 3504

SpringCache 简单入门

spring cache 的简单入门

weixin_70739575的博客 554

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

SpringCache框架入门

在方法执行前spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据, *调用方法并将方法返回值放到缓存中。

2402_87302021的博客 233

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

SpringCache入门案例

如果出现以下异常,使用@SpringBootApplication注解的属性exclude = DataSourceAutoConfiguration.class,忽略数据源的配置。

一起加油吧! 454

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

springcache入门使用

【代码】springcache入门使用。

m0_53303967的博客 127

①优雅的缓存框架:SpringCache入门

SpringCache优雅实现 不侵入业务代码 实现方法级缓存结果。

weixin_42973338的博客 177

SpringCache的入门使用以及在项目里的案例

今天在项目中用到了SpringCache,也首次面试会问的各种缓存问题,话不多说进行正题。

weixin_55592317的博客 1211

SpringCache的简单入门(RedisCacheManager)(@Cacheable、@CachePut、@CacheEvict)

SpringCache的简单入门(RedisCacheManager),@Cacheable、@CachePut、@CacheEvict注解的使用。

weixin_45281071的博客 2296
上一篇: logback+Slf4j日志框架配置
下一篇: Java8新特性 CompletableFuture
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值