Article 40 of spring's Way to God: Cache Use (@EnableCaching, @Cacheable, @CachePut, @CacheEvict, @Caching, @CacheConfig)

This article focuses on the use of caching in spring.

background

Caching is known to all, mainly to improve the speed of system queries.

For example, commodity details in e-commerce, which are usually not frequently changed but accessed frequently, can be pulled out of the DB and put into the cache (such as redis, local memory). When it is retrieved, it is retrieved from the cache first, when it is not, it is retrieved from the db, and then it is dropped into the cache. When the commodity information is changed, the information in the cache can be removed or the latest data can be dropped into the cache.

Spring provides a complete set of caching solutions that are particularly easy to use, mainly through annotations. There are five common annotations, which we will describe one by one.

spel expressions are used extensively in this article, so take a look at this unfamiliar suggestion: Spel in Spring

@EnableCaching: Enable caching

Turn on the caching function and add this annotation to the configuration class. After this annotation, spring knows you need to use the caching function. Other annotations related to caching will be valid. Spring is mainly implemented through aop, which intercepts methods that need to use caching to implement the caching function.

@Cacheable: Give caching

Effect

@Cacheable can be marked either on a method or on a class. Marking on a method means that it supports caching, and marking on a class means that all methods of that class support caching. For a method that supports caching, Spring caches its return value after it is invoked to ensure that the next time the same parameters are used to execute the method, results can be obtained directly from the cache without having to execute the method again. Spring caches the return value of a cached method as a key value, which is the return result of the method. As for keys, Spring supports two strategies, default and custom, which will be explained later. It is important to note that a method that supports caching does not trigger caching when called inside an object. @ Cacheable can specify three attributes, value, key, and condition.

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {
 String[] value() default {};
 String[] cacheNames() default {};
 String key() default "";
 String condition() default "";
 String unless() default "";
}

value property: Specify Cache name

Value, like the cacheNames property, must specify one of them, which indicates on which Cache the return value of the current method will be cached, corresponding to the name of the Cache. It can be one Cache or multiple Caches and is an array when multiple Caches need to be specified.

You can think of a Cache as a HashMap. There can be many Caches in the system, and each Cache has a name. You need to specify in which cache the return value of the method is placed and the name of the cache.

Case 1

The list method below adds the ability to cache and places its results in cache cache1.

package com.javacode2018.cache.demo1;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

@Component
public class ArticleService {

    @Cacheable(cacheNames = {"cache1"})
    public List<String> list() {
        System.out.println("Get a list of articles!");
        return Arrays.asList("spring", "mysql", "java High concurrency", "maven");
    }
}

The next configuration class, MainConfig1, must be annotated with @EnableCaching to enable caching.

Then you need to define a bean in the configuration class: Cache Manager, type CacheManager, CacheManager is an interface, there are several implementations (such as using redis, ConcurrentMap to store cache information), here we use ConcurrentMapCache Manager, internal use ConcurrentHashMap to store cache information directly in local jvm memory, Online environments, however, are typically clustered and can be implemented with redis. The next article describes spring cache integration with redis.

package com.javacode2018.cache.demo1;

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@EnableCaching //@0
@ComponentScan
@Configuration
public class MainConfig1 {

    //@1: Cache Manager
    @Bean
    public CacheManager cacheManager() {
        //Create Cache Manager(ConcurrentMapCacheManager: Its internal use ConcurrentMap Realized),The constructor is used to specify the name of the cache and can specify multiple
        ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager("cache1");
        return cacheManager;
    }

}

For a test class, call the list method twice to see the effect

package com.javacode2018.cache;

import com.javacode2018.cache.demo1.ArticleService;
import com.javacode2018.cache.demo1.MainConfig1;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class CacheTest {

    @Test
    public void test1() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(MainConfig1.class);
        context.refresh();
        ArticleService articleService = context.getBean(ArticleService.class);
        System.out.println(articleService.list());
        System.out.println(articleService.list());
    }

}

output

Get a list of articles!
[spring, mysql, java High concurrency, maven]
[spring, mysql, java High concurrency, maven]

