Analyse du code source @Cacheable de Spring (partie 2)

Logique de traitement de l'aspect du cache CacheInterceptor

Commençons par l'article précédent sur l'analyse du code source @Cacheable de Spring (partie 1) . L'objet proxy a été créé avec succès, puis nous analyserons le processus d'appel. Alors, par où commencer ? Bien sûr, allez voir l’enhancer Advisor. Parce que les proxys dynamiques appelleront ces aspects des logiques.

Nous savons également que Advisor = Advice + Pointcut. L'article précédent sur l'analyse du code source @Cacheable de Spring (Partie 1) a analysé comment le matcher de Pointcut effectue la correspondance. Je ne vais pas trop développer ici, mais les conseils qui me concernent directement sont introduits dans la classe ProxyCachingConfiguration. Cet Advice n'implémente pas directement l'interface Advice, mais implémente son interface de sous-classe MethodInterceptor.

@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {
    
    

	// cache_tag: 缓存方法增强器
	@Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor(
			CacheOperationSource cacheOperationSource, CacheInterceptor cacheInterceptor) {
    
    

		BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
		// 缓存用来解析一些属性封装的对象 CacheOperationSource
		advisor.setCacheOperationSource(cacheOperationSource);
		// 缓存拦截器执行对象
		advisor.setAdvice(cacheInterceptor);
		if (this.enableCaching != null) {
    
    
			advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));
		}
		return advisor;
	}

	// cache_tag: Cache 注解解析器
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public CacheOperationSource cacheOperationSource() {
    
    
		return new AnnotationCacheOperationSource();
	}

	// cache_tag: 缓存拦截器执行器
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public CacheInterceptor cacheInterceptor(CacheOperationSource cacheOperationSource) {
    
    
		CacheInterceptor interceptor = new CacheInterceptor();
		interceptor.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager);
		interceptor.setCacheOperationSource(cacheOperationSource);
		return interceptor;
	}
}

Alors jetez un oeil à ce qui se fait dans ce cours ? Entrez la classe CacheInterceptor, le code source principal est le suivant :

public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {
    
    

	@Override
	@Nullable
	public Object invoke(final MethodInvocation invocation) throws Throwable {
    
    
		Method method = invocation.getMethod();

		CacheOperationInvoker aopAllianceInvoker = () -> {
    
    
			try {
    
    
				return invocation.proceed();
			}
			catch (Throwable ex) {
    
    
				throw new CacheOperationInvoker.ThrowableWrapper(ex);
			}
		};

		Object target = invocation.getThis();
		Assert.state(target != null, "Target must not be null");
		try {
    
    
			// cache_tag: 开始执行真正的缓存拦截逻辑
			return execute(aopAllianceInvoker, target, method, invocation.getArguments());
		}
		catch (CacheOperationInvoker.ThrowableWrapper th) {
    
    
			throw th.getOriginal();
		}
	}
}

Continuez à suivre la méthodeexecute().Le code source principal est le suivant :

	@Nullable
	protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
    
    

		// CacheMethodInterceptor 的父类 CacheAspectSupport 实现了接口 InitializingBean 接口,就是在这里把 initialized 设置成 true 的
		if (this.initialized) {
    
    
			Class<?> targetClass = getTargetClass(target);
			CacheOperationSource cacheOperationSource = getCacheOperationSource();
			if (cacheOperationSource != null) {
    
    
				Collection<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass);
				if (!CollectionUtils.isEmpty(operations)) {
    
    
					return execute(invoker, method,
							new CacheOperationContexts(operations, method, args, target, targetClass));
				}
			}
		}

		return invoker.invoke();
	}

Vous n'avez pas besoin de regarder la méthode getTargetClass(), récupérez simplement la classe cible, puis analysez la méthode getCacheOperationSource(). Le code source principal est le suivant :

	@Nullable
	public CacheOperationSource getCacheOperationSource() {
    
    
		return this.cacheOperationSource;
	}

Vous pouvez voir qu'une instance de classe CacheOperationSource est renvoyée. En fait, cette instance de classe a été injectée via l'annotation @Bean dans la classe ProxyCachingConfiguration, et il s'agit de CacheOperationSource. Vous pouvez simplement comprendre que cette classe sert à analyser et collecter des annotations telles que @Cacheable.

