Cache annotations

  • @CacheableThe cache method returns a value
  • @CacheConfigClass level annotations are used to extract the cache configuration common properties under the current class for examplecacheNames, methods that have the same attributes have a higher priority
  • @CacheEvictUsed to delete cached data
  • @CachePutThis annotation will always execute method logic for updating the cache operation. This annotation will require the attention that the return value of the method must be the same as the return value of the cached method
  • @CachingImplementation of complex caches, such as multidimensionalkeyOf course, you can also split into a simple cache
  • @EnableCachingEnabling Cached annotations

Note Details

1.@CacheableNote that the cache method returns a value with the following parameters
  • String[] value() default {};cacheNameequivalent
  • String[] cacheNames() default {};We can understand this to bekeyThe components of thekeyiscacheNames::xxx
  • String key() default "";The customkeySupport the use ofSpELExpressions can get parameters, or field properties in an object
  • String keyGenerator() default "";Specify the use of customkeyGenerator, which defaults toSimpleKey
  • String cacheManager() default "";The top-level interface is used for standardization, masking differences between different components, which actually existRedisCacheManagerIf only one cache component is configured in the system, you do not need to specify this parameter
  • String cacheResolver() default "";The cache parser is used to manage the cache manager
  • String condition() default "";Method to determine whether to cache before execution (here is the cache condition)
  • String unless() default "";Method after the execution of the condition to determine whether to cache (here is not the cached condition), you can get the result for judgment, can be compared withconditionCombination use:
    • condition = falseDon’t cache
    • condition = true, unless = trueDon’t cache
    • condition = true, unless = falseThe cache
  • boolean sync() default false;Synchronization or not, in the case of high concurrency, the following may happen, yes10When multiple threads are accessing the same resource at the same time, concurrent fetching can occur without cache10All threads go to the database to fetch data, if setsync=true, in which one thread gets the data and then cache it for other threads to use, which can effectively avoid the cache breakdown problem.

The @cacheable annotation can be enabled with the @enablecaching annotation in SpringBoot. You just need to introduce dependencies

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

And specify the Redis configuration in the configuration file

spring.redis.port=6379
spring.redis.host=localhost
Copy the code
2. @Cacheconfig extracts the common configuration and contains four properties, described as above, except that they are the common configuration at the current class level.
  • String[] cacheNames() default {};
  • String keyGenerator() default "";
  • String cacheManager() default "";
  • String cacheResolver() default "";
3. The @cacheevict attribute is as follows
  • String[] value() default {};
  • String[] cacheNames() default {};
  • String key() default "";
  • String keyGenerator() default "";
  • String cacheManager() default "";
  • String cacheResolver() default "";
  • String condition() default "";
  • boolean allEntries() default false;This property defaults to false and is set to true to clear all caches in the current domain (cacheNames)
  • boolean beforeInvocation() default false;Set to true to prevent dirty data in the cache from being thrown after deleting the cache. For example, I updated a piece of data and committed a transaction, but an exception was thrown in the subsequent execution, resulting in dirty data not being cleared in time. What should be paid attention to here is the time related to transactions.
4. @cachePUT Cache update, when a method is annotated with this annotation, the method logic executes anyway. The cache always uses the return value of this method. Its attributes are as follows

There is one less sync property than Cacheable.

  • String[] value() default {};cacheNameequivalent
  • String[] cacheNames() default {};We can understand this to bekeyThe components of thekeyiscacheNames::xxx
  • String key() default "";The customkeySupport the use ofSpELExpressions can get parameters, or field properties in an object
  • String keyGenerator() default "";Specify the use of customkeyGenerator, which defaults toSimpleKey
  • String cacheManager() default "";The top-level interface is used for standardization, masking differences between different components, which actually existRedisCacheManagerIf only one cache component is configured in the system, you do not need to specify this parameter
  • String cacheResolver() default "";The cache parser is used to manage the cache manager
  • String condition() default "";Method to determine whether to cache before execution (here is the cache condition)
  • String unless() default "";Method after the execution of the condition to determine whether to cache (here is not the cached condition), you can get the result for judgment, can be compared withconditionCombination use:
    • condition = falseDon’t cache
    • condition = true, unless = trueDon’t cache
    • condition = true, unless = falseThe cache
5. @caching. This annotation is actually a combination of @cacheevict, @cachePUT, and @cacheable

I can check a user object can be multidimensional, and is calling the same interface, according to the name, according to the ID, at this time I can set different dimensions of the cache key in the same cache field, you can use this annotation to achieve the effect.

  • Cacheable[] cacheable() default {};
  • CachePut[] put() default {};
  • CacheEvict[] evict() default {};
6. @enablecaching This annotation is used to enable the annotation cache. Its attributes are as follows
  • boolean proxyTargetClass() default false;
  • AdviceMode mode() default AdviceMode.PROXY;
  • int order() default Ordered.LOWEST_PRECEDENCE;

This annotation @ import (CachingConfigurationSelector. Class), in the CachingConfigurationSelector import the following two categories: AutoProxyRegistrar ProxyCachingConfiguration please look at the code

