首页 > 编程语言 >Spring缓存注解的使用与源码分析

Spring缓存注解的使用与源码分析

时间:2023-04-23 10:34:00浏览次数:40  
标签:缓存 return Spring ann cache public 源码 class


Spring Cache提供了一个对缓存使用的抽象,以及大量的实现方便开发者使用。

Spring Cache主要提供了如下注解:

注解

说明

@Cacheable

根据方法的请求参数对其结果进行缓存

@CachePut

根据方法的请求参数对其结果进行缓存,和@Cacheable不同的是,它每次都会触发真实方法的调用

@CacheEvict

根据一定的条件对缓存进行清空

缓存注解的使用

配置类

@PropertySource("classpath:redis.properties")
@Configuration
@EnableCaching // 开启缓存注解
public class CacheConfig {

	@Value("${redis.host}")
	private String hostName;

	@Value("${redis.port}")
	private Integer port;

	@Value("${redis.password}")
	private String password;
	
	@Bean
	public RedisCacheManager redisCacheManager() {
		return RedisCacheManager.create(jedisConnectionFactory());
	}

	@Bean
	public JedisConnectionFactory jedisConnectionFactory() {
		RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(hostName, port);
		redisStandaloneConfiguration.setPassword(password);
		JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(redisStandaloneConfiguration);
		return jedisConnectionFactory;
	}
}

service层的使用

@Cacheable(cacheNames = "redisCache", key = "'good' + #id")
public Good queryById(Integer id) {
	System.out.println("-------queryById-------");
	return goodDao.selectById(id);
}

@CachePut(cacheNames = "redisCache", key = "'good' + #id")
public Good updateGood(Integer id) {
	Good good = new Good();
	good.setId(id);
	good.setGoodName("iphone xx");
	goodDao.updateById(good);
	return good;
}

测试类

public class CacheDemo {

	public static void main(String[] args) {
		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
		applicationContext.register(GoodDao.class);
		applicationContext.register(GoodServiceImpl.class);
		applicationContext.register(CacheConfig.class);
		applicationContext.register(JdbcConfig.class);
		applicationContext.refresh();

		GoodService goodService = applicationContext.getBean(GoodService.class);
		Good good = goodService.updateGood(6);
		goodService.queryById(good.getId());
	}
}

运行结果如下:

DEBUG JdbcTemplate:860 - Executing prepared SQL update
DEBUG JdbcTemplate:609 - Executing prepared SQL statement [update t_good set good_name=? where id=?]
DEBUG DataSourceUtils:115 - Fetching JDBC Connection from DataSource
DEBUG DriverManagerDataSource:144 - Creating new JDBC DriverManager Connection to [jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&allowMultiQueries=true&characterEncoding=UTF-8&useFastDateParsing=false&zeroDateTimeBehavior=convertToNull]

从运行结果可以发现第二次查询直接走的缓存切面,并没有去查询数据库。

源码分析

@EnableCaching

@EnableCaching用于向容器中注入相关配置类:

org.springframework.cache.annotation.EnableCaching

@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {

@EnableCaching导入了CachingConfigurationSelector。

org.springframework.cache.annotation.CachingConfigurationSelector

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

private String[] getProxyImports() {
	List<String> result = new ArrayList<>(3);
	result.add(AutoProxyRegistrar.class.getName());
	result.add(ProxyCachingConfiguration.class.getName());
	if (jsr107Present && jcacheImplPresent) {
		result.add(PROXY_JCACHE_CONFIGURATION_CLASS);
	}
	return StringUtils.toStringArray(result);
}

CachingConfigurationSelector注入了AutoProxyRegistrar和ProxyCachingConfiguration。

AutoProxyRegistrar

顾名思义,AutoProxyRegistrar从名称上就可以看出其本质是一个ImportBeanDefinitionRegistrar,而ImportBeanDefinitionRegistrar最重要的方法为registerBeanDefinitions(),这个方法的主要作用就是用方法参数中的registry向容器中注入一些BeanDefinition。

org.springframework.context.annotation.AutoProxyRegistrar#registerBeanDefinitions

–> org.springframework.aop.config.AopConfigUtils#registerAutoProxyCreatorIfNecessary(org.springframework.beans.factory.support.BeanDefinitionRegistry)

–> org.springframework.aop.config.AopConfigUtils#registerAutoProxyCreatorIfNecessary(org.springframework.beans.factory.support.BeanDefinitionRegistry, java.lang.Object)

public static BeanDefinition registerAutoProxyCreatorIfNecessary(
		BeanDefinitionRegistry registry, @Nullable Object source) {

	return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);
}