Examinez ensuite la logique de la méthode getCacheOperations(method,targetClass). Vous pouvez voir que la méthode actuellement appelée et la classe cible sont transmises en tant que paramètres puis traitées. Le code source principal est le suivant :

	@Override
	@Nullable
	public Collection<CacheOperation> getCacheOperations(Method method, @Nullable Class<?> targetClass) {
    
    
		if (method.getDeclaringClass() == Object.class) {
    
    
			return null;
		}

		Object cacheKey = getCacheKey(method, targetClass);
		Collection<CacheOperation> cached = this.attributeCache.get(cacheKey);

		if (cached != null) {
    
    
			return (cached != NULL_CACHING_ATTRIBUTE ? cached : null);
		}
		else {
    
    
			// cache_tag: 计算缓存注解上面的配置的值,然后封装成 CacheOperation 缓存属性对象,基本和事物的一样
			// 注意每个缓存注解对应一种不同的解析处理
			Collection<CacheOperation> cacheOps = computeCacheOperations(method, targetClass);
			if (cacheOps != null) {
    
    
				if (logger.isTraceEnabled()) {
    
    
					logger.trace("Adding cacheable method '" + method.getName() + "' with attribute: " + cacheOps);
				}
				this.attributeCache.put(cacheKey, cacheOps);
			}
			else {
    
    
				this.attributeCache.put(cacheKey, NULL_CACHING_ATTRIBUTE);
			}
			return cacheOps;
		}
	}

Continuez à saisir la méthode calculateCacheOperations(). Le code source principal est le suivant :

	@Nullable
	private Collection<CacheOperation> computeCacheOperations(Method method, @Nullable Class<?> targetClass) {
    
    
		// Don't allow no-public methods as required.
		if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
    
    
			return null;
		}

		// The method may be on an interface, but we need attributes from the target class.
		// If the target class is null, the method will be unchanged.
		Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);

		// First try is the method in the target class.
		Collection<CacheOperation> opDef = findCacheOperations(specificMethod);
		if (opDef != null) {
    
    
			return opDef;
		}

		// cache_tag: 在 method 方法上找到缓存相关的注解封装成 CacheOperation 缓存对象属性,跟事物基本一样
		opDef = findCacheOperations(specificMethod.getDeclaringClass());
		if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
    
    
			return opDef;
		}

		if (specificMethod != method) {
    
    
			// Fallback is to look at the original method.
			opDef = findCacheOperations(method);
			if (opDef != null) {
    
    
				return opDef;
			}
			// Last fallback is the class of the original method.
			opDef = findCacheOperations(method.getDeclaringClass());
			if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
    
    
				return opDef;
			}
		}

		return null;
	}

Comme le montre le code source ci-dessus, il sera jugé si la méthode actuellement appelée est modifiée par des annotations telles que @Cacheable. Sinon, continuez à chercher s'il y en a une dans la classe où se trouve la méthode. Sinon, continuez à rechercher la méthode d'interface de la classe parent. Sinon, continuez à la rechercher sur l'interface de la classe parent. En fin de compte, si elle n'est pas trouvée, null est renvoyé, indiquant que la méthode actuelle n'a pas besoin d'être proxy. Appelez directement la méthode cible pour exécuter la logique d'exécution normale. Si vous le trouvez, vous devez utiliser la logique sectionnelle.

Voir la méthode findCacheOperations(), le code source principal est le suivant :

	@Override
	@Nullable
	protected Collection<CacheOperation> findCacheOperations(Class<?> clazz) {
    
    
		return determineCacheOperations(parser -> parser.parseCacheAnnotations(clazz));
	}

	@Nullable
	private Collection<CacheOperation> parseCacheAnnotations(
			DefaultCacheConfig cachingConfig, AnnotatedElement ae, boolean localOnly) {
    
    

		Collection<? extends Annotation> anns = (localOnly ?
				AnnotatedElementUtils.getAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS) :
				AnnotatedElementUtils.findAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS));
		if (anns.isEmpty()) {
    
    
			return null;
		}
		// cache_tag: 熟悉不能再熟悉的缓存注解 Cacheable/CacheEvict/CachePut/Caching
		// 注意每一种类型的注解解析是不太一样的哦,具体看 parseCacheableAnnotation() 解析方法
		final Collection<CacheOperation> ops = new ArrayList<>(1);
		anns.stream().filter(ann -> ann instanceof Cacheable).forEach(
				ann -> ops.add(parseCacheableAnnotation(ae, cachingConfig, (Cacheable) ann)));
		anns.stream().filter(ann -> ann instanceof CacheEvict).forEach(
				ann -> ops.add(parseEvictAnnotation(ae, cachingConfig, (CacheEvict) ann)));
		anns.stream().filter(ann -> ann instanceof CachePut).forEach(
				ann -> ops.add(parsePutAnnotation(ae, cachingConfig, (CachePut) ann)));
		anns.stream().filter(ann -> ann instanceof Caching).forEach(
				ann -> parseCachingAnnotation(ae, cachingConfig, (Caching) ann, ops));
		return ops;
	}

