Spring's Road to God Chapter 40: Cache Use (@EnableCaching, @Cacheable, @CachePut, @CacheEvict, @Caching, @CacheConfig)

Spring's Road to God Chapter 40: Cache Use (@EnableCaching, @Cacheable, @CachePut, @CacheEvict, @Caching, @CacheConfig)

This article mainly explains the use of cache in spring.

background

Everyone knows about caching, it is mainly used to improve the query speed of the system.

For example, product details information in e-commerce, this information usually does not change frequently but is accessed frequently, we can take this information out of the db and put it in the cache (such as redis, local memory), when getting it, Get it from the cache first. If there is no cache, get it from the db, and then throw it into the cache. When the product information is changed, you can delete the information in the cache or throw the latest data into the cache. .

Spring provides a whole set of caching solutions, which are very easy to use. The caching is mainly used through annotations. There are 5 commonly used annotations, and we will introduce them one by one.

This article will use a lot of spel expressions. If you are not familiar with this piece of advice, take a look first: Detailed Explanation of Spel in Spring

@EnableCaching: enable caching

To enable the caching function, this annotation needs to be added to the configuration class. After this annotation, spring knows that you need to use the caching function, and other caching-related annotations will be effective. Spring is mainly implemented through aop, through aop To intercept the method that needs to use the cache to realize the cache function.

@Cacheable: Give cache function

effect

@Cacheable can be marked on a method or on a class. When marked on a method, it means that the method supports caching. When marked on a class, it means that all methods of this class support caching. For a method that supports caching, Spring will cache its return value after it is called to ensure that the next time the method is executed with the same parameters, the result can be obtained directly from the cache without the need to execute the method again. Spring caches the return value of a method with a key-value pair, and the value is the return result of the method. As for the key, Spring supports two strategies, the default strategy and the custom strategy, which will be explained later. It should be noted that when a method that supports caching is called inside the object, the caching function will not be triggered. @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 attribute: specify the Cache name

The value has the same function as the cacheNames attribute, and one of them must be specified, indicating which Cache the return value of the current method will be cached on, corresponding to the name of the Cache. It can be one Cache or multiple Caches, and it is an array when multiple Caches need to be specified.

Cache can be imagined as a HashMap. There can be many Cache in the system, and each Cache has a name. You need to specify which cache you want to put the return value of the method in by the name of the cache.

Case 1

The following list method adds the function of caching, and puts the result cache1in the cache.

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("获取文章列表!");
        return Arrays.asList("spring", "mysql", "java高并发", "maven");
    }
}

Here is a configuration class MainConfig1that must be annotated@EnableCaching to enable caching .

Then a bean needs to be defined in the configuration class: Cache Manager, the type is CacheManager, CacheManagerthis is an interface, there are several implementations (such as using redis, ConcurrentMap to store cache information), here we use ConcurrentMapCacheManager, internally use ConcurrentHashMap to directly store cache information In the local jvm memory, but the online environment is generally in the form of a cluster, which can be realized through redis. The next article introduces the integration of spring cache 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:缓存管理器
    @Bean
    public CacheManager cacheManager() {
    
    
        //创建缓存管理器(ConcurrentMapCacheManager:其内部使用ConcurrentMap实现的),构造器用来指定缓存的名称,可以指定多个
        ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager("cache1");
        return cacheManager;
    }

}

listCome to a test class, call the 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

获取文章列表!
[spring, mysql, java高并发, maven]
[spring, mysql, java高并发, maven]

As can be seen from the first line, the first time it entered the list method, the second time it did not enter the list method, but got it from the cache.

key attribute: custom key

The key attribute is used to specify the key corresponding to the return result of the Spring cache method. As mentioned above, you can understand Cache as a hashMap, and the cache is stored in the hashmap in the form of key->value, and value is the value that needs to be cached (that is, the method The return value)

The key attribute supports SpEL expressions; when we do not specify this attribute, Spring will use the default strategy to generate the key (org.springframework.cache.interceptor.SimpleKeyGenerator), and the method parameter will create the key by default.

Custom strategy means that we can specify our key through SpEL expression. The SpEL expression here can use method parameters and their corresponding attributes. When using method parameters, we can directly use "#parameter name" or "#p parameter index".

Spring also provides us with a root object that can be used to generate a key, through which we can obtain the following information.