From the first line, you can see that the first time you enter the list method, the second time you don't, you get it from the cache.

Key property: custom key

The key attribute is used to specify the key that corresponds to the result returned by the Spring cache method. As mentioned above, you can interpret Cache as a hashMap, where the cache is stored in HashMap as key->value, which is the value that needs to be cached (that is, the return value of the method).

The key attribute supports SpEL expressions; When we do not specify this property, Spring uses the default policy to generate the key (org.springframework.cache.interceptor.SimpleKeyGenerator), which is created with the method parameter by default.

Custom policy means that we can specify our key through a SpEL expression, where SpEL expressions can use method parameters and their corresponding attributes, and when using method parameters we can use either'#parameter name'or'#p parameter index' directly.

Spring also provides us with a root object to generate a key from which we can get the following information.

Property NamedescribeExample
methodName Current method name #root.methodName
method Current Method #root.method.name
target Currently invoked object #root.target
targetClass class of the object currently being invoked #root.targetClass
args Array of current method parameters #root.args[0]
caches Cache used by the currently invoked method #root.caches[0].name

Here we'll focus on custom policies.

Case 2

ArticleService adds the following code

@Cacheable(cacheNames = {"cache1"}, key = "#root.target.class.name+'-'+#page+'-'+#pageSize")
public String getPage(int page, int pageSize) {
    String msg = String.format("page-%s-pageSize-%s", page, pageSize);
    System.out.println("from db Get data in:" + msg);
    return msg;
}

New test cases for com.javacode2018.cache.CacheTest

@Test
public void test2() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(MainConfig1.class);
    context.refresh();
    ArticleService articleService = context.getBean(ArticleService.class);
    
    //page=1,pageSize=10 Called twice
    System.out.println(articleService.getPage(1, 10));
    System.out.println(articleService.getPage(1, 10));
    
    //page=2,pageSize=10 Called twice
    System.out.println(articleService.getPage(2, 10));
    System.out.println(articleService.getPage(2, 10));

    {
        System.out.println("Print out below cache1 In Cache key list");
        ConcurrentMapCacheManager cacheManager = context.getBean(ConcurrentMapCacheManager.class);
        ConcurrentMapCache cache1 = (ConcurrentMapCache) cacheManager.getCache("cache1");
        cache1.getNativeCache().keySet().stream().forEach(System.out::println);
    }
}

Run Output

from db Get data in: page-1-pageSize-10
page-1-pageSize-10
page-1-pageSize-10
from db Get data in: page-2-pageSize-10
page-2-pageSize-10
page-2-pageSize-10
Print out below cache1 In Cache key list
com.javacode2018.cache.demo1.ArticleService-getPage-1-10
com.javacode2018.cache.demo1.ArticleService-getPage-2-10

condition property: controls the conditions under which the cache will be used

Sometimes, we may want the query not to go out of the cache, and the results returned are not to be cached, so we can do it by using the condition attribute, which is empty by default, indicating that all invocation scenarios will be cached. Its value is specified by spel expression, and when true, it means trying to get from the cache first; If the cache does not exist, only the method is needed and the method return value is dropped into the cache. When false, do not leave the cache, execute the method directly, and return results will not be dropped into the cache.

The value spel is written like the key attribute.

Case 3

ArticleService adds the following code, the second parameter of the method, cache, which controls whether or not the cache is taken, specifying a condition value of #cache

/**
 * Get articles from article id
 *
 * @param id    Article id
 * @param cache Whether to try to get from the cache
 * @return
 */
@Cacheable(cacheNames = "cache1", key = "'getById'+#id", condition = "#cache")
public String getById(Long id, boolean cache) {
    System.out.println("get data!");
    return "spring cache:" + UUID.randomUUID().toString();
}

For a test case, call the getById method four times, the first two and last cache parameters are true, and the third is false

@Test
public void test3() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(MainConfig1.class);
    context.refresh();
    ArticleService articleService = context.getBean(ArticleService.class);

    System.out.println(articleService.getById(1L, true));
    System.out.println(articleService.getById(1L, true));
    System.out.println(articleService.getById(1L, false));
    System.out.println(articleService.getById(1L, true));
}

