Análisis del código fuente @Cacheable de Spring (Parte 2)

Lógica de procesamiento del aspecto de caché de CacheInterceptor

Comencemos con el artículo anterior sobre el análisis del código fuente @Cacheable de Spring (Parte 1) . El objeto proxy se creó con éxito y luego analizaremos el proceso de llamada. Entonces, ¿En dónde debes empezar? Por supuesto, ve a ver el potenciador Advisor. Porque los proxies dinámicos llamarán a estos aspectos lógicas.

También sabemos que Advisor = Advice + Pointcut. El artículo anterior sobre el análisis del código fuente @Cacheable de Spring (Parte 1) analizó cómo el comparador en Pointcut realiza la comparación. No daré muchos detalles aquí, pero el consejo que me interesa directamente se introduce en la clase ProxyCachingConfiguration. Este Consejo no implementa directamente la interfaz de Consejo, pero implementa su interfaz de subclase 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;
	}
}

Entonces, eche un vistazo a lo que se hace en esta clase. Ingrese a la clase CacheInterceptor, el código fuente principal es el siguiente:

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();
		}
	}
}

Continúe rastreando el método ejecutar (). El código fuente principal es el siguiente:

	@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();
	}

No es necesario mirar el método getTargetClass (), solo obtenga la clase de destino y luego analice el método getCacheOperationSource (). El código fuente principal es el siguiente:

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

Puede ver que se devuelve una instancia de clase CacheOperationSource. De hecho, esta instancia de clase se ha inyectado a través de la anotación @Bean en la clase ProxyCachingConfiguration y es CacheOperationSource. Simplemente puede entender que esta clase es para analizar y recopilar anotaciones como @Cacheable.

Luego observe la lógica del método getCacheOperations(método,targetClass). Puede ver que el método que se llama actualmente y la clase de destino se pasan como parámetros y luego se procesan. El código fuente principal es el siguiente:

	@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;
		}
	}

Continúe ingresando el método ComputeCacheOperations (), el código fuente principal es el siguiente:

	@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;
	}

Como se puede ver en el código fuente anterior, se juzgará si el método llamado actualmente se modifica mediante anotaciones como @Cacheable. De lo contrario, continúe buscando si hay uno en la clase donde se encuentra el método. De lo contrario, Continúe buscando el método de la interfaz de la clase principal, si no, continúe buscándolo en la interfaz de la clase principal. Al final, si no se encuentra, se devuelve nulo, lo que indica que no es necesario proxy del método actual. Llame directamente al método de destino para ejecutar la lógica de ejecución normal. Si lo encuentra, debe utilizar la lógica seccional.

Consulte el método findCacheOperations(). El código fuente principal es el siguiente:

	@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;
	}

Se puede ver desde aquí que si se encuentra una modificación de anotación como @Cacheable en el método, se encapsulará en diferentes objetos según los diferentes tipos de anotaciones, como @Cacheable => CacheableOperation, @CacheEvict => CacheEvictOperation, etc. Luego, estos objetos se agregan a la colección y se devuelven.

Finalmente, regrese al sitio de llamadas, getCacheOperations(method, targetClass)el código fuente es el siguiente:

	@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();
	}

Debido a que las operaciones no son nulas, se ejecutará el método ejecutar(). Aquí, se utiliza un nuevo contexto de caché CacheOperationContexts para encapsular un montón de parámetros. Esto es lo que más le gusta hacer a Spring: sellar múltiples parámetros en un solo parámetro para facilitar el paso, la escritura y la lectura. De todos modos, hay muchos beneficios. El código fuente principal de ejecutar() es el siguiente:

	@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;
	}

Puede ver que la primera línea ejecutará el método ProcessCacheEvicts (), pero cuando vea el segundo parámetro del método, se pasa verdadero, lo que significa que se llama antes del método. Pero tenga en cuenta que el valor predeterminado en la anotación @CacheEvict es falso. Ingrese la lógica interna del método, de la siguiente manera:

	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);
			}
		}
	}

Obviamente, la condición if aquí no se puede cumplir. Debido a que la anotación @CacheEvict es falsa de forma predeterminada, el valor beforeInvocation = true pasado en el método ProcessCacheEvicts() obviamente no es igual. Esta lógica solo se usará si usa @CacheEvict(beforeInvocation=true). ¿Qué hace exactamente esta lógica? También podríamos echar un vistazo primero al método performCacheEvict(). El código fuente principal es el siguiente:

	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());
			}
		}
	}

Todos los cachés se obtienen aquí, pero ¿por qué se pueden obtener aquí? ¿Dónde se asigna el valor? Esta cuestión se abordará más adelante. No hay análisis primero. Suponiendo que se obtienen todos los cachés, como Redis, LocalMap, etc., y luego se llama al método doEvict (), el código fuente principal es el siguiente:

	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);
		}
	}