On peut voir d'ici que si une modification d'annotation telle que @Cacheable est trouvée sur la méthode, elle sera encapsulée dans différents objets selon différents types d'annotations, tels que @Cacheable => CacheableOperation, @CacheEvict => CacheEvictOperation, etc. Ces objets sont ensuite ajoutés à la collection et renvoyés.

Enfin, revenez sur le site appelant. getCacheOperations(method, targetClass)Le code source est le suivant :

	@Nullable
	protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
    
    
		// Check whether aspect is enabled (to cope with cases where the AJ is pulled in automatically)
		// CacheMethodInterceptor 的父类 CacheAspectSupport 实现了接口 InitializingBean 接口,就是在这里把 initialized 设置成 true 的
		if (this.initialized) {
    
    
			Class<?> targetClass = getTargetClass(target);
			CacheOperationSource cacheOperationSource = getCacheOperationSource();
			if (cacheOperationSource != null) {
    
    
				Collection<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass);
				if (!CollectionUtils.isEmpty(operations)) {
    
    
					return execute(invoker, method,
							new CacheOperationContexts(operations, method, args, target, targetClass));
				}
			}
		}

		return invoker.invoke();
	}

Parce que les opérations ne sont pas nulles,la méthodeexecute() sera exécutée.Ici,un nouveau contexte de cache CacheOperationContexts est utilisé pour encapsuler un tas de paramètres. C'est ce que Spring aime faire le plus, sceller plusieurs paramètres en un seul paramètre pour faciliter la transmission, l'écriture et la lecture. Quoi qu'il en soit, les avantages sont nombreux. Le code source principal d'execute() est le suivant :

	@Nullable
	private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
    
    
		// cache_tag: 只有在缓存注解上面标注了 sync=true 才会进入,默认 false
		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, handleSynchronizedGet(invoker, key, cache));
				}
				catch (Cache.ValueRetrievalException ex) {
    
    
					// Directly propagate ThrowableWrapper from the invoker,
					// or potentially also an IllegalArgumentException etc.
					ReflectionUtils.rethrowRuntimeException(ex.getCause());
				}
			}
			else {
    
    
				// No caching required, only call the underlying method
				return invokeOperation(invoker);
			}
		}

		// cache_tag: 执行 CacheEvict 注解的作用,其实就是去回调 clear() 方法,在目标方法之前调用(一般不会这样干吧)
		processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
				CacheOperationExpressionEvaluator.NO_RESULT);

		// cache_tag: 执行 Cacheable 注解的作用,缓存命中,是否获取到了值,获取到了就叫做命中了
		Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));

		// Collect puts from any @Cacheable miss, if no cached item is found
		List<CachePutRequest> cachePutRequests = new ArrayList<>();
		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 {
    
    
			// cache_tag: 如果没有命中缓存,就直接执行目标方法
			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);
		}

		// cache_tag: 执行 CacheEvict 注解的作用,其实就是去回调 clear() 方法
		processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);

		return returnValue;
	}

Vous pouvez voir que la première ligne exécutera la méthode processCacheEvicts(), mais lorsque vous voyez le deuxième paramètre de la méthode, true est transmis, ce qui signifie qu'il est appelé avant la méthode. Mais notez que la valeur par défaut de l'annotation @CacheEvict est false. Entrez la logique interne de la méthode, comme suit :

	private void processCacheEvicts(
			Collection<CacheOperationContext> contexts, boolean beforeInvocation, @Nullable Object result) {
    
    

		for (CacheOperationContext context : contexts) {
    
    
			CacheEvictOperation operation = (CacheEvictOperation) context.metadata.operation;
			if (beforeInvocation == operation.isBeforeInvocation() && isConditionPassing(context, result)) {
    
    
				performCacheEvict(context, operation, result);
			}
		}
	}

Évidemment, la condition if ici ne peut pas être remplie. Étant donné que l'annotation @CacheEvict est fausse par défaut, la valeur beforeInvocation = true transmise dans la méthode processCacheEvicts() n'est évidemment pas égale. Cette logique ne sera utilisée que si vous utilisez @CacheEvict(beforeInvocation=true). À quoi sert exactement cette logique ? Autant jeter un œil à la méthode performCacheEvict() d'abord. Le code source principal est le suivant :

	private void performCacheEvict(
			CacheOperationContext context, CacheEvictOperation operation, @Nullable Object result) {
    
    

		Object key = null;
		for (Cache cache : context.getCaches()) {
    
    
			if (operation.isCacheWide()) {
    
    
				logInvalidating(context, operation, null);
				doClear(cache, operation.isBeforeInvocation());
			}
			else {
    
    
				if (key == null) {
    
    
					key = generateKey(context, result);
				}
				logInvalidating(context, operation, key);
				doEvict(cache, key, operation.isBeforeInvocation());
			}
		}
	}