Run Output

get data!
spring cache:27e7c11a-26ed-4c8b-8444-78257daafed5
spring cache:27e7c11a-26ed-4c8b-8444-78257daafed5
 get data!
spring cache:05ff7612-29cb-4863-b8bf-d1b7c2c192b7
spring cache:27e7c11a-26ed-4c8b-8444-78257daafed5

As you can see from the output, the first and third times both go into the method, and 2 and 4 walk away from the cache. After the first execution, the result is thrown into the cache, SO 2 and 4 get the same result as the first time.

unless property: controls whether results need to be dropped into the cache

SpEL expression used to deny method cache. Unlike condition, this expression is evaluated after the method is called, so you can reference the result. The default value is "", which means the cache will never be vetoed.

Unless is valid only if condition is empty or true, unless is invalid when condition is false, unless is true, and the result returned by the method will not be thrown into the cache; Unless is false, and the result returned by the method is dropped into the cache.

The value spel is written like the key attribute.

Case 4

In the following case, when the result is null, do not cache it, ArticleService adds the following code

Map<Long, String> articleMap = new HashMap<>();
/**
 * Get the article, first from the cache, if the result is empty, do not put the result in the cache
 *
 * @param id
 * @return
 */
@Cacheable(cacheNames = "cache1", key = "'findById'+#id", unless = "#result==null")
public String findById(Long id) {
    this.articleMap.put(1L, "spring series");
    System.out.println("----Get Articles:" + id);
    return articleMap.get(id);
}

For a test case, findById is called four times, with data in the first two times, null returned in the second two times, and key s from the cache are printed out

@Test
public void test4() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(MainConfig1.class);
    context.refresh();
    ArticleService articleService = context.getBean(ArticleService.class);

    System.out.println(articleService.findById(1L));
    System.out.println(articleService.findById(1L));
    System.out.println(articleService.findById(3L));
    System.out.println(articleService.findById(3L));

    {
        System.out.println("Slow Print Out Below cache1 In Cache key list");
        ConcurrentMapCacheManager cacheManager = context.getBean(ConcurrentMapCacheManager.class);
        ConcurrentMapCache cache1 = (ConcurrentMapCache) cacheManager.getCache("cache1");
        cache1.getNativeCache().keySet().stream().forEach(System.out::println);
    }
}

Run Output

----Get Articles:1
spring series
spring series
----Get Articles:3
null
----Get Articles:3
null
Slow Print Out Below cache1 In Cache key list
findById1

You can see that the result of article id 1 is cached, and file id 3 is not cached.

condition versus unless

There are two points in using the cache:

  1. Query whether there is data in the cache
  2. If there is no data in the cache, the target method is executed and the method results are dropped into the cache.

Conditions and unless intervene in spring.

The above two processes in condition scope, when true, try to get data from the cache, if not, execute the method, and then drop the method return value into the cache; If false, the target method is called directly and the result is not cached.

Unless is only valid if condition is true to determine whether the result in point 2 above should not be dropped into the cache, if true, the result will not be dropped into the cache, if false, the result will be dropped into the cache, and unless can use a spel expression to get the method return value through #result.

@CachePut: Cache results

Effect

@CachePut can also be labeled on a class or method, where the labeled method is called each time, and the method results are dropped into the cache after execution. When labeled on a class, it is equivalent to labeling @CachePut on all methods of the class.

There are three scenarios where results are not dropped into the cache

  1. When the method is thrown outward
  2. When condition evaluates to false
  3. When unless evaluates to true

The source code is similar to Cacheable and contains similar parameters.

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CachePut {
 String[] value() default {};
 String[] cacheNames() default {};
 String key() default "";
 String condition() default "";
 String unless() default "";
}
  • value and cacheNames: To specify a cache name, you can specify multiple
  • Key: cached key, spel expression, written reference to key in @Cacheable
  • The condition:spel expression, written like the condition in @Cacheable, drops the method's return value into the cache when it is empty or when it evaluates to true; Otherwise the result will not be dropped into the cache
  • Unless: unless the condition is empty or the result is true; True: results are not dropped into the cache, false: results are dropped into the cache.

