赞
踩
记录一下,学习springboot与缓存整合的笔记
Spring从3.1开始定义了org.springframework.cache.Cache
和org.springframework.cache.CacheManager
接口来统一不同的缓存技术;并支持使用JCache(JSR-107)注解简化我们开发;
每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。
springboot
提供了有关缓存的场景启动器。只需引入spring-boot-starter-cache
与缓存相关的概念:
与缓存相关的注解:
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 缓存组件
2、通过 generateKey(context, result)生成的key在对应的缓存中寻找value,
spring-boot-starter-data-redis
场景启动器。然后加以简单配置就可以)Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。