Tous les caches Cache sont obtenus ici, mais pourquoi peuvent-ils être obtenus ici ? Où est attribuée la valeur ? Cette question sera abordée plus tard. Aucune analyse au préalable. Supposons que tous les caches, tels que Redis, LocalMap, etc., soient obtenus, puis que la méthode doEvict() soit appelée. Le code source principal est le suivant :

	protected void doEvict(Cache cache, Object key, boolean immediate) {
    
    
		try {
    
    
			if (immediate) {
    
    
				cache.evictIfPresent(key);
			}
			else {
    
    
				cache.evict(key);
			}
		}
		catch (RuntimeException ex) {
    
    
			getErrorHandler().handleCacheEvictError(ex, cache, key);
		}
	}

Évidemment, il existe ici une méthode hook, qui est la méthode modèle. La méthode spécifique de sauvegarde du cache est implémentée par des sous-classes, telles que Redis, LocalMap, etc. Si vous ne savez pas comment utiliser ces caches, vous pouvez lire mon autre article Tutoriel d'utilisation du cache @Cacheable de Spring , sinon vous risquez de ne pas le comprendre ici. La méthode doEvict() consiste à vider le cache. Chaque cache est vidé d'une manière différente. Videz simplement le cache et consultez l'API de cache correspondante.

Après avoir lu la méthode processCacheEvicts(), puis examiné la méthode findCachedItem(), il s'agit évidemment d'une méthode de requête. Le code source principal est le suivant :

	@Nullable
	private Cache.ValueWrapper findCachedItem(Collection<CacheOperationContext> contexts) {
    
    
		Object result = CacheOperationExpressionEvaluator.NO_RESULT;
		for (CacheOperationContext context : contexts) {
    
    
			if (isConditionPassing(context, result)) {
    
    
				// cache_tag: 生成一个 key
				Object key = generateKey(context, result);
				Cache.ValueWrapper cached = findInCaches(context, key);
				if (cached != null) {
    
    
					return cached;
				}
				else {
    
    
					if (logger.isTraceEnabled()) {
    
    
						logger.trace("No cache entry for key '" + key + "' in cache(s) " + context.getCacheNames());
					}
				}
			}
		}
		return null;
	}

Continuez à voir la méthode findInCaches(), le code source est le suivant :

	@Nullable
	private Cache.ValueWrapper findInCaches(CacheOperationContext context, Object key) {
    
    
		for (Cache cache : context.getCaches()) {
    
    
			Cache.ValueWrapper wrapper = doGet(cache, key);
			if (wrapper != null) {
    
    
				if (logger.isTraceEnabled()) {
    
    
					logger.trace("Cache entry for key '" + key + "' found in cache '" + cache.getName() + "'");
				}
				return wrapper;
			}
		}
		return null;
	}

Entrez doGet(), le code source est le suivant :

	@Nullable
	protected Cache.ValueWrapper doGet(Cache cache, Object key) {
    
    
		try {
    
    
			// cache_tag: 调用第三方实现获取对应的值,比如 redis、EhCacheCache 等
			return cache.get(key);
		}
		catch (RuntimeException ex) {
    
    
			getErrorHandler().handleCacheGetError(ex, cache, key);
			return null;  // If the exception is handled, return a cache miss
		}
	}

Il s’agit évidemment d’une autre méthode de hook, dont l’implémentation est laissée aux sous-classes. get() consiste à obtenir le cache, qui peut provenir de Redis, LocalMap, etc. Ensuite, s'il y a une valeur, la valeur est renvoyée, ce qui est appelé un succès dans le cache. Si null n'est pas renvoyé, cela est appelé un échec dans le cache.

En supposant que le cache manque, il appellera d'abord la méthode collectPutRequests() pour générer un en-tête de requête CachePutRequest (étant donné que put doit stocker les données obtenues à partir de la méthode cible dans le cache), puis appellera la méthode cible pour obtenir les données.

En supposant qu'un cache soit atteint, la valeur dans le cache est renvoyée directement.

Ensuite la méthode collectPutRequests() sera exécutée pour préparer l'en-tête de requête CachePutRequest. Cependant, s'il existe déjà un en-tête de requête CachePutRequest mentionné ci-dessus, un nouveau sera obtenu directement et ne sera pas renouvelé.