Obviamente, aquí hay un método de enlace, que es el método de plantilla. El método específico para guardar el caché se implementa mediante subclases, como Redis, LocalMap, etc. Si no sabe cómo usar estos cachés, puede leer mi otro artículo Tutorial de uso de caché @Cacheable de Spring ; de lo contrario, es posible que no lo entienda aquí. El método doEvict() es borrar el caché, cada caché se borra de una manera diferente. Simplemente borre el caché y vea la API de caché correspondiente.

Después de leer el método ProcessCacheEvicts () y luego observar el método findCachedItem (), obviamente es un método de consulta. El código fuente principal es el siguiente:

	@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;
	}

Continúe viendo el método findInCaches (), el código fuente es el siguiente:

	@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;
	}

Ingrese doGet (), el código fuente es el siguiente:

	@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
		}
	}

Obviamente es otro método de enlace, cuya implementación se deja en manos de las subclases. get() es para obtener el caché, que puede ser de Redis, LocalMap, etc. Luego, si hay un valor, se devuelve el valor, lo que se denomina acierto de caché. Si no se devuelve nulo, se denomina error de caché.

Suponiendo que el caché falla, primero llamará al método CollectPutRequests() para generar un encabezado de solicitud CachePutRequest (ya que put debe almacenar los datos obtenidos del método de destino en el caché) y luego llamará al método de destino para obtener los datos.

Suponiendo un acierto en la caché, el valor de la caché se devuelve directamente.

Luego se ejecutará el método CollectPutRequests() para preparar el encabezado de solicitud CachePutRequest, sin embargo, si ya existe un encabezado de solicitud CachePutRequest mencionado anteriormente, se obtendrá uno nuevo directamente y no se renovará.

Después de preparar el encabezado de solicitud CachePutRequest anterior, es necesario procesar el encabezado de la solicitud y llamar al método apply () para su procesamiento. El código fuente es el siguiente:

	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);
		}
	}

Puede ver que aquí hay otro método de enlace, cuya implementación se deja en manos de subclases específicas. put() es almacenar el valor en el caché. Para obtener detalles sobre cómo almacenar el caché, consulte la API correspondiente.

Finalmente, verá que se llama al método ProcessCacheEvicts (), pero en este momento el segundo parámetro es verdadero, que es exactamente el mismo que el valor predeterminado en la anotación @CacheEvicts, por lo que se llamará al método ProcessCacheEvicts () aquí para eliminar. los datos almacenados en caché valor borrado.

Regrese y estudie detenidamente el resumen lógico de este párrafo:

Anotación @Cacheable : primero verifique el caché correspondiente (caché como Redis, LocalMap, etc.). Si el caché llega, se devolverá directamente. Si no llega, primero cree un encabezado de solicitud CachePutRequest y luego llame al método de destino. para obtener los datos (puede consultar datos de la base de datos, etc.), y luego Los datos encontrados se guardan en el caché correspondiente y finalmente se devuelven los datos obtenidos.

Anotación @CacheEvicts : si se establece beforeInvocation = true, significa que el caché se elimina primero y luego se llama al método de destino. De lo contrario, se llama primero al método de destino y luego se elimina el caché.

Anotación @CachePut : cada vez se colocará una nueva copia de datos en la caché.

Finalmente, permítanme hablar sobre dónde se asigna un valor a context.getCaches() en el siguiente código. ¿Por qué puedo obtener el valor de caché directamente aquí? ? ? El código fuente es el siguiente:

	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());
			}
		}
	}

Usando la función de referencia de IDEA, podemos ubicar el punto de llamada de nivel superior paso a paso, luego comenzamos a analizar desde este código de entrada, el código fuente es el siguiente:

	@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();
	}

Tenga en cuenta que este método getCacheOperationSource() analizará los atributos de configuración de @Cacheable y otras anotaciones. Cuando se analiza una anotación, encapsulará un objeto CacheOperation. Luego agréguelo a la colección. Esta colección contiene información de configuración correspondiente a cada anotación. Finalmente, envuelva esta colección en un objeto CacheOperationSource. De todos modos, a Spring simplemente le gusta envolver objetos capa por capa de esta manera.

Luego, la colección se obtiene a través del objeto CacheOperationSource, esta colección es el @Cacheable analizado y otra información de configuración de anotaciones. Finalmente, Spring encapsula estas operaciones de colección analizadas en el objeto CacheOperationContexts.

Ingrese a la estructura CacheOperationContexts, el código fuente es el siguiente:

	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);
	}

En el constructor de la clase CacheOperationContexts, las operaciones de colección se recorren una por una, que contiene la información de configuración de @Cacheable y otras anotaciones que se han analizado anteriormente.

Ingrese el método getOperationContext(), el código fuente es el siguiente:

	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);
	}

Encapsule la anotación @Cacheable, @CacheEvict, @CachePut o @Caching en un objeto CacheOperationContext separado. De todos modos, a Spring le gusta hacer esto y le gusta fusionar múltiples parámetros en un parámetro de contexto grande. Hay muchos beneficios.