总结:AutoProxyRegistrar.registerBeanDefinitions()往容器中注入了一个类InfrastructureAdvisorAutoProxyCreator。

这段是不是很熟悉,没错,声明式事务中注入的也是这个InfrastructureAdvisorAutoProxyCreator类,用来生成代理对象。

ProxyCachingConfiguration

ProxyCachingConfiguration向Spring容器中注入了三个Bean:

  1. BeanFactoryCacheOperationSourceAdvisor:切面,用来匹配目标方法和目标类,看那些方法需要实现缓存的增强。
  2. CacheOperationSource:用来解析缓存相关的注解
  3. CacheInterceptor:实现了Advice,是一个通知,实现对目标方法的增强。
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
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());
		if (this.enableCaching != null) {
			advisor.setOrder(this.enableCaching.<Integer>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.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager);
		interceptor.setCacheOperationSource(cacheOperationSource());
		return interceptor;
	}

}

BeanFactoryCacheOperationSourceAdvisor

BeanFactoryCacheOperationSourceAdvisor是一个切面,切面需要包括通知和切点:

  • Advice通知:CacheInterceptor,由ProxyCachingConfiguration注入。
  • Pointcut切点:内部CacheOperationSourcePointcut类型的属性。

CacheOperationSourcePointcut

Pointcut主要包含两部分,对目标类的匹配,对目标方法的匹配。

对目标类的匹配,其实啥也没干,返回true,主要逻辑在对目标方法的匹配上,大部分Pointcut都是如此:

private class CacheOperationSourceClassFilter implements ClassFilter {

	@Override
	public boolean matches(Class<?> clazz) {
		if (CacheManager.class.isAssignableFrom(clazz)) {
			return false;
		}
		// 啥也没干
		CacheOperationSource cas = getCacheOperationSource();
		return (cas == null || cas.isCandidateClass(clazz));
	}
}

对目标方法的匹配:

public boolean matches(Method method, Class<?> targetClass) {
	CacheOperationSource cas = getCacheOperationSource();
	/**
	 * @see AbstractFallbackCacheOperationSource#getCacheOperations(java.lang.reflect.Method, java.lang.Class)
	 */
	// 就是看目标方法上面有没有缓存注解
	return (cas != null && !CollectionUtils.isEmpty(cas.getCacheOperations(method, targetClass)));
}

这里会委托给CacheOperationSource来解析缓存注解,这个类是在ProxyCachingConfiguration中注入的。

最终会调用到org.springframework.cache.annotation.SpringCacheAnnotationParser#parseCacheAnnotations(org.springframework.cache.annotation.SpringCacheAnnotationParser.DefaultCacheConfig, java.lang.reflect.AnnotatedElement, boolean)

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

	// @Cacheable解析为CacheableOperation
	// @CacheEvict解析为CacheEvictOperation
	// @CachePut解析为CachePutOperation
	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;
}

CacheInterceptor

CacheInterceptor是一个Advice,用来实现对目标方法的增强,当调用目标方法时会先进入CacheInterceptor.invoke()方法:

org.springframework.cache.interceptor.CacheInterceptor#invoke

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

	try {
		return execute(aopAllianceInvoker, invocation.getThis(), method, invocation.getArguments());
	}
	catch (CacheOperationInvoker.ThrowableWrapper th) {
		throw th.getOriginal();
	}
}

org.springframework.cache.interceptor.CacheAspectSupport#execute(org.springframework.cache.interceptor.CacheOperationInvoker, java.lang.reflect.Method, org.springframework.cache.interceptor.CacheAspectSupport.CacheOperationContexts)

private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
	// Special handling of synchronized invocation
	if (contexts.isSynchronized()) { // false不会进入
		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);
		}
	}


	// Process any early evictions
	// 先调用@CacheEvict注解中的属性beforeInvocation=true的,实际上这个值默认为false,这里一般不会干啥,看后面的调用
	// cache.evict()
	processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
			CacheOperationExpressionEvaluator.NO_RESULT);

	// Check if we have a cached item matching the conditions
	// cache.get()
	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
	// 调用@CacheEvict注解中的属性beforeInvocation=false的
	processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);

	return returnValue;
}

扩展缓存注解

