自定义springcache实现事务提交后处理缓存

 

之前说到缓存的管理问题,具体看redis缓存管理问题,我想要实现缓存的逻辑在事务提交之后,考虑使用事务监听器,这个我之前也用过,使用监听器实现在事务提交后发送消息,那么问题是我如何拦截到注解,然后发出事件。

有两种方案,一是使用自定义注解,然后用aop自己实现一整套缓存体系,但是有一个我之前就遇到过的问题,就是aop在接口上不起效,而spring-data-jpa的dao层都是直接用接口的,为了缓存而改换整个查询体系成本太大,也是不现实的,或者我可以对应每个dao封装一层service,然后把注解加在service实现类上,很麻烦,改动量极大,而且很不优雅,破坏了架构。

那么回过头来,springcache是怎么实现在接口层上加注解的呢?只要知道了原理我就可以对其模仿。

SpringCache实现原理及核心业务逻辑(二)

SpringCache实现原理及核心业务逻辑(三)

看完之后原来就是CacheInterceptor实现了MethodInterceptor接口,一个拦截器接口,能够拦截到所有方法的执行,在初始化阶段就扫描所有的方法,把对应的缓存注解进行管理,拦截到方法的时候,判断该方法上有没有注解就行了。实际上是很简单的原理,而且拦截器会拦截所有的方法,跟spring-aop没什么关系。

这就是拦截器,然后核心代码在CacheAspectSupport的execute方法里面

@SuppressWarnings("serial")
public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {

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

		CacheOperationInvoker aopAllianceInvoker = new CacheOperationInvoker() {
			@Override
			public Object invoke() {
				try {
					return invocation.proceed();
				}
				catch (Throwable ex) {
					throw new ThrowableWrapper(ex);
				}
			}
		};

		try {//继承自CacheAspectSupport类
			return execute(aopAllianceInvoker, invocation.getThis(), method, invocation.getArguments());
		}
		catch (CacheOperationInvoker.ThrowableWrapper th) {
			throw th.getOriginal();
		}
	}

}
	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)
		if (this.initialized) {
			Class targetClass = getTargetClass(target);
			Collection operations = getCacheOperationSource().getCacheOperations(method, targetClass);
			if (!CollectionUtils.isEmpty(operations)) {//判断该方法是否存在缓存注解
				return execute(invoker, method, new CacheOperationContexts(operations, method, args, target, targetClass));
			}
		}

		return invoker.invoke();
	}

那么接下来有两种办法,一个是我修改源码然后打包,另一个方法是我进行配置,用自己的类代替关键的业务代码。

这是关键的配置类

@Configuration
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {
 
 
	@Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor() {
		BeanFactoryCacheOperationSourceAdvisor advisor =
				new BeanFactoryCacheOperationSourceAdvisor();
		advisor.setCacheOperationSource(cacheOperationSource());
		advisor.setAdvice(cacheInterceptor());
		advisor.setOrder(this.enableCaching.getNumber("order"));
		return advisor;
	}
 
 
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public CacheOperationSource cacheOperationSource() {
		return new AnnotationCacheOperationSource();
	}
 
 
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public CacheInterceptor cacheInterceptor() {
		CacheInterceptor interceptor = new CacheInterceptor();
		interceptor.setCacheOperationSources(cacheOperationSource());
		if (this.cacheResolver != null) {
			interceptor.setCacheResolver(this.cacheResolver);
		}
		else if (this.cacheManager != null) {
			interceptor.setCacheManager(this.cacheManager);
		}
		if (this.keyGenerator != null) {
			interceptor.setKeyGenerator(this.keyGenerator);
		}
		if (this.errorHandler != null) {
			interceptor.setErrorHandler(this.errorHandler);
		}
		return interceptor;
	}
 
 }

在这一步折腾了很久,刚开始我想用@Bean直接配置CacheInterceptor,但是总是报各种奇怪的错误或者是Bean重名之类的,让我很奇怪,因为我对springboot的配置原理一知半解,如果不能进行同名替换,那么@Bean是如何进行配置的。我不得不去探寻springboot的配置原理,至少知道配置路径把,如此这般倒是有所收获。springboot——自动配置。

