The introduction

Caffeine is also the default local cache used by SpringBoot. Today, we will look at how Caffeine integrates with SpringBoot.

Integration of caffeine

Caffeine integrates with SpringBoot in two ways:

  • One is that we introduce it directlyCaffeineRely on, and then useCaffeineMethod implements caching. Equivalent to using native apis
  • The introduction ofCaffeineSpring CacheRely on, useSpringCacheAnnotation methods implement caching. SpringCache has packaged Caffeine for us

Pom files are imported

<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>
Copy the code

The first way

A Cache is first configured, and a Cache object is constructed using the constructor pattern. Subsequent additions and deletions are based on this Cache object.

@Configuration
public class CacheConfig {
    @Bean
    public Cache<String, Object> caffeineCache(a) {
        return Caffeine.newBuilder()
                // Sets the expiration of a fixed time after the last write or access
                .expireAfterWrite(60, TimeUnit.SECONDS)
                // The initial cache size
                .initialCapacity(100)
                // Maximum number of entries in the cache
                .maximumSize(1000)
                .build();
    }
Copy the code

The first way, which we won’t go through, is basically usingcaffeineCacheTo do this for your own businessUsing this way is intrusive to the code.

The second way

  • Need to start the SpingBoot class labelEnableCachingNote, this thing is the same as many frameworks, such as our cuisine integrationdubboI also need to label it@EnableDubboleAnnotations, etc.
    @SpringBootApplication
    @EnableCaching
    public class DemoApplication {
        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);
        }
Copy the code
  • inapplication.ymlConfigure the type of cache we use, expiration time, cache policy, and so on.
spring:
  profiles:
    active: dev
  cache:
    type: CAFFEINE
    caffeine:
      spec: maximumSize=500,expireAfterAccess=600s
Copy the code

If you are not comfortable with this configuration, of course you can use JavaConfig configuration instead of configuration files.

@Configuration
public class CacheConfig {
        @Bean
        public CacheManager cacheManager(a) {
            CaffeineCacheManager cacheManager = new CaffeineCacheManager();
            cacheManager.setCaffeine(Caffeine.newBuilder()
                    // Sets the expiration of a fixed time after the last write or access
                    .expireAfterAccess(600, TimeUnit.SECONDS)
                    // The initial cache size
                    .initialCapacity(100)
                    // Maximum number of entries in the cache
                    .maximumSize(500));
            return cacheManager;
        }
Copy the code

Now how do you use this cache in your 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("Delete id, key as" + id + "Data cache");
    }

    @Override
    @Cacheable(value = "user",key = "#id")
    public UserDTO getUserById(Long id) {
        return userRepository.findOne(id);
    }

Copy the code

There are several annotations in the code above: @cacheput, @cacheevict, @cacheable. All we need to do is label our methods with these annotations and we can use caching. Let’s look at each of these annotations.

@Cacheable

@cacheable can be marked on a class or a method. When marked on a class, it indicates that all methods in that class support caching. Similarly, when marked on a method, it indicates that the method supports caching. For example, the getUserById method in our code has no data in the cache for the first time, so we will query DB, but the second time we will query DB, but directly get the result from the cache and return it.

The value attribute
  • @CacheablethevalueProperty must be specified to indicate which return value of the current method will be cachedCacheTheta of theta corresponds to thetaCacheThe name of the.
key
  • @CacheablethekeyThere are two ways to do it one is to show ourselves to specify ourkey, there is also a default build strategy, the default build strategy isSimpleKeyGeneratorThis class, this generationkeyWe can take a look at its source code:
public static Object generateKey(Object... params) {
        // If the method has no argument key, it is a new SimpleKey().
		if (params.length == 0) {
			return SimpleKey.EMPTY;
		}
		// If the method has only one argument, key is the current argument
		if (params.length == 1) {
			Object param = params[0];
			if(param ! =null && !param.getClass().isArray()) {
				returnparam; }}// If key is multiple arguments, key is the new SimpleKey, but the hashCode and Equals methods of this SimpleKey object are overridden based on the arguments passed to the method.
		return new SimpleKey(params);
	}