有时候由于业务需要或者Spring提供的缓存不满足我们的要求,如无法解决缓存雪崩问题,扩展步骤如下:

  1. 实现CacheManager接口或继承AbstractCacheManager,管理自身的cache实例,也可以直接使用内置的SimpleCacheManager。
  2. 实现Cache接口,自定义缓存实现逻辑。
  3. 将自定义的Cache和CacheManager进行关联并注入到Spring容器中。


标签:缓存,return,Spring,ann,cache,public,源码,class
From: https://blog.51cto.com/u_6784072/6216470

相关文章

  • SpringMVC启动流程源码分析
    SpringMVC向WEB容器中注入了两个对象:ContextLoaderListener:由AbstractContextLoaderInitializer注入。DispatcherServlet:由AbstractDispatcherServletInitializer注入。下面分别分析这两个对象在WEB容器启动时做了什么工作?ContextLoaderListenerContextLoaderListener实现了Servle......
  • vue3 keep-alive实现tab页面缓存
    先上图 如何在我们切换tab标签的时候,缓存标签最后操作的内容,简单来说就是每个标签页中设置的比如搜索条件及结果、分页、新增、编辑等数据在切换回来的时候还能保持原样。看看keep-alive是如何实现该功能的。首先我们要了解keep-alive的基本使用。具体介绍请查看官方文档(htt......
  • 为spring boot定制启动banner
    直接打开这个网站 https://patorjk.com/software/taag/#p=testall&f=Larry%203D&t=Type%20Something%20 输入你想要的文字内容,点TestAll即可,我们这里选择的字体是:Larry3D,你也可以根据喜好,选择自己想要的字体 复制并保存到src/main/resources/banner.txt即可 参考资料:......
  • spring boot配置mybatis出现Invalid bound statement (not found)报错的解决办法
     背景:spring-boot-starter-parent2.5.6mybatis-spring-boot-starter2.2.0我遇到这个报错,是因为使用idea创建xml文件是没有后缀,举个例子,比如你创建的是AccountMapper.xml,结果使用idea创建的是AccountMapper,根本就没有后缀!解决办法也很简单,加上后缀就可以了,不需要做其他额外的......
  • SpringDay01-入门基础知识、Bean的配置(一)
    Spring(黑马)一、基础知识1.1传统JavaWeb的缺点传统的JavaWeb在实现某个主要的业务逻辑时需要做的事情:new一个实现类对象,然后通过对象调用某个主要的方法;开启事务、提交事务、回滚事务;在日志中记录修改数据;在日志中记录异常数据等。以上传统方法带来的问题:实现类与接......
  • 记录一次艰难的云服务器部署前后端项目springBoot+mybatis和vue(两天解决的前后端跨域
    前言大家好我是歌谣今天继续给大家带来后端java的学习最近刚学习完java的一个增删改查紧接着就是部署项目了代码准备工作前端:vue后端:springboot+mybatis数据库mysql部署后端项目打包找到maven-package-runmavenbuild云服务器上面建立文件mkdir/www/springBoot创建文件......
  • SpringSecurity过滤器之HeaderWriterFilter
    HeaderWriterFilter用于对当前的HttpServletResponse添加某些浏览器保护的响应头。HeaderWriterFilter由HeadersConfigurer配置,在执行HeadersConfigurer#configure时调用createHeaderWriterFilter创建HeaderWriterFilter,同时添加了HeaderWriter集合:privateList<HeaderWriter>ge......
  • SpringMVC 后台从前端获取单个参数
    1.编写web.xml(模板)2.springmvc配置文件3.编写对应数据库字段的pojo实体类@Data@AllArgsConstructor@NoArgsConstructorpublicclassUser{privateintid;privateStringname;privateintage;}ViewCode4.编写Controller类首先是从前端获取单......
  • 【Visual Leak Detector】源码文件概览
    说明使用VLD内存泄漏检测工具辅助开发时整理的学习笔记。本篇对VLD源码包中的各文件用途做个概述。同系列文章目录可见《内存泄漏检测工具》目录目录说明1.整体概览2.文件夹.teamcity3文件夹lib3.1文件夹cppformat(生成libformat)3.2文件夹dbghelp3.3文件夹gtest......
  • Forest-声明式HTTP客户端框架-集成到SpringBoot实现调用第三方restful api并实现接口
    场景Forest声明式HTTP客户端API框架,让Java发送HTTP/HTTPS请求不再难。它比OkHttp和HttpClient更高层,是封装调用第三方restfulapiclient接口的好帮手,是retrofit和feign之外另一个选择。通过在接口上声明注解的方式配置HTTP请求接口。官网:Forest 代码地址:forest:声明式HTTP客户......