attribute name describe example
methodName current method name #root.methodName
method current method #root.method.name
target the currently called object #root.target
targetClass The class of the currently called object #root.targetClass
args array of current method parameters #root.args[0]
caches Cache used by the currently called method #root.caches[0].name

Here we mainly look at custom strategies.

Case 2

Add the following code to ArticleService

@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("从db中获取数据:" + msg);
    return msg;
}

com.javacode2018.cache.CacheTestAdd test case

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

    {
    
    
        System.out.println("下面打印出cache1缓存中的key列表");
        ConcurrentMapCacheManager cacheManager = context.getBean(ConcurrentMapCacheManager.class);
        ConcurrentMapCache cache1 = (ConcurrentMapCache) cacheManager.getCache("cache1");
        cache1.getNativeCache().keySet().stream().forEach(System.out::println);
    }
}

run output

从db中获取数据:page-1-pageSize-10
page-1-pageSize-10
page-1-pageSize-10
从db中获取数据:page-2-pageSize-10
page-2-pageSize-10
page-2-pageSize-10
下面打印出cache1缓存中的key列表
com.javacode2018.cache.demo1.ArticleService-getPage-1-10
com.javacode2018.cache.demo1.ArticleService-getPage-2-10

condition attribute: control the usage conditions of the cache

Sometimes, we may want the query not to go to the cache, and the returned results should not be cached at the same time, then we can use the condition attribute to achieve it. The condition attribute is empty by default, which means that all call situations will be cached, and its value is passed through the spel expression To specify, when it is true , it means to try to get it from the cache first; if it does not exist in the cache, only the method is needed, and the method return value is thrown into the cache; when it is false , the cache is not used, and the method is directly executed , and the returned result will not be thrown into the cache.

Its value spel is written similarly to the key attribute.

Case 3

Add the following code to ArticleService. The second parameter cache of the method is used to control whether to go to the cache, and the value of condition is specified as#cache

/**
 * 通过文章id获取文章
 *
 * @param id    文章id
 * @param cache 是否尝试从缓存中获取
 * @return
 */
@Cacheable(cacheNames = "cache1", key = "'getById'+#id", condition = "#cache")
public String getById(Long id, boolean cache) {
    
    
    System.out.println("获取数据!");
    return "spring缓存:" + UUID.randomUUID().toString();
}

Come to a test case, call the getById method 4 times, the first 2 times and the last cache parameter are true, and the third time 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

获取数据!
spring缓存:27e7c11a-26ed-4c8b-8444-78257daafed5
spring缓存:27e7c11a-26ed-4c8b-8444-78257daafed5
获取数据!
spring缓存:05ff7612-29cb-4863-b8bf-d1b7c2c192b7
spring缓存:27e7c11a-26ed-4c8b-8444-78257daafed5

It can be seen from the output that the first and third times both entered the method, while 2 and 4 left the cache, and the results were thrown into the cache after the first execution, so 2 and 4 are 2 The result obtained the second time is the same as the first time.

unless attribute: controls whether the result needs to be thrown into the cache

SpEL expression used to overrule method caching. Unlike condition, this expression is evaluated after the method has been called, so the result can be referenced. The default value is "", which means that the cache is never deprecated.

The premise is that if the condition is empty or true, unless is valid. When the condition is false, unless is invalid. If unless is true, the method return result will not be thrown into the cache; unless is false, the method return result will be thrown into the cache middle.

Its value spel is written similarly to the key attribute.

Case 4

Let's take a case below. When the returned result is null, do not cache the result. Add the following code to ArticleService

Map<Long, String> articleMap = new HashMap<>();
/**
 * 获取文章,先从缓存中获取,如果获取的结果为空,不要将结果放在缓存中
 *
 * @param id
 * @return
 */
@Cacheable(cacheNames = "cache1", key = "'findById'+#id", unless = "#result==null")
public String findById(Long id) {
    
    
    this.articleMap.put(1L, "spring系列");
    System.out.println("----获取文章:" + id);
    return articleMap.get(id);
}

Here is a test case, call findById 4 times, the first 2 times have data, the latter 2 times return null, and print out the key in the cache

@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("下面打印出缓cache1缓存中的key列表");
        ConcurrentMapCacheManager cacheManager = context.getBean(ConcurrentMapCacheManager.class);
        ConcurrentMapCache cache1 = (ConcurrentMapCache) cacheManager.getCache("cache1");
        cache1.getNativeCache().keySet().stream().forEach(System.out::println);
    }
}