Une fois l'en-tête de requête CachePutRequest ci-dessus préparé, il est nécessaire de traiter l'en-tête de requête et d'appeler la méthode apply() pour le traitement. Le code source est le suivant :

	public void apply(@Nullable Object result) {
    
    
		if (this.context.canPutToCache(result)) {
    
    
			for (Cache cache : this.context.getCaches()) {
    
    
				doPut(cache, this.key, result);
			}
		}
	}

	protected void doPut(Cache cache, Object key, @Nullable Object result) {
    
    
		try {
    
    
			cache.put(key, result);
		}
		catch (RuntimeException ex) {
    
    
			getErrorHandler().handleCachePutError(ex, cache, key, result);
		}
	}

Vous pouvez voir qu’il existe ici une autre méthode de hook, dont l’implémentation est laissée à des sous-classes spécifiques. put() consiste à stocker la valeur dans le cache. Pour plus de détails sur la façon de stocker le cache, consultez l'API correspondante.

Enfin, vous verrez la méthode processCacheEvicts() être appelée, mais à ce moment-là, le deuxième paramètre est vrai, ce qui est exactement la même que la valeur par défaut dans l'annotation @CacheEvicts, donc la méthode processCacheEvicts() ici sera appelée pour supprimer les données mises en cache.valeur effacée.

Revenez en arrière et étudiez attentivement le résumé logique de ce paragraphe :

Annotation @Cacheable : Vérifiez d'abord le cache correspondant (cache tel que Redis, LocalMap, etc.). Si le cache arrive, il sera renvoyé directement. S'il n'atteint pas, créez d'abord un en-tête de requête CachePutRequest, puis appelez la méthode cible. pour obtenir les données (peut interroger les données de la base de données, etc.), puis les données trouvées sont enregistrées dans le cache correspondant, et enfin les données obtenues sont renvoyées.

Annotation @CacheEvicts : Si beforeInvocation = true est défini, cela signifie que le cache est d'abord supprimé, puis la méthode cible est appelée. Sinon, la méthode cible est appelée en premier, puis le cache est supprimé.

Annotation @CachePut : Une nouvelle copie des données sera mise dans le cache à chaque fois.

Enfin, permettez-moi de parler de l'endroit où context.getCaches() dans le code suivant se voit attribuer une valeur. Pourquoi puis-je obtenir la valeur du cache directement ici ? ? ? Le code source est le suivant :

	private void performCacheEvict(
			CacheOperationContext context, CacheEvictOperation operation, @Nullable Object result) {
    
    

		Object key = null;
		for (Cache cache : context.getCaches()) {
    
    
			if (operation.isCacheWide()) {
    
    
				logInvalidating(context, operation, null);
				doClear(cache, operation.isBeforeInvocation());
			}
			else {
    
    
				if (key == null) {
    
    
					key = generateKey(context, result);
				}
				logInvalidating(context, operation, key);
				doEvict(cache, key, operation.isBeforeInvocation());
			}
		}
	}

Grâce à la fonction de référence IDEA, nous pouvons localiser le point d'appel de niveau supérieur étape par étape. Ensuite, nous commençons l'analyse à partir de ce code d'entrée. Le code source est le suivant :

	@Nullable
	protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
    
    
		// Check whether aspect is enabled (to cope with cases where the AJ is pulled in automatically)
		// CacheMethodInterceptor 的父类 CacheAspectSupport 实现了接口 InitializingBean 接口,就是在这里把 initialized 设置成 true 的
		if (this.initialized) {
    
    
			Class<?> targetClass = getTargetClass(target);
			CacheOperationSource cacheOperationSource = getCacheOperationSource();
			if (cacheOperationSource != null) {
    
    
				Collection<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass);
				if (!CollectionUtils.isEmpty(operations)) {
    
    
					return execute(invoker, method,
							new CacheOperationContexts(operations, method, args, target, targetClass));
				}
			}
		}

		return invoker.invoke();
	}

Notez que cette méthode getCacheOperationSource() analysera les attributs de configuration de @Cacheable et d'autres annotations. Lorsqu'une annotation est analysée, elle encapsulera un objet CacheOperation. Ajoutez ensuite à la collection. Cette collection contient des informations de configuration correspondant à chaque annotation. Enfin, enveloppez cette collection dans un objet CacheOperationSource. Quoi qu'il en soit, Spring aime simplement envelopper les objets couche par couche comme celui-ci.

Ensuite, la collection est obtenue via l'objet CacheOperationSource. Cette collection est le @Cacheable analysé et d'autres informations de configuration d'annotation. Enfin, Spring encapsule ces opérations de collection analysées dans l'objet CacheOperationContexts.

