SpringBoot与缓存源码分析

记录一下,学习springboot与缓存整合的笔记

Spring从3.1开始定义了org.springframework.cache.Cacheorg.springframework.cache.CacheManager接口来统一不同的缓存技术;并支持使用JCache(JSR-107)注解简化我们开发;

  • Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
  • Cache接口下Spring提供了各种xxxCache的实现;如RedisCacheEhCacheCache ,
  • ConcurrentMapCache等;

每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。

springboot与cache整合

springboot提供了有关缓存的场景启动器。只需引入spring-boot-starter-cache
与缓存相关的概念:

  • Cache 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等
  • CacheManager 缓存管理器,管理各种缓存(Cache)组件
  • keyGenerator 缓存数据时key生成策略

与缓存相关的注解:

  • @EnableCaching:开启基于注解的缓存
  • @Cacheable : 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
  • @CacheEvict:清空缓存 (属性设置:cacheNames/value指定清空那个cache, key:指定要清空缓存中的key, allEntries清空所有cahe中所有的缓存,默认是false,beforeInvocation方法之前清空缓存,默认是false)
  • @CachePut:保证方法被调用,又希望结果被缓存。
  • @Cache :可以自定义复杂的缓存规则
  • @CacheConfig:可以标注在类上,用来设置公共的属性。
入门环境搭建。(使用 @Cacheable作为记录)

1.引入先关依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.21.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.kuake</groupId>
	<artifactId>springboot01-cache</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>springboot01-cache</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<!--缓存场景启动器-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-cache</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.3.4</version>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

配置连接数据库信息

##配置连接数据库信息
spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql:///springbootidea
    driver-class-name: com.mysql.jdbc.Driver

# 日志级别 trace debug info warn error
logging:
  level:
    com.kuake.springboot.mapper: debug

2.定义实体类

public class Department {

  private long id;
  private String departmentName;


  public long getId() {
    return id;
  }

  public void setId(long id) {
    this.id = id;
  }


  public String getDepartmentName() {
    return departmentName;
  }

  public void setDepartmentName(String departmentName) {
    this.departmentName = departmentName;
  }


  @Override
  public String toString() {
    return "Department{" +
            "id=" + id +
            ", departmentName='" + departmentName + '\'' +
            '}';
  }
}

3.定义mapper相关方法接口

public interface DepartmentMapper {

    @Select("SELECT * FROM department WHERE id =#{id}")
     Department selectById(Integer id);

    @Delete("DELETE  FROM department WHERE id=#{id}")
    void deleteById(Integer id);

    @Update("UPDATE department SET  departmentName =#{department} WHERE #{id}")
    void update(Department department);

    @Insert("INSERT INTO department(departmentName) VALUES (#{departmentName})")
    void insert(Department department);
}

service层方法

@Service
public class DepartmentService {
    @Autowired
    private DepartmentMapper departmentMapper;

    @Cacheable(cacheNames = "dep" )//标注此方法,开启缓存
    public Department selectById(Integer id){
        System.out.println("从数据库中查询:"+id+"部门");
        Department department = departmentMapper.selectById(id);
        return department;
    }

    public void update(Department department){
        departmentMapper.update(department);
    }

    public void delete(Integer id){
        departmentMapper.deleteById(id);
    }

    public  void insert(Department department){
        departmentMapper.insert(department);
    }
}

controller方法

@Controller
public class DepartmentController {
    @Autowired
    private DepartmentService departmentService;

    @GetMapping("/dep/{id}")
    @ResponseBody
    public Department  getDep(@PathVariable Integer id){
        Department department = departmentService.selectById(id);
        return department;
    }
}

开始测试,浏览器访问:http://localhost:8080/dep/2
在这里插入图片描述
控制台:
在这里插入图片描述
发出sql从数据中查询,当再次刷新浏览器,重新发送请求时候,控制台没有打印sql语句,说明没有查询从数据库中查询。从缓存中获取。


自动配置原理的简单分析:
进入与catch相关的自动配置 CacheAutoConfiguration

@Configuration//这个是个配置类
@ConditionalOnClass(CacheManager.class)//判断是否有CacheManager这个类
@ConditionalOnBean(CacheAspectSupport.class) //判断是否有CacheAspectSupport这个对象
@ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver") //判断容器是否有name为cacheResolver的CacheManager对象
@EnableConfigurationProperties(CacheProperties.class) //开启自动配置类,并且将与配置文件绑定的配置类,加入到容器当中
@AutoConfigureBefore(HibernateJpaAutoConfiguration.class) //在HibernateJpaAutoConfiguration对象创建前加入到容器当中
@AutoConfigureAfter({ CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class, //在上述之后加入到容器当中
		RedisAutoConfiguration.class })
@Import(CacheConfigurationImportSelector.class) //【重点】 导入一个CacheConfigurationImportSelector
public class CacheAutoConfiguration {

CacheConfigurationImportSelector的代码如下:

	/**
	 * {@link ImportSelector} to add {@link CacheType} configuration classes.
	 */
	static class CacheConfigurationImportSelector implements ImportSelector {