Case 5

To illustrate that they share a cache and that the key is the same, then call the findById method after the add method is executed. You can get the data directly from the cache.

@CachePut(cacheNames = "cache1", key = "'findById'+#id")
public String add(Long id, String content) {
    System.out.println("New Articles:" + id);
    this.articleMap.put(id, content);
    return content;
}

test case

@Test
public void test5() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(MainConfig1.class);
    context.refresh();
    ArticleService articleService = context.getBean(ArticleService.class);

    //Add 3 new articles due to add Method has@CachePut Annotations, so when added, they are automatically dropped into the cache
    articleService.add(1L, "java High concurrency series");
    articleService.add(2L, "Maven Master Series");
    articleService.add(3L, "MySQL Master Series");

    //Then call findById Get to see if the cache will go
    System.out.println("call findById Method, will try to get from the cache");
    System.out.println(articleService.findById(1L));
    System.out.println(articleService.findById(2L));
    System.out.println(articleService.findById(3L));

    {
        System.out.println("Print out below cache1 In Cache key list");
        ConcurrentMapCacheManager cacheManager = context.getBean(ConcurrentMapCacheManager.class);
        ConcurrentMapCache cache1 = (ConcurrentMapCache) cacheManager.getCache("cache1");
        cache1.getNativeCache().keySet().stream().forEach(System.out::println);
    }
}

Run Output

New Articles:1
 New Articles:2
 New Articles:3
call findById Method, will try to get from the cache
java High concurrency series
Maven Master Series
MySQL Master Series
 Slow Print Out Below cache1 In Cache key list
findById3
findById2
findById1

Take a look at the output, and then look at the code for the findById method

@Cacheable(cacheNames = "cache1", key = "'findById'+#id", unless = "#result==null")
public String findById(Long id) {
    this.articleMap.put(1L, "spring series");
    System.out.println("----Get Articles:" + id);
    return articleMap.get(id);
}

There is nothing in the output -- this means that the result obtained by calling the findById method is from the cache.

@CacheEvict: Cache Cleanup

Effect

To clear the cache, @CacheEvict can also be labeled on a class or method, which will clear the specified cache when the target method is called. When labeled on a class, it is equivalent to labeling @CacheEvict on all methods of the class.

Take a look at the source code, and you'll look at the comments for each parameter in more detail.

public @interface CacheEvict {

    /**
     * cache Name, as cacheNames
     */
    String[] value() default {};

    /**
     * cache Name, as cacheNames
     */
    String[] cacheNames() default {};

    /**
     * Cached key, written with reference to the key in the @Cacheable comment above
     */
    String key() default "";

    /**
     * @CacheEvict The condition under which the comment takes effect, which is a spel expression, written with reference to the condition in the @Cacheable comment above
     */
    String condition() default "";

    /**
     * Whether to clean up all cache information in the cache specified by cacheNames, default is false
     * You can think of a cache as a HashMap, which is equivalent to HashMap.clear() when allEntries is true
     * When allEntries is false, only the data corresponding to the key is destroyed, which is equivalent to HashMap.remove(key)
     */
    boolean allEntries() default false;

    /**
     * What to do with the cleanup operation (after the or method executes successfully before the method executes)
     * true: @CacheEvict Perform cleanup before labeling method executes
     * false: @CacheEvict After the labeled method has been successfully executed, the cleanup operation is performed, and when the method pops up an exception, the cleanup operation is not performed
     */
    boolean beforeInvocation() default false;
}

condition attribute

The condition under which the @CacheEvict comment takes effect, which is a spel expression, written with reference to the condition in the @Cacheable comment above

Which caches will be cleared?

By default, the cache information specified by the key parameter in the cache specified by cacheNames is cleared.

However, when allEntries is true, all cache information in the cache specified by cacheNames is cleared.

When exactly do you clear the cache?

This is controlled by the beforeInvocation parameter, which defaults to false and will perform a cleanup operation after the target method has successfully executed. If the method throws an exception outside, it will not perform a cleanup operation.

If beforeInvocation   To true, the cache cleanup operation is performed before the method is executed, and it is not executed after the method is executed.