Entrez la structure CacheOperationContexts, le code source est le suivant :

	public CacheOperationContexts(Collection<? extends CacheOperation> operations, Method method,
			Object[] args, Object target, Class<?> targetClass) {
    
    

		this.contexts = new LinkedMultiValueMap<>(operations.size());
		for (CacheOperation op : operations) {
    
    
			this.contexts.add(op.getClass(), getOperationContext(op, method, args, target, targetClass));
		}
		this.sync = determineSyncFlag(method);
	}

Dans le constructeur de classe CacheOperationContexts, les opérations de collection sont parcourues une par une, qui contient les informations de configuration de @Cacheable et d'autres annotations qui ont été analysées précédemment.

Entrez la méthode getOperationContext(), le code source est le suivant :

	protected CacheOperationContext getOperationContext(
			CacheOperation operation, Method method, Object[] args, Object target, Class<?> targetClass) {
    
    

		CacheOperationMetadata metadata = getCacheOperationMetadata(operation, method, targetClass);
		return new CacheOperationContext(metadata, args, target);
	}

Encapsulez l'annotation @Cacheable, ou @CacheEvict, ou @CachePut, ou @Caching dans un objet CacheOperationContext distinct. Quoi qu'il en soit, Spring aime faire cela et aime fusionner plusieurs paramètres en un seul grand paramètre de contexte. Les avantages sont nombreux.

Entrez ensuite la méthode de construction de la classe CacheOperationContext. Le code source est le suivant :

	public CacheOperationContext(CacheOperationMetadata metadata, Object[] args, Object target) {
    
    
		this.metadata = metadata;
		this.args = extractArgs(metadata.method, args);
		this.target = target;
		// cache_tag: 保存所有获取到的 Cache
		this.caches = CacheAspectSupport.this.getCaches(this, metadata.cacheResolver);
		this.cacheNames = createCacheNames(this.caches);
	}

Ensuite, vous pouvez voir que le cache des caches est attribué ici. Voyons donc comment la valeur est attribuée. Entrez la méthode getCaches(), le code source est le suivant :

	protected Collection<? extends Cache> getCaches(
			CacheOperationInvocationContext<CacheOperation> context, CacheResolver cacheResolver) {
    
    
		// cache_tag: 解析有哪些 Cache 比如 redis 等,就跟解析有哪些数据源一样一样的
		Collection<? extends Cache> caches = cacheResolver.resolveCaches(context);
		if (caches.isEmpty()) {
    
    
			throw new IllegalStateException("No cache could be resolved for '" +
					context.getOperation() + "' using resolver '" + cacheResolver +
					"'. At least one cache should be provided per cache operation.");
		}
		return caches;
	}

Continuez le suivi et entrez la méthode solveCaches(). Le code source est le suivant :

	@Override
	public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {
    
    
		/**
		 * 获取到注解上指定的缓存名称,比如 @Cacheable 指定有 "myMapCache","myRedisCache" 两个缓存名称
		 */
		Collection<String> cacheNames = getCacheNames(context);
		if (cacheNames == null) {
    
    
			return Collections.emptyList();
		}
		Collection<Cache> result = new ArrayList<>(cacheNames.size());
		for (String cacheName : cacheNames) {
    
    
			Cache cache = getCacheManager().getCache(cacheName);
			if (cache == null) {
    
    
				throw new IllegalArgumentException("Cannot find cache named '" +
						cacheName + "' for " + context.getOperation());
			}
			result.add(cache);
		}
		return result;
	}

Il y a trois endroits dans le code ci-dessus qui sont très importants. getCacheNames() Obtient la valeur de l'attribut cacheNames configurée sur des annotations telles que @Cacheable. La méthode getCacheManager() obtient une instance de CacheManager. Récupérez ensuite l'instance de cache Cache correspondante via cette instance. Ajoutez ensuite cette instance de cache à la collection de résultats et renvoyez la valeur finale attribuée à la variable membre des caches. Ainsi, en fin de compte, il peut obtenir les données directement à partir de l'endroit situé au-dessus.

Ici, nous nous concentrons sur l'étude de ces trois méthodes et entrons dans la méthode getCacheNames(). Le code source principal est le suivant :

	@Override
	protected Collection<String> getCacheNames(CacheOperationInvocationContext<?> context) {
    
    
		return context.getOperation().getCacheNames();
	}

C'est très simple d'obtenir directement la valeur. Les informations de configuration de l'annotation telles que @Cacheable ont été analysées auparavant et encapsulées dans l'objet CacheOperation, vous pouvez donc ici obtenir directement l'objet encapsulé CacheOperation de l'annotation correspondante via context.getOperation(). Ainsi, les cacheNames peuvent être obtenus à partir de cet objet encapsulé. C'est si simple.