Luego ingrese el método de construcción de la clase CacheOperationContext, el código fuente es el siguiente:

	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);
	}

Luego puede ver que el caché de caché está asignado aquí. Entonces, echemos un vistazo a cómo se asigna el valor. Ingrese el método getCaches(), el código fuente es el siguiente:

	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;
	}

Continúe con el seguimiento e ingrese el método resolveCaches (). El código fuente es el siguiente:

	@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;
	}

Hay tres lugares en el código anterior que son muy importantes. getCacheNames() Obtiene el valor del atributo cacheNames configurado en anotaciones como @Cacheable. El método getCacheManager() obtiene una instancia de CacheManager. Luego obtenga la instancia de caché de caché correspondiente a través de esta instancia. Luego agregue esta instancia de caché a la colección de resultados y devuelva el valor final asignado a la variable miembro de cachés. Entonces, al final, puede obtener los datos directamente desde el lugar anterior.

Aquí nos centramos en estudiar estos tres métodos e ingresamos el método getCacheNames (). El código fuente principal es el siguiente:

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

Es muy sencillo obtener el valor directamente. La información de configuración de anotaciones como @Cacheable se ha analizado antes y encapsulada en el objeto CacheOperation, por lo que aquí puede obtener directamente el objeto encapsulado CacheOperation de la anotación correspondiente a través de context.getOperation (). Por lo tanto, los nombres de caché se pueden obtener de este objeto encapsulado. Es así de simple.

Echemos un vistazo al método getCacheManager(). El código fuente es el siguiente:

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

Se descubre que se usa directamente, por lo que debe asignarse e inicializarse en algún lugar. De hecho, este CacheManager debe ser definido por usted mismo. Porque el CacheManager que configure determinará qué tipo de caché utilizará. Por ejemplo, si desea configurar una instancia de RedisCacheManager mediante la anotación @Bean, RedisCacheManager debe introducir el caché de Redis. EhCacheCacheManager debe introducir el caché de EhCacheCache. Este CacheManager es una clase de administración de caché definida por el usuario. Por supuesto, también se puede personalizar.

Luego mire CacheManager, que proporciona un método getCache() que puede usarse para obtener una instancia de caché.

public interface CacheManager {
    
    

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

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

Puede personalizar completamente la instancia de caché aquí, solo necesita implementar la interfaz de caché. Los ejemplos son los siguientes:

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();
	}
}

Finalmente, lo que devuelve CacheManager es nuestra instancia de caché personalizada MyMapCache. Finalmente, agregue esta instancia de MyMapCache a la colección de resultados y asígnela a la variable miembro this.caches.

Sin embargo, existe una desventaja al implementar la interfaz CacheManager. Sólo se puede devolver una instancia de caché a la vez. ¿Qué sucede si desea devolver varias? Qué hacer, entonces Spring ya lo pensó y preparó la clase abstracta AbstractCacheManager para usted con anticipación. Tiene un método loadCaches(), el código fuente es el siguiente:

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();
}

Se pueden devolver muchas instancias de caché en el método loadCaches (). Entonces, ¿cómo almacenar tantas instancias? Debe haber una relación de mapeo, por lo que se debe usar Map. Luego, la clave es el nombre de caché correspondiente y el valor es el correspondiente. Caché. Así es como está diseñado Spring. de. Esto es muy útil cuando se requieren diseños de caché doble o triple. Desde el código fuente específico, puede ver que AbstractCacheManager es una clase de extensión de CacheManager e implementa la interfaz InitializingBean. Luego debe prestar atención al método afterPropertiesSet () de esta clase. El código fuente es el siguiente:

	@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);
		}
	}

Del código anterior se desprende que las instancias de caché cargadas mediante el método loadCaches() se almacenan en el contenedor cacheMap una por una. Debido a que existen varias instancias de la clase Cache, se debe establecer una relación de mapeo, por lo que es mejor almacenar un Map.

Luego, eche un vistazo a cómo obtener la instancia de caché de caché correspondiente durante el proceso de llamada. El código fuente es el siguiente:


	@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;
	}

Lo que obtiene getCacheManager () es la clase que implementa AbstractCacheManager. Actualmente, se encuentra que Spring tiene una clase SimpleCacheManager que ha sido implementada, por lo que se supone que es la clase de administración SimpleCacheManager , y luego ingresa al método getCache () y usa directamente El método de plantilla de la clase principal AbstractCacheManager, el código fuente es el siguiente:

	@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;
	}

Simplemente obtenga la instancia de caché correspondiente directamente de cacheMap, porque la asignación de cacheMap se creó anteriormente. Este es el soporte de Spring para el diseño de caché multinivel.

Supongo que te gusta

Origin blog.csdn.net/qq_35971258/article/details/128729027
Recomendado
Clasificación