Case 6

A new method in ArticleService, labeled with @CacheEvict, cleans up the cache information of key=findById+parameter id in cache1 after execution. Note that the values of the two parameters, cacheNames and keys, are the same as those of the two parameters in findById.

@CacheEvict(cacheNames = "cache1", key = "'findById'+#id") //@1
public void delete(Long id) {
    System.out.println("Delete article:" + id);
    this.articleMap.remove(id);
}

Add a new test case with clear comments and no explanation

@Test
public void test6() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(MainConfig1.class);
    context.refresh();
    ArticleService articleService = context.getBean(ArticleService.class);

    //First invocation findById,If not in the cache, call the method and drop the result into the cache
    System.out.println(articleService.findById(1L));
    //Second call findById,Cache exists and is fetched directly from the cache
    System.out.println(articleService.findById(1L));

    //Perform the deletion operation, delete Method has@CacheEvict Method, clears the cache
    articleService.delete(1L);

    //Call Again findById Method, if it finds that the cache is missing, it calls the target method
    System.out.println(articleService.findById(1L));
}

Run Output

----Get Articles:1
spring series
spring series
 Delete article:1
----Get Articles:1
spring series

The findById was called three times, the first time it was not in the cache, so it went inside the method, and then dropped the result into the cache. The second time it was in the cache, so it was fetched from the cache, and then the delete method was executed. After the execution of this method, the article information with Article id 1L in the cache was cleared, and the third findById method was executed. No data was found in the cache. Then it goes inside the target method, which outputs the content.

@Caching: Cache annotation group

When we use more than one of the @Cacheable, @CachePut, and @CacheEvic annotations on a class or on the same method, we can use the @Caching annotation to do this.

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Caching {

 Cacheable[] cacheable() default {};

 CachePut[] put() default {};

 CacheEvict[] evict() default {};

}

@CacheConfig: Extract Public Configuration

This annotation is labeled on a class and can be extracted from the common parameters of several other cache annotations (@Cacheable, @CachePut, and @CacheEvic) and placed in @CacheConfig.

For example, when there are many methods in a class that need cached annotations (@Cacheable, @CachePut, and @CacheEvic), you can see the source code of these three annotations. They have many common attributes, such as cacheNames, keyGenerator, cacheManager, cacheResolver. If the values of these attributes are the same, you can extract them. Put it in @CacheConfig, but these comments (@Cacheable, @CachePut, and @CacheEvic) can also specify the value of the property to override the value of the property in @CacheConfig.

@CacheConfig(cacheNames = "cache1")
public class ArticleService {
    @Cacheable(key = "'findById'+#id")
    public String findById(Long id) {
        this.articleMap.put(1L, "spring series");
        System.out.println("----Get Articles:" + id);
        return articleMap.get(id);
    }
}

principle

Caching in spring is mainly achieved by AOP in spring. Proxy objects are created by Aop for bean s that need to use caching, and the execution of target methods is intercepted by proxy objects to achieve caching function.

Focus on the @EnableCaching comment, which you can see from the @Import comment

@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {
}

Eventually, a proxy object will be created for bean s that need to use the cache, and an interceptor, org.springframework.cache.interceptor.CacheInterceptor, will be added to the proxy. The invoke method in this class is critical and will block the execution of all cache-related target methods. You can take a closer look at it.

summary

Spring series has 40 articles so far, it is really not easy to stick with me until now.

Friends who haven't finished reading, I suggest you read them all in order. Articles are best read in order. The knowledge points before and after are dependent.

If you've read all the previous articles, then the principles of this article are easy to understand without me.

Case Source

https://gitee.com/javacode2018/spring-series

All the case code for Pedestrian java will be put on this one in the future. watch for everyone to keep an eye on the dynamics.

Source: https://mp.weixin.qq.com/s?__ Biz=MzA5MTkxMDQ4MQ==&mid=2648936253&idx=2&sn=fe74d8130a85dd705a80092b2ba48c&scene=21#wechat_ Redirect

Posted by Goofan on Fri, 05 Nov 2021 09:04:50 -0700