头图

introduction

Earlier we have to learn Caffeine king of the local cache performance of Caffeine , and also mentioned SpringBoot local cache is used by default Caffeine friends, today we take a look at Caffeine how to SpringBoot integration.

Integrated caffeine

There are two ways to integrate caffeine and SpringBoot

  • One is that we directly introduce the Caffeine dependency, and then use the Caffeine method to implement caching. Equivalent to using native api
  • Introduce Caffeine and Spring Cache dependencies, and use the SpringCache annotation method to implement caching. SpringCache helped us encapsulate Caffeine
    Pom file introduction

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    <dependency>
      <groupId>com.github.ben-manes.caffeine</groupId>
      <artifactId>caffeine</artifactId>
      <version>2.6.0</version>
    </dependency>
    The first way

    First configure a Cache Cache object through the constructor mode, and then add, delete and check the cache based on this cache object.

    @Configuration
    public class CacheConfig {
      @Bean
      public Cache<String, Object> caffeineCache() {
          return Caffeine.newBuilder()
                  // 设置最后一次写入或访问后经过固定时间过期
                  .expireAfterWrite(60, TimeUnit.SECONDS)
                  // 初始的缓存空间大小
                  .initialCapacity(100)
                  // 缓存的最大条数
                  .maximumSize(1000)
                  .build();
      }

    We will not introduce the first method one by one. Basically, use caffeineCache to operate the following methods according to your own business.
    在这里插入图片描述
    Used in this way is intrusive to the code.

    The second way
  • EnableCaching annotation on the SpingBoot startup class. This thing is the same as many frameworks. For example, our food integration dubbo also needs to be marked with the @EnableDubbole annotation.

      @SpringBootApplication
      @EnableCaching
      public class DemoApplication {
          public static void main(String[] args) {
              SpringApplication.run(DemoApplication.class, args);
          }
  • Configure the type of cache we use, expiration time, cache strategy, etc. at application.yml

    spring:
    profiles:
      active: dev
    cache:
      type: CAFFEINE
      caffeine:
        spec: maximumSize=500,expireAfterAccess=600s

    If we are not accustomed to using this configuration, of course we can also use JavaConfig configuration instead of configuration files.

    @Configuration
    public class CacheConfig {
          @Bean
          public CacheManager cacheManager() {
              CaffeineCacheManager cacheManager = new CaffeineCacheManager();
              cacheManager.setCaffeine(Caffeine.newBuilder()
                      // 设置最后一次写入或访问后经过固定时间过期
                      .expireAfterAccess(600, TimeUnit.SECONDS)
                      // 初始的缓存空间大小
                      .initialCapacity(100)
                      // 缓存的最大条数
                      .maximumSize(500));
              return cacheManager;
          }

    The next step is how to use this cache in the code

    
      @Override
      @CachePut(value = "user", key = "#userDTO.id")
      public UserDTO save(UserDTO userDTO) {
          userRepository.save(userDTO);
          return userDTO;
      }
    
      @Override
      @CacheEvict(value = "user", key = "#id")//2
      public void remove(Long id) {
          logger.info("删除了id、key为" + id + "的数据缓存");
      }
    
      @Override
      @Cacheable(value = "user",key = "#id")
      public UserDTO getUserById(Long id) {
          return userRepository.findOne(id);
      }
    

    In the above code, we can see that there are several annotations @CachePut、@CacheEvict、@Cacheable
    We only need to mark these annotations on the method, and we can use the cache. Let's introduce these annotations separately.

    @Cacheable

    @Cacheable It can be marked either on the class or on the method. When it is marked on the class, it states that all methods on this class will support caching, the same
    When it acts on the method, it indicates that the method supports caching. For example, the getUserById our code above has no data in the cache for the first time, we will query DB , but the second query will not DB , but directly get the result from the cache and return it NS.

    value attribute
  • @Cacheable of value attributes must be specified, and its return value indicates the current process will be cached in which Cache the corresponding Cache name.

    key
  • @Cacheable of key . One is to specify our key displayed by ourselves, and there is also a default generation strategy. The default generation strategy is SimpleKeyGenerator . The way to generate key is relatively simple. We can look at it. The source code:

    public static Object generateKey(Object... params) {
          // 如果方法没有参数 key就是一个 new SimpleKey()
          if (params.length == 0) {
              return SimpleKey.EMPTY;
          }
          // 如果方法只有一个参数 key就是当前参数
          if (params.length == 1) {
              Object param = params[0];
              if (param != null && !param.getClass().isArray()) {
                  return param;
              }
          }
          // 如果key是多个参数,key就是new SimpleKey ,不过这个SimpleKey对象的hashCode 和Equals方法是根据方法传入的参数重写的。
          return new SimpleKey(params);
      }

    The above code is still very well understood and divided into three situations:

  • The method has no parameters, so new uses a globally empty SimpleKey object as key .
  • If the method has only one parameter, use the current parameter as key
  • The method parameter is greater than 1 months, it new a SimpleKey object as key , new this SimpleKey when a parameter rewrite the incoming SimpleKey of hashCode and equals method,
    As for the reasons why need to rewrite the words, just like Map with custom objects as key time must be rewritten hashCode and equals is the same principle of the method, because caffein also help the ConcurrentHashMap to achieve,

    summary

    In the above code, we can find that the default generation of key only related to the parameters we passed in. If we have multiple methods without parameters in a class, and then we use the default cache generation strategy, it will cause cache loss.
    Either the caches cover each other, or ClassCastException may happen because they all use the same key . For example, the following code will ClassCastException an exception (0618482212674c)

    @Cacheable(value = "user")
      public UserDTO getUser() {
          UserDTO userDTO = new UserDTO();
          userDTO.setUserName("Java金融");
          return userDTO;
      }
      @Cacheable(value = "user")
      public UserDTO2 getUser1() {
          UserDTO2 userDTO2 = new UserDTO2();
          userDTO2.setUserName2("javajr.cn");
          return userDTO2;
      }

    Therefore, it is generally not recommended to use the default cache generation strategy of key If we have to use it, we'd better rewrite it ourselves, including the method name, etc. Similar to the following code:

    @Component
    public class MyKeyGenerator extends SimpleKeyGenerator {
    
      @Override
      public Object generate(Object target, Method method, Object... params) {
          Object generate = super.generate(target, method, params);
          String format = MessageFormat.format("{0}{1}{2}", method.toGenericString(), generate);
          return format;
      }
    Custom key

    We can Spring specify our EL expressions key . The EL expression here can use method parameters and their corresponding attributes.
    When using method parameters, we can directly use " name" or " index", which is also our recommended practice:

     @Cacheable(value="user", key="#id")
      public UserDTO getUserById(Long id) {
          UserDTO userDTO = new UserDTO();
          userDTO.setUserName("java金融");
          return userDTO;
      }
      @Cacheable(value="user", key="#p0")
      public UserDTO getUserById1(Long id) {
          return null;
      }
      @Cacheable(value="user", key="#userDTO.id")
      public UserDTO getUserById2(UserDTO userDTO) {
          return null;
      }
      @Cacheable(value="user", key="#p0.id")
      public UserDTO getUserById3(UserDTO userDTO) {
          return null;
      }
    @CachePut

    @CachePut specified property is and @Cacheable the same, but they are two distinct, @CachePut method annotated will not go to check whether the cache has a value, but each will go to perform the method, and the results returned, and The results will also be cached.

    ![在这里插入图片描述](https://img-blog.csdnimg.cn/ff516023113046adbf86caaea6e499f6.png)
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    

Why is it such a process? We can go and see the key code of its source code is this line,

        Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));

When we use the method with @Cacheable annotation, then contexts will add CacheableOperation , only if the content retrieved by contexts.get(CacheableOperation.class) is not empty, will the content be fetched from the cache, otherwise cacheHit will Return directly to null . As for when contexts will be added to CacheableOperation, we will understand the method SpringCacheAnnotationParser#parseCacheAnnotations The specific source code will not be shown, and those who are interested can read it by themselves.

@CacheEvict

Delete the data in the cache. The usage is similar to the previous two annotations. There are value and key attributes. One thing to note is that it has an additional attribute beforeInvocation

  • beforeInvocation This attribute needs to pay attention to its default value is false, false means that the cache will not be deleted before the method is called, and the cache will be deleted only after the method is successfully executed. If it is set to true , the cache will be deleted before calling the method, and the delete cache will be called after the method is executed successfully. This is double deletion. If the method executes abnormally, the cache will not be deleted.
  • allEntrie Whether to clear all cached content, the default value is false , if specified as true , all caches will be cleared immediately after the method is called
@Caching

This is a combined annotation that integrates the above three annotations and has three attributes: cacheable, put and evict, which are used to specify @Cacheable , @CachePut and @CacheEvict , respectively.

summary

The second method is intrusive, and its implementation principle is relatively simple. It is implemented through the aspect method interceptor. It intercepts all methods. Its core code is as follows: it seems to be indistinguishable from our business code. Those who are interested can also take a look.

if (contexts.isSynchronized()) {
            CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next();
            if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
                Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
                Cache cache = context.getCaches().iterator().next();
                try {
                    return wrapCacheValue(method, cache.get(key, () -> unwrapReturnValue(invokeOperation(invoker))));
                }
                catch (Cache.ValueRetrievalException ex) {
                    // The invoker wraps any Throwable in a ThrowableWrapper instance so we
                    // can just make sure that one bubbles up the stack.
                    throw (CacheOperationInvoker.ThrowableWrapper) ex.getCause();
                }
            }
            else {
                // No caching required, only call the underlying method
                return invokeOperation(invoker);
            }
        }


        // Process any early evictions
        // beforeInvocation 属性是否为true,如果是true就删除缓存
        processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
                CacheOperationExpressionEvaluator.NO_RESULT);

        // Check if we have a cached item matching the conditions
        Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));

        // Collect puts from any @Cacheable miss, if no cached item is found
        List<CachePutRequest> cachePutRequests = new LinkedList<>();
        if (cacheHit == null) {
            collectPutRequests(contexts.get(CacheableOperation.class),
                    CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
        }

        Object cacheValue;
        Object returnValue;

        if (cacheHit != null && !hasCachePut(contexts)) {
            // If there are no put requests, just use the cache hit
            cacheValue = cacheHit.get();
            returnValue = wrapCacheValue(method, cacheValue);
        }
        else {
            // Invoke the method if we don't have a cache hit
            returnValue = invokeOperation(invoker);
            cacheValue = unwrapReturnValue(returnValue);
        }

        // Collect any explicit @CachePuts
        collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);

        // Process any collected put requests, either from @CachePut or a @Cacheable miss
        for (CachePutRequest cachePutRequest : cachePutRequests) {
            cachePutRequest.apply(cacheValue);
        }

        // Process any late evictions
        processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);

        return returnValue;
    }

Finish

  • Due to my lack of knowledge, there will inevitably be mistakes. If you find something wrong, please leave a message to point it out and I will correct it.
  • If you think the article is not bad, your forwarding, sharing, admiration, liking, and commenting are your greatest encouragement to me.
  • Thank you for reading, welcome and thank you for your attention.


Standing on the shoulders of giants and picking apples:
https://www.cnblogs.com/fashflying/p/6908028.html#!comments


java金融
89 声望15 粉丝