Jetons un coup d'oeil à la méthode getCacheManager(). Le code source est le suivant :

	public CacheManager getCacheManager() {
    
    
		Assert.state(this.cacheManager != null, "No CacheManager set");
		return this.cacheManager;
	}

On constate qu'il est utilisé directement, il faut donc l'attribuer et l'initialiser quelque part. En fait, ce CacheManager doit être défini par vous-même. Parce que le CacheManager que vous configurez déterminera le type de cache que vous utilisez. Par exemple, si vous souhaitez configurer une instance RedisCacheManager via l'annotation @Bean, alors RedisCacheManager doit introduire le cache Redis. EhCacheCacheManager doit introduire le cache EhCacheCache. Ce CacheManager est une classe de gestion de cache définie par l'utilisateur. Bien entendu, il peut également être personnalisé.

Regardez ensuite CacheManager, qui fournit une méthode getCache() qui peut être utilisée pour obtenir une instance de cache.

public interface CacheManager {
    
    

	// 根据名字获取某个缓存
	@Nullable
	Cache getCache(String name);

	// 获取到所有缓存的名字
	Collection<String> getCacheNames();
}

Vous pouvez complètement personnaliser l'instance de cache ici, il vous suffit d'implémenter l'interface Cache. Les exemples sont les suivants :

public class MyMapCache implements Cache {
    
    

	public static final Map<Object, Object> map = new ConcurrentHashMap<>();

	private String cacheName;

	public MyMapCache(String cacheName) {
    
    
		this.cacheName = cacheName;
	}

	@Override
	public String getName() {
    
    
		return cacheName;
	}

	@Override
	public Object getNativeCache() {
    
    
		return null;
	}

	@Override
	public ValueWrapper get(Object key) {
    
    
		System.out.println(">>>>>>我是 MyMapCache 缓存中的 get() 方法");
		Object o = map.get(key);
		if (Objects.nonNull(o)) {
    
    
			return new SimpleValueWrapper(o);
		}
		return null;
	}

	@Override
	public <T> T get(Object key, Class<T> type) {
    
    

		return (T)map.get(key);
	}

	@Override
	public <T> T get(Object key, Callable<T> valueLoader) {
    
    
		return (T)map.get(key);
	}

	@Override
	public void put(Object key, Object value) {
    
    
		System.out.println(">>>>>>我是 MyMapCache 缓存中的 put() 方法");
		map.put(key, value);
	}

	@Override
	public void evict(Object key) {
    
    
		map.remove(key);
	}

	@Override
	public void clear() {
    
    
		map.clear();
	}
}

Enfin, ce que CacheManager renvoie est notre instance de cache personnalisée MyMapCache. Enfin, ajoutez cette instance MyMapCache à la collection de résultats et affectez-la à la variable membre this.caches.

Cependant, l'implémentation de l'interface CacheManager présente un inconvénient : une seule instance de cache peut être renvoyée à la fois. Que faire si vous souhaitez en renvoyer plusieurs ? Que faire, donc Spring y a déjà pensé et a préparé la classe abstraite AbstractCacheManager pour vous à l'avance. Il possède une méthode loadCaches(), le code source est le suivant :

public abstract class AbstractCacheManager implements CacheManager, InitializingBean {
    
    

	// cache_tag: 封装成 Map,方便 getCache(cacheName) 操作
	private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);

	// cache_tag: 用来保存外界传进来的缓存管理名称,一般的话就只有一个,最多两个(本地缓存+redis缓存)
	private volatile Set<String> cacheNames = Collections.emptySet();
	@Override
	public void afterPropertiesSet() {
    
    
		initializeCaches();
	}

	public void initializeCaches() {
    
    
		// cache_tag: 预留给 CacheManager 管理类的方法,可以查看 SimpleCacheManager 子类
		// 这里我们就是配置的 SimpleCacheManager 缓存管理类,Cache 使用的 ConcurrentMapCache
		// 每个子类 Cache 都实现了 Cache 接口定义的 CRUD 方法
		Collection<? extends Cache> caches = loadCaches();

		// 下面就是一堆的赋值,如果 caches 是空的话,就是代码没哟配置缓存相关的东西,自然而然下面所有逻辑都不走了
		synchronized (this.cacheMap) {
    
    
			this.cacheNames = Collections.emptySet();
			this.cacheMap.clear();
			Set<String> cacheNames = new LinkedHashSet<>(caches.size());
			for (Cache cache : caches) {
    
    
				String name = cache.getName();
				this.cacheMap.put(name, decorateCache(cache));
				cacheNames.add(name);
			}
			this.cacheNames = Collections.unmodifiableSet(cacheNames);
		}
	}

	protected abstract Collection<? extends Cache> loadCaches();
}

