“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@CachePut
Update cache@Caching
: all annotation combination
Configuration parameters
cacheNames/value
: can be interpreted as a cache prefixkey
: can be understood as the cache key variable, support SpEL expressionkeyGenerator
: key Assembly policycondition/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