public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
    private String[] getProxyImports() {
      List<String> result = new ArrayList<>(3);
      result.add(AutoProxyRegistrar.class.getName());
      result.add(ProxyCachingConfiguration.class.getName());
      if (jsr107Present && jcacheImplPresent) {
         result.add(PROXY_JCACHE_CONFIGURATION_CLASS);
      }
      return StringUtils.toStringArray(result);
   }
   // Here is a layer of filtering based on 'mode'
    @Override
    public String[] selectImports(AdviceMode adviceMode) {
        switch (adviceMode) {
            case PROXY:
                return getProxyImports();
            case ASPECTJ:
                return getAspectJImports();
            default:
                return null; }}}Copy the code

The AutoProxyRegistrar handles the proxyTargetClass and AdviceMode attributes in annotations

public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

    // Here AUTO_PROXY_CREATOR(APC), if the mode attribute is PROXY, uses the interface-based JDK native PROXY, and if proxyTargetClass is true, forces the use of CGLIB subclass-based PROXY
    // Special note: This configuration applies to all beans managed by Spring, so exercise caution.
   @Override
   public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
      boolean candidateFound = false;
      Set<String> annoTypes = importingClassMetadata.getAnnotationTypes();
      for (String annoType : annoTypes) {
         AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annoType);
         if (candidate == null) {
            continue;
         }
         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; }}}}/ /... Irrelevant code neglect}}Copy the code

Assembly principle

ProxyCachingConfiguration cache for the assembly, see code @ Role comments don’t need to pipe him the annotations made a classification of beans, no actual effect, it is marked as infrastructure This configuration class is configured with three specific to Bean, respectively

  • BeanFactoryCacheOperationSourceAdvisor
  • CacheOperationSource
  • CacheInterceptor
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {

   @Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
   @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
   public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor(a) {
      BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
      advisor.setCacheOperationSource(cacheOperationSource());
      advisor.setAdvice(cacheInterceptor());
      if (this.enableCaching ! =null) {
         advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));
      }
      return advisor;
   }

   @Bean
   @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
   public CacheOperationSource cacheOperationSource(a) {
      return new AnnotationCacheOperationSource();
   }

   @Bean
   @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
   public CacheInterceptor cacheInterceptor(a) {
      CacheInterceptor interceptor = new CacheInterceptor();
      interceptor.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager);
      interceptor.setCacheOperationSource(cacheOperationSource());
      returninterceptor; }}Copy the code

The following instances are assembled in the cacheInterceptor bean

  • this.errorHandler
  • this.keyGenerator
  • this.cacheResolver
  • this.cacheManager

These instances have parent classes, so how does the parent class load these classes? Please see the following code in the superclass AbstractCachingConfiguration automatic injection needed a CachingConfigurer implementation class set, means that we can either go to realize CachingConfigurer this interface or bean to configure this interface

@Configuration(proxyBeanMethods = false)
public abstract class AbstractCachingConfiguration implements ImportAware {
// Omit...
    @Autowired(required = false)
   void setConfigurers(Collection<CachingConfigurer> configurers) {
      if (CollectionUtils.isEmpty(configurers)) {
         return;
      }
      if (configurers.size() > 1) {
         throw new IllegalStateException(configurers.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.");
      }
      CachingConfigurer configurer = configurers.iterator().next();
      useCachingConfigurer(configurer);
   }
    protected void useCachingConfigurer(CachingConfigurer config) {
      this.cacheManager = config::cacheManager;
      this.cacheResolver = config::cacheResolver;
      this.keyGenerator = config::keyGenerator;
      this.errorHandler = config::errorHandler; }}Copy the code

These values are null if they are not configured. Look at the code below, which creates the default values. SimpleCacheErrorHandler,SimpleKeyGenerator,SimpleCacheResolver SingletonSupplier is an implementation of an Supplier constructed with two suppliers in it. The get method returns a generic instance. Cacheresolver.get () is used to return the associated generic instance. If a value is passed, the instance that was passed in is used, and if no value is passed, the default generated value is used.

public abstract class CacheAspectSupport extends AbstractCacheInvoker
      implements BeanFactoryAware.InitializingBean.SmartInitializingSingleton {
    public void configure(
         @Nullable Supplier<CacheErrorHandler> errorHandler, @Nullable Supplier<KeyGenerator> keyGenerator,
         @Nullable Supplier<CacheResolver> cacheResolver, @Nullable Supplier<CacheManager> cacheManager) {

      this.errorHandler = new SingletonSupplier<>(errorHandler, SimpleCacheErrorHandler::new);
      this.keyGenerator = new SingletonSupplier<>(keyGenerator, SimpleKeyGenerator::new);
      this.cacheResolver = newSingletonSupplier<>(cacheResolver, () -> SimpleCacheResolver.of(SupplierUtils.resolve(cacheManager))); }}Copy the code

These are the things that a regular Spring project configuration needs to extend and the configuration

So it’s pretty easy to do under SpringBoot, so look at the CacheAutoConfiguration class, which is under SpringBoot so you won’t find it under the Spring-Framework project, but that’s okay so let’s go ahead and look at it, From above we know that we need to configure these four properties and see how the code does it.