Copy the code

The above code is still fairly understandable in three cases:

  • Method has no arguments, so new uses a global nullSimpleKeyObject to act askey.
  • The method has only one parameter and uses the current parameter as thekey
  • Method parameter is greater than1A,newaSimpleKeyObject to act askey.newthisSimpleKeyIs overridden with the parameters passed inSimpleKeythehashCodeandequalsMethod,

The reason why we need to override hashCode and equals methods is the same as that we need to override hashCode and equals methods when we use custom objects as keys, because we use ConcurrentHashMap to implement it.

summary

If we have a class with multiple methods that have no arguments, we will lose the cache if we use the default cache generation strategy. Or caches overwrite each other, or classcastExceptions can occur because they’re all using the same key. For example, this code will generate a ClassCastException

  @Cacheable(value = "user")
    public UserDTO getUser(a) {
        UserDTO userDTO = new UserDTO();
        userDTO.setUserName("Java financial");
        return userDTO;
    }
    @Cacheable(value = "user")
    public UserDTO2 getUser1(a) {
        UserDTO2 userDTO2 = new UserDTO2();
        userDTO2.setUserName2("javajr.cn");
        return userDTO2;
    }
Copy the code

So using the default cache key generation strategy is generally not recommended. If we have to use it we’d better rewrite it ourselves, with the method name and so on. Something like this:

@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;
    }
Copy the code
The custom key

We can specify our key via Spring’s EL expression. The EL expression here can use method parameters and their corresponding attributes. When using method parameters, we can simply use “# parameter name” or “#p parameter index”, which is the preferred method:

   @Cacheable(value="user", key="#id")
    public UserDTO getUserById(Long id) {
        UserDTO userDTO = new UserDTO();
        userDTO.setUserName("Java financial");
        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;
    }
Copy the code

@CachePut

@CachePutThe specified attributes are and@CacheableSame thing, but there’s a difference between the two,@CachePutInstead of checking the cache for a value, the annotated method executes the method each time and returns the result, which is also cached.

Why is it a process like this and we can look at the source code and the key code is this line,

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

CacheableOperation can be added to contexts if @cacheable (context.get (cacheableOperation.class) isn’t empty. CacheHit returns null if it does not fetch from the cache. When that join CacheableOperation contexts we see SpringCacheAnnotationParser# parseCacheAnnotations the method will understand. Specific source code is not shown, interested in their own to turn.

@CacheEvict

Delete data from the cache. The usage is similar to the previous two annotations with the value and key properties. Note that it has a beforeInvocation property

  • beforeInvocationNote that the default value of this property is false, which means that the cache will not be deleted until the method is successfully executed. Set totrueThe cache will be deleted before the method is called, and the cache will be deleted after the method is successfully executed. The cache will not be deleted if the method does not perform properly.
  • allEntrieWhether to clear all cache contents. The default value isfalseIf is specifiedtrue, all caches will be cleared immediately after the method call

@Caching

This is a composite 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 way is intrusive, its implementation principle is relatively simple is through the section of the method interceptor to achieve, intercept all methods, its core code is as follows: it looks like the same as our business code, interested can also go to 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
				returninvokeOperation(invoker); }}// Process any early evictions
		// beforeInvocation whether the attribute is true, if so remove the cache
		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;
	}
Copy the code

The end of the

  • As a result of their talent and learning, it is inevitable that there will be mistakes, if you found the wrong place, but also hope that the message to me pointed out, I will correct it.
  • If you think the article is good, your forwarding, sharing, appreciation, like, message is the biggest encouragement to me.
  • Thank you for reading. Welcome and thank you for your attention.

Standing on the shoulders of giants pick apples: www.cnblogs.com/fashflying/…