“This article has participated in the call for good writing activities, click to view: the back end, the big front end double track submission, 20,000 yuan prize pool waiting for you to challenge!”

The last post covered the basic use of the caching annotation @cacheable @cacheevit @cacheput in Spring, Let’s take a look at a little more advanced (Spring series Cacheable @cacheevit @cacheput using gestures)

  • Key Generation Policy
  • Timeout specified

I. Project environment

1. Project dependencies

This project is developed by SpringBoot 2.2.1.RELEASE + Maven 3.5.3 + IDEA + redis5.0

Open a Web service for testing

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

II. Expand knowledge

1. Key generation policy

For the @cacheable annotation, there are two parameters used to assemble the cached key

  • CacheNames/Value: Similar to cache prefixes
  • Key: SpEL expression that usually generates the final cache key from passing arguments

Default redisKey = cacheNames::key (note the two colons in the middle)

Such as

/** * If no key is specified, the default policy is used.@linkOrg. Springframework. Cache. The interceptor. SimpleKeyGenerator} to generate key * < p > * the corresponding key is: k1: : id value -- > with cacheNames * *@param id
 * @return* /
@Cacheable(value = "k1")
public String key1(int id) {
    return "defaultKey:" + id;
}
Copy the code

The cache key is generated by SimpleKeyGenerator by default. For example, if id=1, the corresponding cache key is k1::1

What if there are no arguments, or multiple arguments?

/**
 * redis_key :  k2::SimpleKey[]
 *
 * @return* /
@Cacheable(value = "k0")
public String key0(a) {
    return "key0";
}

/**
 * redis_key :  k2::SimpleKey[id,id2]
 *
 * @param id
 * @param id2
 * @return* /
@Cacheable(value = "k2")
public String key2(Integer id, Integer id2) {
    return "key1" + id + "_" + id2;
}


@Cacheable(value = "k3")
public String key3(Map map) {
    return "key3" + map;
}
Copy the code

Then write a test case

@RestController
@RequestMapping(path = "extend")
public class ExtendRest {
    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private ExtendDemo extendDemo;

    @GetMapping(path = "default")
    public Map<String, Object> key(int id) {
        Map<String, Object> res = new HashMap<>();
        res.put("key0", extendDemo.key0());
        res.put("key1", extendDemo.key1(id));
        res.put("key2", extendDemo.key2(id, id));
        res.put("key3", extendDemo.key3(res));

        // Get all the cache keys out
        Set<String> keys = (Set<String>) redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
            Set<byte[]> sets = connection.keys("k*".getBytes());
            Set<String> ans = new HashSet<>();
            for (byte[] b : sets) {
                ans.add(new String(b));
            }
            return ans;
        });

        res.put("keys", keys);
        returnres; }}Copy the code

After access, the output is as follows

{
    "key1": "defaultKey:1"."key2": "key11_1"."key0": "key0"."key3": "key3{key1=defaultKey:1, key2=key11_1, key0=key0}"."keys": [
        "K2: : SimpleKey [1, 1]." "."k1::1"."k3::{key1=defaultKey:1, key2=key11_1, key0=key0}"."k0::SimpleKey []"]}Copy the code

The subtotal

  • Single parameter:cacheNames::arg
  • No arguments:cacheNames::SimpleKey [], followed bySimpleKey []To supplement the
  • Many parameters:cacheNames::SimpleKey [arg1, arg2...]
  • Non-base objects:cacheNames::obj.toString()

2. Customize the key generation policy

If you want to use a custom key generation strategy, simply inherit KeyGenerator and declare it as a bean

@Component("selfKeyGenerate")
public static class SelfKeyGenerate implements KeyGenerator {
    @Override
    public Object generate(Object target, Method method, Object... params) {
        return target.getClass().getSimpleName() + "#" + method.getName() + "(" + JSON.toJSONString(params) + ")"; }}Copy the code

The keyGenerator in the annotations is then used to specify the key generation strategy where it is used

/** * The corresponding redisKey is get vv::ExtendDemo#selfKey([id]) **@param id
 * @return* /
@Cacheable(value = "vv", keyGenerator = "selfKeyGenerate")
public String selfKey(int id) {
    return "selfKey:" + id + "-- >" + UUID.randomUUID().toString();
}
Copy the code

The test case

@GetMapping(path = "self")
public Map<String, Object> self(int id) {
    Map<String, Object> res = new HashMap<>();
    res.put("self", extendDemo.selfKey(id));
    Set<String> keys = (Set<String>) redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
        Set<byte[]> sets = connection.keys("vv*".getBytes());
        Set<String> ans = new HashSet<>();
        for (byte[] b : sets) {
            ans.add(new String(b));
        }
        return ans;
    });
    res.put("keys", keys);
    return res;
}
Copy the code

The cache key is placed in the keys that return the result, and the output is as expected

{
    "keys": [
        "vv::ExtendDemo#selfKey([1])"]."self": "selfKey:1 --> f5f8aa2a-0823-42ee-99ec-2c40fb0b9338"
}
Copy the code

3. Cache expiration time

No expiration time is set for all the above caches. In actual service scenarios, no expiration time is set for caches. But more need to set a TTL, for Spring cache annotations, native does not provide an additional configuration to specify TTL, if we want to specify TTL, can be done through RedisCacheManager

private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
    // Set the JSON serialization
    Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
    ObjectMapper om = new ObjectMapper();
    om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    jackson2JsonRedisSerializer.setObjectMapper(om);

    RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
    redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
            RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)).
            // Set the expiration time
            entryTtl(Duration.ofSeconds(seconds));

    return redisCacheConfiguration;
}
Copy the code

Above is a method to set a RedisCacheConfiguration with two points

  • Serialization: Use JSON to serialize cached content
  • Expiration time: Set the expiration time according to the parameter

If you want to customize the configuration for a particular key, do the following

private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap(a) {
    Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>(8);
    // Customize the cache time
    // This k0 represents cacheNames/ Value in the cache annotation
    redisCacheConfigurationMap.put("k0".this.getRedisCacheConfigurationWithTtl(60 * 60));
    return redisCacheConfigurationMap;
}
Copy the code

Finally, define the redisc Manager we need

@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
    return new RedisCacheManager(
            RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
            // The default policy is used for unconfigured keys
            this.getRedisCacheConfigurationWithTtl(60),
            // Specify the key policy
            this.getRedisCacheConfigurationMap()
    );
}
Copy the code

Add the information that returns TTL based on the previous test case

private Object getTtl(String key) {
    return redisTemplate.execute(new RedisCallback() {
        @Override
        public Object doInRedis(RedisConnection connection) throws DataAccessException {
            returnconnection.ttl(key.getBytes()); }}); }@GetMapping(path = "default")
public Map<String, Object> key(int id) {
    Map<String, Object> res = new HashMap<>();
    res.put("key0", extendDemo.key0());
    res.put("key1", extendDemo.key1(id));
    res.put("key2", extendDemo.key2(id, id));
    res.put("key3", extendDemo.key3(res));

    Set<String> keys = (Set<String>) redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
        Set<byte[]> sets = connection.keys("k*".getBytes());
        Set<String> ans = new HashSet<>();
        for (byte[] b : sets) {
            ans.add(new String(b));
        }
        return ans;
    });

    res.put("keys", keys);

    Map<String, Object> ttl = new HashMap<>(8);
    for (String key : keys) {
        ttl.put(key, getTtl(key));
    }
    res.put("ttl", ttl);
    return res;
}
Copy the code

The following result is displayed: Note the TTL expiration time

4. Customize the expiration time extension

Although the above can achieve the failure time specified, but it is still not very cool to use, or the global setting of a unified failure time; Or it’s hard coded in the code to specify that the expiration time is separate from where the cache is defined, which is not intuitive

Here is a case that sets the expiration time directly in the annotation

Use case as follows

/** * The customized RedisCacheManager parses value, where = indicates expiration time *@param key
 * @return* /
@Cacheable(value = "ttl=30")
public String ttl(String key) {
    return "k_" + key;
}
Copy the code

User-defined policies are as follows:

  • In value, cacheName is displayed on the left of the equals sign, and expiration time is displayed on the right of the equals sign

To implement this logic, extend a custom RedisCacheManager, such as

public class TtlRedisCacheManager extends RedisCacheManager {
    public TtlRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
        super(cacheWriter, defaultCacheConfiguration);
    }

    @Override
    protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
        String[] cells = StringUtils.delimitedListToStringArray(name, "=");
        name = cells[0];
        if (cells.length > 1) {
            long ttl = Long.parseLong(cells[1]);
            // Set the cache expiration time according to the pass parameter
            cacheConfig = cacheConfig.entryTtl(Duration.ofSeconds(ttl));
        }
        return super.createRedisCache(name, cacheConfig); }}Copy the code

Rewrite the createRedisCache logic, parse the invalid time according to name;

The register is used as above, declaring a bean object as Spring

@Primary
@Bean
public RedisCacheManager ttlCacheManager(RedisConnectionFactory redisConnectionFactory) {
    return new TtlRedisCacheManager(RedisCacheWriter.lockingRedisCacheWriter(redisConnectionFactory),
            // Default cache configuration
            this.getRedisCacheConfigurationWithTtl(60));
}
Copy the code

The test case is as follows

@GetMapping(path = "ttl")
public Map ttl(String k) {
    Map<String, Object> res = new HashMap<>();
    res.put("execute", extendDemo.ttl(k));
    res.put("ttl", getTtl("ttl::" + k));
    return res;
}
Copy the code

The verification results are as follows

5. Summary

This will basically introduce the common posture of caching annotations in Spring, whether it is the use of several annotations case, or a custom key strategy, expiration time specified, purely from the perspective of use, can basically meet our daily requirements of the scene

The following is a general abstraction for cached annotations

Cache annotations

  • @Cacheable: Cache exists, then cache access; Otherwise, the method is executed and the result returned is written to the cache
  • @CacheEvit: invalidation cache
  • @CachePutUpdate cache
  • @Caching: all annotation combination

Configuration parameters

  • cacheNames/value: can be interpreted as a cache prefix
  • key: can be understood as the cache key variable, support SpEL expression
  • keyGenerator: key Assembly policy
  • condition/unless: Specifies whether the cache is available

Default cache KE policy y

The cacheNames below are the cache prefixes defined in the annotations, fixed with two semicolons

  • Single parameter:cacheNames::arg
  • No arguments:cacheNames::SimpleKey [], followed bySimpleKey []To supplement the
  • Many parameters:cacheNames::SimpleKey [arg1, arg2...]
  • Non-base objects:cacheNames::obj.toString()

Cache expiration time

This article introduces two ways to set the TTL TTL. One is centralized configuration by setting a RedisCacheConfiguration

Another is to extend the RedisCacheManager class for custom cacheNames extension resolution

This is the end of Spring cache notes, I am a gray, welcome to pay attention to long grass’s public number a gray blog

III. Can’t miss the source code and related knowledge points

0. Project

Series of blog posts

  • The Spring family cache annotation @cacheable @cacheevit @cacheput uses gestures

The source code

  • Project: github.com/liuyueyi/sp…
  • Source: github.com/liuyueyi/sp…

1. An ashy Blog

As far as the letter is not as good, the above content is purely one’s opinion, due to the limited personal ability, it is inevitable that there are omissions and mistakes, if you find bugs or have better suggestions, welcome criticism and correction, don’t hesitate to appreciate

Below a gray personal blog, record all the study and work of the blog, welcome everyone to go to stroll

  • A grey Blog Personal Blog blog.hhui.top
  • A Grey Blog-Spring feature Blog Spring.hhui.top