记录一下,学习springboot与缓存整合的笔记
Spring从3.1开始定义了org.springframework.cache.Cache
和org.springframework.cache.CacheManager
接口来统一不同的缓存技术;并支持使用JCache(JSR-107)注解简化我们开发;
- Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
- Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache ,
- 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
给容器当中添加了一个ConcurrentMapCacheManager
cacahe管理器组件。
执行过程原理分析:
当我们执行@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
场景启动器。然后加以简单配置就可以)