倒是知道了springboot自动配置的入口,但是我在spring.factories中并没有找到ProxyCachingConfiguration,后来发现实际上入口在@EnableCaching里,EnableCachine导入了 CachingConfigurationSelector

	@Override
	public String[] selectImports(AdviceMode adviceMode) {
		switch (adviceMode) {
			case PROXY:
				return getProxyImports();
			case ASPECTJ:
				return getAspectJImports();
			default:
				return null;
		}
	}

	/**
	 * Return the imports to use if the {@link AdviceMode} is set to {@link AdviceMode#PROXY}.
	 * 

Take care of adding the necessary JSR-107 import if it is available. */ private String[] getProxyImports() { List result = new ArrayList(); result.add(AutoProxyRegistrar.class.getName()); result.add(ProxyCachingConfiguration.class.getName());//导入缓存配置 if (jsr107Present && jcacheImplPresent) { result.add(PROXY_JCACHE_CONFIGURATION_CLASS); } return result.toArray(new String[result.size()]); }

于是我有了一个大胆的想法,那就是自己写一个@EnabledCaching类和CachineConfigurationSelector,导入自己的ProxyCachineConfiguration类,最后导入自己写的CacheInterceptor,其他的部分就继续导入它已有的类。最后实践证明我成功了。这一部分就不展开了,除了以上的几个类,只要有编译错误的类就自己写一个就行。这样算不算变相地修改源码?

然后是核心代码,CacheAspectSupport类中

private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
		logger.info("=====================myIntercepter=======================");
		// Special handling of synchronized invocation
		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, new Callable() {
						@Override
						public Object call() throws Exception {
							return 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
				return invokeOperation(invoker);
			}
		}


		// Process any early evictions
		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 cachePutRequests = new LinkedList();
		if (cacheHit == null) {
			collectPutRequests(contexts.get(CacheableOperation.class),
					CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
		}
		Object cacheValue;
		Object returnValue;

		if (cacheHit != null && cachePutRequests.isEmpty() && !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);
		}

		for (CachePutRequest cachePutRequest : cachePutRequests) {
			cachePutRequest.apply(cacheValue);
		}
        //发布事务事件
		publisher.publishEvent(new CacheDTO(cacheValue, this, contexts));
		// 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;
	} 
  

我把处理缓存的逻辑换成了发出事件,并把相关的参数传进去,注意里面有些类在源代码中是私有的,我给改成公有的了,不然传不出去

public class CacheDTO {
	private Object cacheValue;
	private CacheAspectSupport cacheAspectSupport;
	private CacheOperationContexts contexts;
	public CacheDTO(Object cacheValue) {
		super();
		this.cacheValue = cacheValue;
	}
	
	public CacheDTO(Object cacheValue) {
		super();
		this.cacheValue = cacheValue;
	}
	

	public CacheDTO(Object cacheValue, CacheAspectSupport cacheAspectSupport, CacheOperationContexts contexts) {
		super();
		this.cacheValue = cacheValue;
		this.cacheAspectSupport = cacheAspectSupport;
		this.contexts = contexts;
	}

	public CacheDTO() {
		super();
		// TODO Auto-generated constructor stub
	}

	public Object getCacheValue() {
		return cacheValue;
	}

	public void setCacheValue(Object cacheValue) {
		this.cacheValue = cacheValue;
	}

	public CacheAspectSupport getCacheAspectSupport() {
		return cacheAspectSupport;
	}

	public void setCacheAspectSupport(CacheAspectSupport cacheAspectSupport) {
		this.cacheAspectSupport = cacheAspectSupport;
	}

	public CacheOperationContexts getContexts() {
		return contexts;
	}

	public void setContexts(CacheOperationContexts contexts) {
		this.contexts = contexts;
	}
	
}
@Component  
public class MachineTransactionalEventListener{
	Logger logger = LoggerFactory.getLogger(this.getClass());

	@TransactionalEventListener(fallbackExecution = true,phase=TransactionPhase.AFTER_COMMIT)  
    public void handleCachePut(CacheDTO cache) throws Exception{  
		logger.info("====================handleCachePut=======================");
		CacheOperationContexts contexts = cache.getContexts();
		List cachePutRequests = new LinkedList();
		Object cacheValue = cache.getCacheValue();
//CachePut和CacheEvict在事务提交之后执行		cache.getCacheAspectSupport().collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
		for (CachePutRequest cachePutRequest : cachePutRequests) {
			cachePutRequest.apply(cacheValue);
		}
		// Process any late evictions
		cache.getCacheAspectSupport().processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
    }  
}

最后成功实现了在事务后提交缓存的功能。这次收获还是比较大,清楚了springboot的配置入口,修改了源代码,有了这次的经验未来修改源码就更有底气了。

你可能感兴趣的:(缓存,事务)