run output

----获取文章:1
spring系列
spring系列
----获取文章:3
null
----获取文章:3
null
下面打印出缓cache1缓存中的key列表
findById1

It can be seen that the result of the article id 1 is cached, but the result of the file id 3 is not cached.

condition vs unless

There are 2 points in the process of using the cache:

  1. Query whether there is data in the cache
  2. If there is no data in the cache, execute the target method, and then throw the method result into the cache.

These two points are intervened in spring through condition and unless.

The two processes above the condition scope, when true , will try to get data from the cache, if not, will execute the method, and then throw the method return value into the cache; if it is false, directly call the target method, and Results are not placed in cache.

The unless is only valid when the condition is true . It is used to judge whether the result should not be thrown into the cache in the second point above. If it is true, the result will not be thrown into the cache. If it is false, the result will be Throw it into the cache, and unless you can use the spel expression to get the method return value through **#result**.

@CachePut: Put the result into the cache

effect

@CachePut can also be marked on a class or method. The marked method will be called every time, and then after the method is executed, the method result will be thrown into the cache; when marked on a class, it is equivalent to all methods of the class Annotated @CachePut.

There are 3 cases, the result will not be thrown into the cache

  1. When the method throws out
  2. When the evaluation result of condition is false
  3. When the evaluation result of unless is 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: used to specify the cache name, you can specify multiple
  • key: cached key, spel expression, refer to the key in @Cacheable
  • condition: spel expression, written in the same way as the condition in @Cacheable, when it is empty or the calculation result is true , the return value of the method will be thrown into the cache; otherwise, the result will not be thrown into the cache
  • unless: When the condition is empty or the calculation result is true, unless will take effect; true : the result will not be thrown to the cache, false : the result will be thrown to the cache.

Case 5

Let’s take a case to implement the operation of adding an article, and then throw the article into the cache. Note that the cacheNames and key parameters in @CachePut below are the same as those in @Cacheable on the findById method in Case 4, indicating that they share a cache. The same is true for the key, so after the add method is executed, call the findById method to get the data directly from the cache.

@CachePut(cacheNames = "cache1", key = "'findById'+#id")
public String add(Long id, String content) {
    
    
    System.out.println("新增文章:" + 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);

    //新增3个文章,由于add方法上面有@CachePut注解,所以新增之后会自动丢到缓存中
    articleService.add(1L, "java高并发系列");
    articleService.add(2L, "Maven高手系列");
    articleService.add(3L, "MySQL高手系列");

    //然后调用findById获取,看看是否会走缓存
    System.out.println("调用findById方法,会尝试从缓存中获取");
    System.out.println(articleService.findById(1L));
    System.out.println(articleService.findById(2L));
    System.out.println(articleService.findById(3L));

    {
    
    
        System.out.println("下面打印出cache1缓存中的key列表");
        ConcurrentMapCacheManager cacheManager = context.getBean(ConcurrentMapCacheManager.class);
        ConcurrentMapCache cache1 = (ConcurrentMapCache) cacheManager.getCache("cache1");
        cache1.getNativeCache().keySet().stream().forEach(System.out::println);
    }
}

run output

新增文章:1
新增文章:2
新增文章:3
调用findById方法,会尝试从缓存中获取
java高并发系列
Maven高手系列
MySQL高手系列
下面打印出缓cache1缓存中的key列表
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系列");
    System.out.println("----获取文章:" + id);
    return articleMap.get(id);
}

There is no ----such content in the output, indicating that the result obtained by calling the findById method is obtained from the cache.

@CacheEvict: cache cleaning

effect

Used to clear the cache, @CacheEvict can also be marked on the class or method. If it is marked on the method, when the target method is called, the specified cache will be cleared; when marked on the class, it is equivalent to all methods in the class @CacheEvict is marked on it.

Let's take a look at the source code, and take a look at the comments of each parameter in detail.

public @interface CacheEvict {
    
    

    /**
     * cache的名称,和cacheNames效果一样
     */
    String[] value() default {
    
    };

    /**
     * cache的名称,和cacheNames效果一样
     */
    String[] cacheNames() default {
    
    };

    /**
     * 缓存的key,写法参考上面@Cacheable注解的key
     */
    String key() default "";

    /**
     * @CacheEvict 注解生效的条件,值为spel表达式,写法参考上面 @Cacheable注解中的condition
     */
    String condition() default "";