  • this.errorHandler
  • this.keyGenerator
  • this.cacheResolver
  • this.cacheManager
@Configuration
@ConditionalOnClass(CacheManager.class)
@ConditionalOnBean(CacheAspectSupport.class)
@ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver")
@EnableConfigurationProperties(CacheProperties.class)
@AutoConfigureAfter({ CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class, HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class })
@Import(CacheConfigurationImportSelector.class)
public class CacheAutoConfiguration {

   @Bean
   @ConditionalOnMissingBean
   public CacheManagerCustomizers cacheManagerCustomizers( ObjectProvider
       
        > customizers)
       > {
      return new CacheManagerCustomizers(
            customizers.orderedStream().collect(Collectors.toList()));
   }

   @Bean
   public CacheManagerValidator cacheAutoConfigurationValidator( CacheProperties cacheProperties, ObjectProvider
       
         cacheManager)
        {
      return new CacheManagerValidator(cacheProperties, cacheManager);
   }

   @Configuration
   @ConditionalOnClass(LocalContainerEntityManagerFactoryBean.class)
   @ConditionalOnBean(AbstractEntityManagerFactoryBean.class)
   protected static class CacheManagerJpaDependencyConfiguration
         extends EntityManagerFactoryDependsOnPostProcessor {

      public CacheManagerJpaDependencyConfiguration(a) {
         super("cacheManager"); }}/** * Bean used to validate that a CacheManager exists and provide a more meaningful * exception. */
   static class CacheManagerValidator implements InitializingBean {

      private final CacheProperties cacheProperties;

      private final ObjectProvider<CacheManager> cacheManager;

      CacheManagerValidator(CacheProperties cacheProperties,
            ObjectProvider<CacheManager> cacheManager) {
         this.cacheProperties = cacheProperties;
         this.cacheManager = cacheManager;
      }

      @Override
      public void afterPropertiesSet(a) {
         Assert.notNull(this.cacheManager.getIfAvailable(),
               () -> "No cache manager could "
                     + "be auto-configured, check your configuration (caching "
                     + "type is '" + this.cacheProperties.getType() + "')"); }}/ * * * {@link ImportSelector} to add {@link CacheType} configuration classes.
    */
   static class CacheConfigurationImportSelector implements ImportSelector {

      @Override
      public String[] selectImports(AnnotationMetadata importingClassMetadata) {
         CacheType[] types = CacheType.values();
         String[] imports = new String[types.length];
         for (int i = 0; i < types.length; i++) {
            imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
         }
         returnimports; }}}Copy the code

@ Import (CacheConfigurationImportSelector. Class) to Import the current class of a static inner class, is actually a ImportSelector mechanism of the interface is to collect needs to be spring loaded classes, The following cache configuration class is loaded here. You can see that SpringBoot supports so many caching systems and has implementations. The corresponding implementation only needs to introduce the relevant starter, and the annotations use the same set above.

final class CacheConfigurations {
    static{ Map<CacheType, Class<? >> mappings =newEnumMap<>(CacheType.class); mappings.put(CacheType.GENERIC, GenericCacheConfiguration.class); mappings.put(CacheType.EHCACHE, EhCacheCacheConfiguration.class); mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class); mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class); mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class); mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class); mappings.put(CacheType.REDIS, RedisCacheConfiguration.class); mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class); mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class); mappings.put(CacheType.NONE, NoOpCacheConfiguration.class); MAPPINGS = Collections.unmodifiableMap(mappings); }}Copy the code

Many of the global properties of the cache can be configured directly using the configuration file. For details, see CacheProperties

# Generic cache domain
spring.cache.cache-names=cacheTest
# Cache type, which can be all caches in the list above
spring.cache.type=redis
# Whether to cache null values, default to true, to prevent cache penetration
spring.cache.redis.cache-null-values=true
The hash tag of the redis-cluster can be set to the same slot. The hash tag of the redis-cluster can be set to the same slot
spring.cache.redis.key-prefix={user}
# Cache expiration time is 10 seconds
spring.cache.redis.time-to-live=10000ms
# Whether to use a prefix
spring.cache.redis.use-key-prefix=true
Copy the code

Build a use case

Import the packages and configured properties at the beginning and then add the following code

@RestController
@EnableCaching
public class CacheTest {
    
    @PostMapping("/cacheTest")
    @Cacheable(cacheNames = "cacheTest")
    public String testCacheNames(String s) {
        System.out.println("No aaa from cache.");
        return s;
    }
    
    @GetMapping("delCache")
    @CacheEvict(cacheNames = "cacheTest",allEntries=true)
    public String testDel(a){
        System.out.println("Delete cache");
        return "del"; }}Copy the code

Start tests, but you can also build unit tests. Request the interface and you get the following output

127.0.0.1:6379 > keys * 1)"cacheTest::xxxxxx"
Copy the code

Complex use, try it yourself.