		@Override
		public String[] selectImports(AnnotationMetadata importingClassMetadata) {
			CacheType[] types = CacheType.values();
			String[] imports = new String[types.length];
			// 获得所有的CacheConfigurations 并且保存在imports当中
			for (int i = 0; i < types.length; i++) {
				imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
			}
			return imports;
		}

	}

在上面代码打上断点,探究有哪些CacheConfigurations 相关对象。
在这里插入图片描述
一共有11个与xxxxCacheConfigurations 相关对象,通过源码得知 默认的使用的SimpleCacheConfiguration

/**
 * Simplest cache configuration, usually used as a fallback.
 *
 * @author Stephane Nicoll
 * @since 1.3.0
 */
@Configuration
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class SimpleCacheConfiguration {

	private final CacheProperties cacheProperties;

	private final CacheManagerCustomizers customizerInvoker;

	SimpleCacheConfiguration(CacheProperties cacheProperties,
			CacheManagerCustomizers customizerInvoker) {
		this.cacheProperties = cacheProperties;
		this.customizerInvoker = customizerInvoker;
	}

	@Bean
	public ConcurrentMapCacheManager cacheManager() {
		// 给容器中添加了ConcurrentMapCacheManager,缓存管理
		ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
		List<String> cacheNames = this.cacheProperties.getCacheNames();
		if (!cacheNames.isEmpty()) {
			cacheManager.setCacheNames(cacheNames);
		}
		return this.customizerInvoker.customize(cacheManager);
	}

}

自动配置总结:
1.CacheAutoConfiguration使用了@Import(CacheConfigurationImportSelector.class)导入了相关xxxxCacheConfigurations
2.默认使用的SimpleCacheConfiguration给容器当中添加了一个ConcurrentMapCacheManagercacahe管理器组件。


执行过程原理分析:

当我们执行@Cacheable标注的方法时:

ConcurrentMapCacheManager的 getCache方法
	@Override
	public Cache getCache(String name) {
		// 在map中获取 key为name的value   这里的name就是@Cacheable(cacheNames = "dep" )设置的值
		Cache cache = this.cacheMap.get(name);
		if (cache == null && this.dynamic) {
			synchronized (this.cacheMap) {
			// 获取
				cache = this.cacheMap.get(name);
				if (cache == null) {
					//如果 null 那么就创建一个ConcurrentMapCache 
					cache = createConcurrentMapCache(name);
					// 把新创建cache  放入ConcurrentMap<String, Cache> cacheMap 当中
					this.cacheMap.put(name, cache);
				}
			}
		}
		return cache;
	}

从cache当中通过key查找 value

	/**
	 * Find a cached item only for {@link CacheableOperation} that passes the condition.
	 * @param contexts the cacheable operations
	 * @return a {@link Cache.ValueWrapper} holding the cached item,
	 * or {@code null} if none is found
	 */
	private Cache.ValueWrapper findCachedItem(Collection<CacheOperationContext> contexts) {
		Object result = CacheOperationExpressionEvaluator.NO_RESULT;
		for (CacheOperationContext context : contexts) {
			if (isConditionPassing(context, result)) {
				//获得缓存的 key  这个方法后面会 默认的生成方式是:SimpleKeyGenerator#generateKey() 
				Object key = generateKey(context, result);
				//执行findInCaches ,
				//最后this.store.get(key),也就是从 ConcurrentMap<Object, Object> store	
				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;
	}

如果对应的cache当中内容不为空,那么直接返回cacahe 如果为空。

	// 这个就是从缓存当中 寻找key的value 如果为null 
		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<CachePutRequest>();
		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 {
			// 如果缓存中没有找到 通过反射 调用源方法,获得返回值 也就是 depatement的信息
			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) {
			// 这段代码的内部就是 调用put方法 将返回值的信息又添加到  cache当中
			cachePutRequest.apply(cacheValue);
		}

		// Process any late evictions
		processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
		
		// 最后返回结果
		return returnValue;
	}

其存储关系如下:
在这里插入图片描述
下面接着来看一下generateKey(context, result)方法。

		protected Object generateKey(Object result) {
			if (StringUtils.hasText(this.metadata.operation.getKey())) {
			// 如果注解上 设置了key属性,那么取key设置的值
				EvaluationContext evaluationContext = createEvaluationContext(result);
				return evaluator.key(this.metadata.operation.getKey(), this.methodCacheKey, evaluationContext);
			}
			//如果没有 调用keyGenerator方法
			return this.metadata.keyGenerator.generate(this.target, this.metadata.method, this.args);
		}
	public static Object generateKey(Object... params) {
		if (params.length == 0) {
			// 返回 new SimpleKey()
			return SimpleKey.EMPTY;
		}
		if (params.length == 1) {
		// 如果参数只有一个 那么 key=参数的值
			Object param = params[0];
			if (param != null && !param.getClass().isArray()) {
				return param;
			}
		}
		// 多个参数 调用SimpleKey(params)
		return new SimpleKey(params);
	}

执行过程总结:
1、ConcurrentMapCacheManager调用getCache 获得createConcurrentMapCache 缓存组件

  • 如果 ConcurrentMap<String, Cache> cacheMap有那么返回;
  • 否则,创建一个 【createConcurrentMapCache(name)】,然后添加到cacheMap当中

2、通过 generateKey(context, result)生成的key在对应的缓存中寻找value,

  • 如果存在,直接从缓存中通过get获得 并返回
  • 否则,将执行目标方法,获得值,并执行对应cache的put方法 将目标方法的值,添加到缓存当中。
    也就是一句话,有则取,无则创建或执行
    (同样可以整合RedisCache,让redis来存储缓存,导入spring-boot-starter-data-redis场景启动器。然后加以简单配置就可以)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值