    /**
     * 是否清理 cacheNames 指定的缓存中的所有缓存信息,默认是false
     * 可以将一个cache想象为一个HashMap,当 allEntries 为true的时候,相当于HashMap.clear()
     * 当 allEntries 为false的时候,只会干掉key对应的数据,相当于HashMap.remove(key)
     */
    boolean allEntries() default false;

    /**
     * 何事执行清除操作(方法执行前 or 方法执行成功之后)
     * true:@CacheEvict 标注的方法执行之前,执行清除操作
     * false:@CacheEvict 标注的方法执行成功之后,执行清除操作,当方法弹出异常的时候,不会执行清除操作
     */
    boolean beforeInvocation() default false;
}

condition attribute

The conditions under which the @CacheEvict annotation takes effect, the value is a spel expression, and the writing method refers to the condition in the @Cacheable annotation above

Which caches are cleared?

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

But when allEntries is true, all cache information in the cache specified by cacheNames will be cleared.

When should the cache be cleared?

This is controlled by the beforeInvocation parameter, which is false by default. By default , the cleanup operation will be performed after the target method is successfully executed. If the method throws an exception, the cleanup operation will not be performed;

If beforeInvocation is true , the cache cleaning operation will be performed before the method is executed, and will not be executed after the method is executed.

Case 6

Add a new method in ArticleService and mark it with @CacheEvict. After this method is executed, the key=findById+参数idcache information in cache1 will be cleared. Note that the values ​​of the parameters cacheNames and key are the same as those of the parameters in findById.

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

New test cases are added, the comments are clearer, so I won’t explain them

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

    //第1次调用findById,缓存中没有,则调用方法,将结果丢到缓存中
    System.out.println(articleService.findById(1L));
    //第2次调用findById,缓存中存在,直接从缓存中获取
    System.out.println(articleService.findById(1L));

    //执行删除操作,delete方法上面有@CacheEvict方法,会清除缓存
    articleService.delete(1L);

    //再次调用findById方法,发现缓存中没有了,则会调用目标方法
    System.out.println(articleService.findById(1L));
}

run output

----获取文章:1
spring系列
spring系列
删除文章:1
----获取文章:1
spring系列

Call findById 3 times, the first time, there is no in the cache, so it enters the method, and then throws the result into the cache, the second time it is in the cache, so it is obtained from the cache, and then the delete method is executed, this method executes After the completion, the article information with the article id of 1L in the cache will be cleared, and finally the findById method is executed for the third time. At this time, no data is found in the cache, and then it enters the target method, and the target method outputs the content ----.

@Caching: cache annotation group

When we use multiple annotations of @Cacheable, @CachePut and @CacheEvic on a class or on the same method at the same time, we can use the @Caching annotation to achieve 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 common configuration

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

For example, when there are many methods in a class that need to use these cache annotations (@Cacheable, @CachePut, and @CacheEvic), you can take a look at the source code of these three annotations. They have many common attributes, such as: cacheNames, keyGenerator , cacheManager, cacheResolver, if these attribute values ​​are the same, they can be extracted and placed in @CacheConfig, but these annotations (@Cacheable, @CachePut and @CacheEvic) can also specify the value of the attribute in @CacheConfig attribute values ​​are overwritten.

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

principle

The cache in spring is mainly realized by aop in spring. Through Aop, a proxy object is created for the bean that needs to use the cache, and the execution of the target method is intercepted by the proxy object to realize the cache function.

The focus is on this annotation, which can be seen @EnableCachingfrom this annotation@Import

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

Finally, a proxy object will be created for the bean that needs to use the cache, and an interceptor will be added to the proxy. The method org.springframework.cache.interceptor.CacheInterceptorin this class invokeis the key, and it will intercept the execution of all cache-related target methods. You can take a closer look.

Summarize

There are already 40 articles in the Spring series, and it is really not easy to stick with me until now.

For those who haven’t finished reading it, I suggest that you read it in order. It is best to read the articles in order, and the knowledge points before and after are dependent.

If you have read the previous articles, then I don’t need to introduce the principle of this article, you can easily understand it yourself.

Case source code

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

Passerby A All java case codes will be put on this in the future, everyone watch it, you can continue to pay attention to the dynamics.

Source: https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648936253&idx=2&sn=fe74d8130a85dd70405a80092b2ba48c&scene=21#wechat_redirect

Guess you like

Origin blog.csdn.net/china_coding/article/details/130778193