De nombreuses instances de Cache peuvent être renvoyées dans la méthode loadCaches(). Alors, comment stocker autant d'instances ? Il doit y avoir une relation de mappage, donc Map doit être utilisé. Ensuite, la clé est le nom de cache correspondant et la valeur est le nom de cache correspondant. Cache. C'est ainsi que Spring est conçu. Ceci est très utile lorsque des conceptions à double ou triple cache sont requises. À partir du code source spécifique, vous pouvez voir que AbstractCacheManager est une classe d'extension de CacheManager et implémente l'interface InitializingBean. Ensuite, vous devez faire attention à la méthode afterPropertiesSet() de cette classe. Le code source est le suivant :

	@Override
	public void afterPropertiesSet() {
    
    
		initializeCaches();
	}

	public void initializeCaches() {
    
    
		// cache_tag: 预留给 CacheManager 管理类的方法,可以查看 SimpleCacheManager 子类
		// 这里我们就是配置的 SimpleCacheManager 缓存管理类,Cache 使用的 ConcurrentMapCache
		// 每个子类 Cache 都实现了 Cache 接口定义的 CRUD 方法
		Collection<? extends Cache> caches = loadCaches();

		// 下面就是一堆的赋值,如果 caches 是空的话,就是代码没哟配置缓存相关的东西,自然而然下面所有逻辑都不走了
		synchronized (this.cacheMap) {
    
    
			this.cacheNames = Collections.emptySet();
			this.cacheMap.clear();
			Set<String> cacheNames = new LinkedHashSet<>(caches.size());
			for (Cache cache : caches) {
    
    
				String name = cache.getName();
				this.cacheMap.put(name, decorateCache(cache));
				cacheNames.add(name);
			}
			this.cacheNames = Collections.unmodifiableSet(cacheNames);
		}
	}

À partir du code ci-dessus, il apparaît que les instances de Cache chargées via la méthode loadCaches() sont stockées une par une dans le conteneur cacheMap. Comme il existe plusieurs instances de la classe Cache, une relation de mappage doit être établie. Il est donc préférable de stocker une carte.

Voyons ensuite comment obtenir l'instance de cache Cache correspondante pendant le processus d'appel. Le code source est le suivant :


	@Override
	public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {
    
    
		/**
		 * 获取到注解上指定的缓存名称,比如 @Cacheable 指定有 "myMapCache","myRedisCache" 两个缓存名称
		 */
		Collection<String> cacheNames = getCacheNames(context);
		if (cacheNames == null) {
    
    
			return Collections.emptyList();
		}
		Collection<Cache> result = new ArrayList<>(cacheNames.size());
		for (String cacheName : cacheNames) {
    
    
			Cache cache = getCacheManager().getCache(cacheName);
			if (cache == null) {
    
    
				throw new IllegalArgumentException("Cannot find cache named '" +
						cacheName + "' for " + context.getOperation());
			}
			result.add(cache);
		}
		return result;
	}

Ce que getCacheManager() obtient, c'est la classe qui implémente AbstractCacheManager.Actuellement, on constate que Spring a une classe SimpleCacheManager qui a été implémentée, donc l'hypothèse obtenue est la classe de gestion SimpleCacheManager , puis entre dans la méthode getCache() et utilise directement la méthode modèle de la classe parent AbstractCacheManager. Le code source est le suivant :

	@Override
	@Nullable
	public Cache getCache(String name) {
    
    
		// Quick check for existing cache...
		Cache cache = this.cacheMap.get(name);
		if (cache != null) {
    
    
			return cache;
		}

		// The provider may support on-demand cache creation...
		Cache missingCache = getMissingCache(name);
		if (missingCache != null) {
    
    
			// Fully synchronize now for missing cache registration
			synchronized (this.cacheMap) {
    
    
				cache = this.cacheMap.get(name);
				if (cache == null) {
    
    
					cache = decorateCache(missingCache);
					this.cacheMap.put(name, cache);
					updateCacheNames(name);
				}
			}
		}
		return cache;
	}

Obtenez simplement l'instance de Cache correspondante directement depuis cacheMap, car le mappage cacheMap a été créé ci-dessus. Il s'agit de la prise en charge par Spring de la conception de cache à plusieurs niveaux.

Je suppose que tu aimes

Origine blog.csdn.net/qq_35971258/article/details/128729027
conseillé
Classement