java框架之SpringBoot(11)-缓存抽象及整合Redis
Spring缓存抽象
介绍
Spring 从 3.1 版本开始定义了 org.springframework.cache.Cache 和 org.springframework.cache.CacheManager 接口来统一不同的缓存技术,并支持使用 JCache(JSR107)注解简化我们开发。
Java Caching 定义了 5 个核心接口,分别是 CachingProvider、CacheManager、Cache、Entry 和Expiry。
- CachingProvider :可以用来创建、配置、获取、管理和控制多个 CacheManager。一个应用可以在运行期间访问多个 CachingProvider。
- CacheManager: 可以用来创建、配置、获取、管理和控制多个唯一命名的 Cache,这些 Cache 存在于 CacheManager 的上下文中。一个 CacheManager 仅被一个 CachingProvider 所拥有。
- Cache :是一个类似 Map 数据结构并临时存储以 key 为索引的值。一个 Cache 仅被一个 CacheManager 所拥有。
- Entry :是一个存储在 Cache 中的 key-value 对。
- Expiry:每一个存储在 Cache 中的条目有一个定义的有效期。一旦超过这个时间,条目就变更为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过 ExpiryPolicy 设置。
由于 JSR107 的使用相对来说比较繁杂,所以这里我们使用 Spring 提供的缓存抽象。
- Cache 接口为缓存的组件规范定义,包含缓存的各种操作集合。
- Cache 接口下 Spring 提供了各种 Cache 的实现,如 RedisCache、EhCacheCache、ConcurrentMapCache 等。
每次调用具有缓存功能的方法时,Spring 会检查指定目标方法是否已经被调用过,如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回,下次调用就直接从缓存中获取。
使用 Spring 缓存抽象时我们需要关注以下两点:
- 确定方法需要被缓存以及它们的缓存策略。
- 从缓存中读取之前缓存存储的数据。
概念&注解
| Cache | 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache 等。 |
| @CacheManager | 缓存管理器,管理各种缓存(Cache)组件。 |
| @Cacheable | 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存。 |
| @CacheEvict | 清空缓存。 |
| @CachePut | 保证方法被调用,又希望结果被缓存。 |
| @EnableCaching | 开启基于注解的缓存。 |
| keyGenerator | 缓存数据时 key 的生成策略。 |
| serialize | 缓存数据时 value 的序列化策略。 |
简单使用
搭建测试环境
1、新建 SpringBoot 项目,引入如下场景启动器:Cache、Web、Mysql、MyBatis。
2、初始化测试数据:
; DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` ) NOT NULL AUTO_INCREMENT, `name` ) DEFAULT NULL, `gender` ) DEFAULT NULL COMMENT '0:女 1:男', `birthday` date DEFAULT NULL, `address` ) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT; -- ---------------------------- -- Records of user -- ---------------------------- ', '1997-02-23', '北京'); ', '1998-02-03', '武汉'); ', '1996-06-04', '上海');
user.sql
3、编写与表对应的 JavaBean:
package com.springboot.bean;
import java.util.Date;
public class User {
private Integer id;
private String name;
private Integer gender;
private Date birthday;
private String address;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", gender=" + gender +
", birthday=" + birthday +
", address='" + address + '\'' +
'}';
}
}
com.springboot.bean.User
4、配置数据库连接信息:
spring:
datasource:
username: root
password: root
url: jdbc:mysql://192.168.202.136:3306/springboot_cache
driver-class-name: com.mysql.jdbc.Driver
application.yml
5、编写测试Mapper:
package com.springboot.mapper;
import com.springboot.bean.User;
import org.apache.ibatis.annotations.*;
import java.util.List;
public interface UserMappper {
@Select("select * from user")
public List<User> getAll();
@Select("select * from user where id=#{id}")
public User getById(Integer id);
@Delete("delete from user where id=#{id}")
public void deleteById(Integer id);
@Options(useGeneratedKeys = true,keyProperty = "id")
@Insert("insert into user(name,gender,birthday,address) values(#{name},#{gender},#{birthday},#{address})")
public void add(User user);
@Update("update user set name=#{name},gender=#{gender},birthday=#{birthday},address=#{address} where id=#{id}")
public void update(User user);
@Select("select * from user where name=#{name}")
public User getByName(String name);
}
com.springboot.mapper.UserMappper
6、编写 Service 并使用注解缓存功能:
package com.springboot.service;
import com.springboot.bean.User;
import com.springboot.mapper.UserMappper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {
@Autowired
private UserMappper userMappper;
@Cacheable(cacheNames = {"users"})
public List<User> getAll(){
System.out.println("UserService.getAll() 执行了");
return userMappper.getAll();
}
}
com.springboot.service.UserService
7、配置 mapper 包扫描,开启注解缓存:
package com.springboot;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching // 开启注解缓存
@MapperScan("com.springboot.mapper") // mapper 包扫描
public class CacheTestApplication {
public static void main(String[] args) {
SpringApplication.run(CacheTestApplication.class, args);
}
}
com.springboot.CacheTestApplication
8、编写 Controller 并测试:
package com.springboot.controller;
import com.springboot.bean.User;
import com.springboot.mapper.UserMappper;
import com.springboot.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/users")
public List<User> getAll(){
return userService.getAll();
/*
首次访问会输出以下内容:
UserService.getAll() 执行了
后续访问不会输出,说明实际上并没有执行 userService.getAll() 方法,即未查询数据库从中获取数据,而是从缓存中获取
缓存成功
*/
}
}
com.springboot.controller.UserController
CacheManager 是用来管理多个 Cache 组件的,对缓存的 CRUD 操作实际上还是通过 Cache 组件,而每一个 Cache 组件有自己唯一名称。
@Cacheable
主要针对方法配置,能够根据方法的请求参数对其结果进行缓存。
属性
- cacheNames/value:指定 Cache 组件名称。
- key:指定缓存数据所使用的 key,默认 key 使用参数值。比如 id 为 1,那么在 Cache 组件中的 key 就为 1。
key 还可以通过 SpEL 表达式进行取值。规则如下:
名字 位置 描述 实力 methodName root object 当前被调用的方法名 #root.methodName method root object 当前被调用的方法对象 #root.method.name target root object 当前被调用的目标对象 #root.target targetClass root object 当前被调用的目标对象类 #root.targetClass args root object 当前被调用方法的参数列表 #root.args[0] caches root object 当前被调用方法使用的缓存组件列表,如 @Cacheable(value={"cache1","cache2"}) 就有两个缓存组件 #root.caches[0].name argument name evaluation context 可以直接使用 #参数名 来获取对应参数值,也可以使用 #p0 或 #a0 的形式,0 代表参数索引 #id、#a0、#p0 result evaluation context 获取方法执行后的返回值 #result - keyGenerator:通过 IoC 容器中的生成器组件 id 来指定 key 的生成器,key 与 keyGenerator 二选一使用。
- cacheManager:指定缓存管理器 bean。
- cacheSolver:指定缓存解析器 bean。
- condition:指定条件,当符合指定条件时才进行缓存,也可使用 SpEL 表达式同 key。
- unless:指定条件,当符合指定条件时不进行缓存,也可使用 SpEL 表达式同 key。
- sync:是否使用异步模式,如果启用,那么 unless 属性失效。
示例
@Cacheable(cacheNames = {"users"})
public User getById(Integer id) {
System.out.println("UserService.getById(" + id + ") 执行了");
return userMappper.getById(id);
}
com.springboot.service.UserService#getById
@GetMapping("/user/{id}")
public User getById(@PathVariable Integer id) {
return userService.getById(id);
/*
首次访问 http://localhost:8080/user/1 会输出一下内容:
UserService.getById(1) 执行了
后续访问不会输出,说明实际上并没有执行 userService.getById(1) 方法,即未查询数据库从中获取数据,而是从缓存中获取
而如果访问 http://localhost:8080/user/2 会输出一下内容:
UserService.getById(2) 执行了
后续访问也不会输出,即会自动根据参数的不同进行缓存
OK 缓存成功
*/
}
com.springboot.controller.UserController#getById
@CachePut
主要针对方法配置,目标方法正常执行完成后将其结果更新到缓存。
属性
参考 @Cacheable 注解属性。
示例
/*
注意:这里要保证 key 与 getById 方法所使用 key 的生成后的值相同,否则会出现更新缓存后通过 getById 获取数据依旧为旧数据的情况
*/
@CachePut(cacheNames = {"users"}, key = "#user.id")
public User update(User user) {
System.out.println("UserService.update(" + user + ") 执行了");
userMappper.update(user);
return user;
}
com.springboot.service.UserService#update
@PutMapping("/user")
public User update(User user){
return userService.update(user);
/*
1、以 get 方式访问 localhost:8080/user/1 会输出一下内容:
UserService.getById(1) 执行了
后续访问不会输出
2、以 put 方式访问 localhost:8080/user,修改数据,输出以下内容:
UserService.update(User{id=1, name='张三new', gender=0, birthday=Wed Apr 03 00:00:00 CST 1996, address='深圳'}) 执行了
每次访问都会输出
3、以 get 方式访问 localhost:8080/user/1 不会输出内容,返回数据是更新后的数据
*/
}
com.springboot.controller.UserController#update
@CacheEvict
主要针对方法配置,默认在目标方法正常执行完成后清除缓存。
属性
- beforeInvocation:默认为 false,标识是否在目标方法执行之前清除缓存。
- allEntries:默认为 false,标识是否清除缓存组件中所有内容。
更多可参考 @Cacheable 注解属性。
示例
/*
删除缓存,key 可以不指定,因为 key 默认就是以 id 为基础生成的
*/
@CacheEvict(cacheNames = {"users"}, key = "#id")
public void delete(Integer id) {
System.out.println("UserService.delete(" + id + ") 执行了");
}
com.springboot.service.UserService#delete
@DeleteMapping("/user/{id}")
public void delete(@PathVariable Integer id){
userService.delete(id);
/*
1、以 get 方式访问 localhost:8080/user/1 会输出以下内容
UserService.getById(1) 执行了
后续访问不会输出
2、以 delete 方式访问 localhost:8080/user/1 会输出以下内容
UserService.delete(1) 执行了
每次访问都会输出
3、以 get 方式再次访问 localhost:8080/user/1 会输出以下内容
UserService.getById(1) 执行了
即缓存被删除了重新执行方法获取了数据
*/
}
com.springboot.controller.UserController#delete
@Caching
主要针对方法配置,为 @Cacheable、@CachePut、CacheEvict 的组合注解,常用于定制较复杂的缓存策略。
属性
- cacheable:放置 @Cacheable 注解数组。
- put:放置 @CachePut 注解数组。
- evict:放置 @CacheEvict 注解数组。
示例
@Caching(
cacheable = {
// 以 name 生成 key 进行缓存
@Cacheable(value = "users", key = "#name")
},
put = {
// 以 id 生成 key ,且执行结果不为空时更新缓存
@CachePut(value = "users", key = "#result.id", condition = "#result != null")
},
evict = {
// name 为 deleteAll 清除所有缓存
@CacheEvict(value = "users", condition = "#name=='deleteAll'", allEntries = true)
}
)
public User getByName(String name) {
System.out.println("UserService.getByName(" + name + ") 执行了");
return userMappper.getByName(name);
}
com.springboot.service.UserService#getByName
@GetMapping("/user/name/{name}")
public User getByName(@PathVariable String name){
return userService.getByName(name);
/*
1、以 get 方式请求 localhost:8080/user/name/李四,输出以下内容:
UserService.getByName(李四) 执行了
每次访问都会输出,因为 @CachePut 需要执行结果更新缓存
2、以 get 方式请求 localhost:8080/user/2,没有输出内容,因为通过 [1] 已经完成了 id 为 2 的缓存,直接从缓存中取出结果返回了
3、以 get 方式请求 localhost:8080/user/name/deleteAll,输出以下内容:
UserService.getByName(deleteAll) 执行了
每次访问都会输出,因为 @CachePut 需要执行结果更新缓存
4、以 get 方式请求 localhost:8080/user/2,输出以下内容:
UserService.getById(2) 执行了
后续访问不会输出,这次输出的原因是通过 [3] 清楚了所有缓存
*/
}
com.springboot.controller.UserController#getByName
@CacheConfig
通常用来标注在类上,用来定义公共缓存配置。
属性
具有如下属性:cachenames、keyGenerator、cacheManager、cacheResolver,可参考 @Cacheable 注解属性。
示例
package com.springboot.service;
import com.springboot.bean.User;
import com.springboot.mapper.UserMappper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.*;
import org.springframework.stereotype.Service;
/**
* 因为下面方法都是使用同一个 Cache 组件,所以可以在类上一次性指定所有方法使用的 Cache 组件名称
*/
@CacheConfig(cacheNames = {"users"} )
@Service
public class UserService {
@Autowired
private UserMappper userMappper;
@Cacheable(keyGenerator = "myKeyGenerator")
public User getById(Integer id) {
System.out.println("UserService.getById(" + id + ") 执行了");
return userMappper.getById(id);
}
@CachePut(key = "#user.id")
public User update(User user) {
System.out.println("UserService.update(" + user + ") 执行了");
userMappper.update(user);
return user;
}
@CacheEvict(key = "#id")
public void delete(Integer id) {
System.out.println("UserService.delete(" + id + ") 执行了");
}
@Caching(
cacheable = {
@Cacheable(key = "#name")
},
put = {
@CachePut(key = "#result.id", condition = "#result != null")
},
evict = {
@CacheEvict(condition = "#name=='deleteAll'", allEntries = true)
}
)
public User getByName(String name) {
System.out.println("UserService.getByName(" + name + ") 执行了");
return userMappper.getByName(name);
}
}
com.springboot.service.UserService
原理及总结
依旧是从自动配置类入手以 @Cacheable 注解执行流程进行分析:
package org.springframework.boot.autoconfigure.cache;
@Configuration
@ConditionalOnClass(CacheManager.class)
@ConditionalOnBean(CacheAspectSupport.class)
@ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver")
@EnableConfigurationProperties(CacheProperties.class)
@AutoConfigureBefore(HibernateJpaAutoConfiguration.class)
@AutoConfigureAfter({ CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class,
RedisAutoConfiguration.class })
@Import(CacheConfigurationImportSelector.class)
public class CacheAutoConfiguration {
// 注册缓存管理器定制器
@Bean
@ConditionalOnMissingBean
public CacheManagerCustomizers cacheManagerCustomizers(
ObjectProvider<List<CacheManagerCustomizer<?>>> customizers) {
return new CacheManagerCustomizers(customizers.getIfAvailable());
}
@Bean
public CacheManagerValidator cacheAutoConfigurationValidator(
CacheProperties cacheProperties, ObjectProvider<CacheManager> cacheManager) {
return new CacheManagerValidator(cacheProperties, cacheManager);
}
@Configuration
@ConditionalOnClass(LocalContainerEntityManagerFactoryBean.class)
@ConditionalOnBean(AbstractEntityManagerFactoryBean.class)
protected static class CacheManagerJpaDependencyConfiguration
extends EntityManagerFactoryDependsOnPostProcessor {
public CacheManagerJpaDependencyConfiguration() {
super("cacheManager");
}
}
static class CacheManagerValidator implements InitializingBean {
private final CacheProperties cacheProperties;
private final ObjectProvider<CacheManager> cacheManager;
CacheManagerValidator(CacheProperties cacheProperties,
ObjectProvider<CacheManager> cacheManager) {
this.cacheProperties = cacheProperties;
this.cacheManager = cacheManager;
}
@Override
public void afterPropertiesSet() {
Assert.notNull(this.cacheManager.getIfAvailable(),
"No cache manager could "
+ "be auto-configured, check your configuration (caching "
+ "type is '" + this.cacheProperties.getType() + "')");
}
}
static class CacheConfigurationImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
CacheType[] types = CacheType.values();
String[] imports = new String[types.length];
for (int i = 0; i < types.length; i++) {
imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
}
return imports;
}
}
}
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration
看到第 11 行,该行是通过 @Import(CacheConfigurationImportSelector.class) 导入缓存配置类, CacheConfigurationImportSelector 为自动配置类中的内部类,对应的导入方法为 65 行 selectImports 方法。通过调试会发现它实际导入了如下配置类:
org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
那么实际生效的是哪个配置类呢?可以在配置文件中开启 debug 模式,接着在控制台中就可以看到:
SimpleCacheConfiguration matched: - Cache org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration automatic cache type (CacheCondition) - @ConditionalOnMissingBean (types: org.springframework.cache.CacheManager; SearchStrategy: all) did not find any beans (OnBeanCondition)
即生效的配置类为 org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration 。查看该配置类:
package org.springframework.boot.autoconfigure.cache;
@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 cacheManager = new ConcurrentMapCacheManager();
List<String> cacheNames = this.cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return this.customizerInvoker.customize(cacheManager);
}
}
org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration
在第 19 行可以看到往 IoC 容器中注册了一个 org.springframework.cache.concurrent.ConcurrentMapCacheManager ,该类实现了 org.springframework.cache.CacheManager 接口,可以使用它的 getCache(String) 方法来获取缓存组件:
public Cache getCache(String name) {
// this.cacheMap : private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap(16);
Cache cache = (Cache)this.cacheMap.get(name);
if (cache == null && this.dynamic) {
synchronized(this.cacheMap) {
// 从 ConcurrentMap 对象中获取 Cache 组件
cache = (Cache)this.cacheMap.get(name);
if (cache == null) { // 如果获取的 Cache 组件为空
// 新创建一个 ConcurrentMapCache 组件
cache = this.createConcurrentMapCache(name);
// 将其放入 ConcurrentMap 对象中
this.cacheMap.put(name, cache);
}
}
}
return cache;
}
org.springframework.cache.concurrent.ConcurrentMapCacheManager#getCache
可以看到该方法的作用实际上就是用来获取一个 ConcurrentMapCache 类型的 Cache 组件,而我们通过 Cache 组件获取数据时是通过 get 方法,最终是通过该类的 get 方法调用 lookup(key) 方法:
@Override
protected Object lookup(Object key) {
// this.store : private final ConcurrentMap<Object, Object> store;
return this.store.get(key);
}
org.springframework.cache.concurrent.ConcurrentMapCache#lookup
而这个 key 默认是使用 org.springframework.cache.interceptor.SimpleKeyGenerator#generateKey 生成的:
package org.springframework.cache.interceptor;
import java.lang.reflect.Method;
public class SimpleKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
return generateKey(params);
}
public static Object generateKey(Object... params) {
if (params.length == 0) {
//SimpleKey.EMPTY : public static final SimpleKey EMPTY = new SimpleKey();
return SimpleKey.EMPTY;
}
if (params.length == 1) {
Object param = params[0];
if (param != null && !param.getClass().isArray()) {
return param;
}
}
return new SimpleKey(params);
}
}
org.springframework.cache.interceptor.SimpleKeyGenerator
可以看到它的生成规则如下:
- 当目标方法的参数个数为空时,使用 new SimpleKey() 实例化一个 SimpleKey 作为 key 值。
- 当目标方法的参数个数为 1 且该参数不为空也不是数组类型时,直接使用该参数值作为 key 值。
- 当目标方法的参数不止一个时,使用 new SimpleKey(params) 实例化一个 SimpleKey 作为 key 值。
即 ConcurrentMapCache 组件也是将实际缓存数据存放在 ConcurrentMap 对象中。
ConcurrentMapCache 类实现了 Cache 接口,Cache 接口中定义了 get 方法用来获取数据、put 方法用来存放数据、evict 方法用来删除指定数据、clear 方法用来清空所有数据。
上述运行流程如下:
- 目标方法运行之前会使用 CacheManager 先根据 cacheNames 获取 Cache 组件,如果没有,则会创建一个新的 Cache 组件返回。
- 通过返回的 Cache 组件使用生成的 key 获取缓存内容。
- 如果获取到的缓存内容为空,就直接调用目标方法,然后将目标方法执行后的返回值通过 Cache 组件的 put 方法放入缓存(ConcurrentMap 对象)中;如果获取到的缓存内容不为空,就直接返回获取到的缓存内容,不会执行目标方法。
默认情况下:
- 生效的自动配置类为 SimpleCacheConfiguration。
- CacheManager 的实现为 ConcurrentMapCacheManager。
- key 是使用 KeyGenerator 生成的,实现为 SimpleKeyGenerator。
整合Redis
redis安装
参考【Linux 下源码安装 Redis】或【Docker 安装连接 Redis】。
准备环境
1、在上面简单使用示例项目的基础上再引入 redis 缓存的场景启动器:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、配置 redis:
spring:
datasource:
username: root
password: root
url: jdbc:mysql://192.168.202.136:3306/springboot_cache
driver-class-name: com.mysql.jdbc.Driver
redis:
# 指定 redis 主机地址
host: 192.168.202.136
application.yml
3、测试:
访问 localhost:8080/user/1,然后查看 Redis:

会发现 id 为 1 的用户已经被缓存到 redis。
test
RedisTemplate使用
package com.springboot;
import com.springboot.bean.User;
import com.springboot.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisTests {
@Autowired
private RedisTemplate redisTemplate; // k-v 都为字符串
@Autowired
private StringRedisTemplate stringRedisTemplate; // k-v 都为 Object
@Autowired
private UserService userService;
// 操作字符串
@Test
public void testString(){
stringRedisTemplate.opsForValue().set("msg","hello 张三");
String msg = stringRedisTemplate.opsForValue().get("msg");
System.out.println(msg);
/*
hello 张三
*/
}
// 操作对象
// 注意:序列化类型需要实现 Serializable 接口
@Test
public void testObject(){
User user = userService.getById(2);
redisTemplate.opsForValue().set("id_2", user);
Object user2 = redisTemplate.opsForValue().get("id_2");
System.out.println(user2);
/*
User{id=2, name='李四', gender=0, birthday=Tue Feb 03 00:00:00 CST 1998, address='武汉'}
*/
}
/*
还可通过如下几种方式操作列表、集合、有序集合、哈希
RedisTemplate.opsForList()
RedisTemplate.opsForSet()
RedisTemplate.opsForZSet()
RedisTemplate.opsForHash()
*/
}
test
经过上述测试我们查看 redis 中数据会发现 redis 中保存的是 SpringBoot 以默认方式序列化后的数据,如果我们想要以 json 方式序列化保存数据到 redis 我们该怎么做呢?
我们使用的 RedisTemplate 和 StringRedisTemplate bean 都是在 redis 的自动配置类中注册的,查看:
@Configuration
protected static class RedisConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean(StringRedisTemplate.class)
public StringRedisTemplate stringRedisTemplate(
RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration.RedisConfiguration
每个 RedisTemplate 对象都可定制自己的序列化器,查看源码会发现它默认使用的序列化器为 org.springframework.data.redis.serializer.JdkSerializationRedisSerializer 。我们只需要修改它默认的序列化器即可:
package com.springboot.config;
import com.springboot.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
@Configuration
public class CacheConfig {
@Bean
public RedisTemplate<Object, User> userRedisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<Object, User> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<User> serializer = new Jackson2JsonRedisSerializer<User>(User.class);
template.setDefaultSerializer(serializer);
return template;
}
}
com.springboot.config.CacheConfig
package com.springboot;
import com.springboot.bean.Dept;
import com.springboot.bean.User;
import com.springboot.service.DeptService;
import com.springboot.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisTests {
@Autowired
private UserService userService;
@Autowired
private RedisTemplate userRedisTemplate;
@Test
public void test(){
User user = userService.getById(2);
userRedisTemplate.opsForValue().set("id_2", user);
Object user2 = userRedisTemplate.opsForValue().get("id_2");
System.out.println(user2);
/*
User{id=2, name='李四', gender=0, birthday=Tue Feb 03 00:00:00 CST 1998, address='武汉'}
*/
}
}

test
自定义CacheManager
上面说的是通过 RedisTemplate 操作保存 json 数据到 redis,如果要使用注解方式该怎么做呢?
一旦我们引入了 redis 缓存的场景启动器,那么默认生效的缓存配置类就变成了 org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration :
package org.springframework.boot.autoconfigure.cache;
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisTemplate.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {
private final CacheProperties cacheProperties;
private final CacheManagerCustomizers customizerInvoker;
RedisCacheConfiguration(CacheProperties cacheProperties,
CacheManagerCustomizers customizerInvoker) {
this.cacheProperties = cacheProperties;
this.customizerInvoker = customizerInvoker;
}
@Bean
public RedisCacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
cacheManager.setUsePrefix(true);
List<String> cacheNames = this.cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return this.customizerInvoker.customize(cacheManager);
}
}
org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
缓存管理器也更换为第 21 行的 RedisCacheManager,RedisCacheManager 帮我们创建 RedisCache 来作为缓存组件,而 RedisCache 组件就是通过操作 Redis 缓存数据的。
在第 22 行也可以看到,创建 RedisCacheManager 实例时通过构造方法传入的 RedisTemplate,所以我们只需要自己定义一个 RedisCacheManager,让其 RedisTemplate 是使用 json 序列化器即可:
package com.springboot.config;
import com.springboot.bean.Dept;
import com.springboot.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
@Configuration
public class CacheConfig {
@Bean
public RedisTemplate<Object, User> userRedisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<Object, User> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<User> serializer = new Jackson2JsonRedisSerializer<User>(User.class);
template.setDefaultSerializer(serializer);
return template;
}
@Bean
public RedisTemplate<Object, Dept> deptRedisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<Object, Dept> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Dept> serializer = new Jackson2JsonRedisSerializer<Dept>(Dept.class);
template.setDefaultSerializer(serializer);
return template;
}
@Primary // 默认
@Bean
public RedisCacheManager userCacheManager(RedisTemplate<Object, User> redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
// key 使用 cacheName 作为前缀
cacheManager.setUsePrefix(true);
return cacheManager;
}
@Bean
public RedisCacheManager deptCacheManager(RedisTemplate<Object, Dept> redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
// key 使用 cacheName 作为前缀
cacheManager.setUsePrefix(true);
return cacheManager;
}
}
com.springboot.config.CacheConfig
然后再使用注解时指定要使用的缓存管理器即可。
还可以通过手动编码方式获取到缓存管理器对指定缓存组件进行操作:
package com.springboot.service;
import com.springboot.bean.Dept;
import com.springboot.bean.User;
import com.springboot.mapper.DeptMappper;
import com.springboot.mapper.UserMappper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.annotation.*;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.stereotype.Service;
@Service
@CacheConfig(cacheManager = "deptCacheManager")
public class DeptService {
@Autowired
private DeptMappper deptMappper;
@Autowired
private RedisCacheManager deptCacheManager;
public Dept getById(Integer id) {
System.out.println("DeptService.getById(" + id + ") 执行了");
Dept dept = deptMappper.getById(id);
// 获取到缓存组件
Cache cache = deptCacheManager.getCache("dept");
cache.put(id, dept);
return dept;
}
}
com.springboot.service.DeptService
java框架之SpringBoot(11)-缓存抽象及整合Redis的更多相关文章
- java框架之SpringBoot(9)-数据访问及整合MyBatis
简介 对于数据访问层,无论是 SQL 还是 NOSQL,SpringBoot 默认采用整合 SpringData 的方式进行统一处理,添加了大量的自动配置,引入了各种 Template.Reposit ...
- java框架之springboot
快速入门 一.helloworld示例 二.springboot单元测试 三.springboot热部署 web开发 整合redis thymeleaf使用 spring-data-jpa使用 整合m ...
- 使用Spring提供的缓存抽象机制整合EHCache为项目提供二级缓存
Spring自身并没有实现缓存解决方案,但是对缓存管理功能提供了声明式的支持,能够与多种流行的缓存实现进行集成. Spring Cache是作用在方法上的(不能理解为只注解在方法上),其核心思想是 ...
- SpringBoot学习笔记(6) SpringBoot数据缓存Cache [Guava和Redis实现]
https://blog.csdn.net/a67474506/article/details/52608855 Spring定义了org.springframework.cache.CacheMan ...
- SpringBoot进阶教程(二十七)整合Redis之分布式锁
在之前的一篇文章(<Java分布式锁,搞懂分布式锁实现看这篇文章就对了>),已经介绍过几种java分布式锁,今天来个Redis分布式锁的demo.redis 现在已经成为系统缓存的必备组件 ...
- java框架之SpringBoot(3)-日志
市面上的日志框架 日志抽象层 日志实现 JCL(Jakarta Commons Logging).SLF4J(Simple Logging Facade For Java).JBoss-Logging ...
- java框架之SpringBoot(15)-安全及整合SpringSecurity
SpringSecurity介绍 Spring Security 是针对 Spring 项目的安全框架,也是 Spring Boot 底层安全模块默认的技术选型.它可以实现强大的 Web 安全控制.对 ...
- java框架之SpringBoot(16)-分布式及整合Dubbo
前言 分布式应用 在分布式系统中,国内常用 Zookeeper + Dubbo 组合,而 SpringBoot 推荐使用 Spring 提供的分布式一站式解决方案 Spring + SpringBoo ...
- java框架之SpringBoot(1)-入门
简介 Spring Boot 用来简化 Spring 应用开发,约定大于配置,去繁从简,just run 就能创建一个独立的.产品级别的应用. 背景: J2EE 笨重的开发.繁多的配置.低下的开发效率 ...
随机推荐
- [HDFS Manual] CH6 HDFS Federation
HDFS Federation HDFS Federation 1 Background 2.多个namenode/namespace 2.1 关键好处 3 联合配置 3.1 配置 3.2 格式化na ...
- 利用pentestbox打造ms17-010移动"杀器"
本文首发Freebuf,属原创奖励计划,未经许可禁止转载. 链接:http://www.freebuf.com/articles/system/132274.html 一. 前言 前段时间Shadow ...
- Zookeeper —— 初识
什么是 Zookeeper Zookeeper 是一个开放源代码的分布式协调服务,由雅虎创建,是 Google Chubby 的开源实现: Zookeeper 是典型的分布式数据一致性的解决方案,分布 ...
- Web API 异常处理
Web Api的ExceptionFilter只能截获并处理Action执行过程中发生的异常,在Action执行过程之外如果出现异常,ExceptionFilter是无能为力的. 这些异常包括: 1. ...
- IntelliJ IDEA(2018)安装和破解
IDEA 全称 IntelliJ IDEA,是Java语言开发的集成环境,IntelliJ在业界被公认为最好的java开发工具之一,尤其在智能代码助手.代码自动提示.重构.J2EE支持.各类版本工具( ...
- ubuntu cli
查看安装包的路径 dpkg -L redis-server 防火墙 ufw redis 相关 service redis-server restart
- php7新特性总结
PHP新功能总结 改进的性能 - 将PHPNG代码合并到PHP7中,速度是PHP 5的两倍. 降低内存消耗 - 优化的PHP 7使用较少的资源. 标量类型声明 - 现在可以强制执行参数和返回类型. 一 ...
- java interface接口的传值方法
A 类 package interface_test; public class A { private IPresenter ip; public A(IPresenter ip) { this.i ...
- python程序打包成.exe
安装pyinstaller 方法一:使用pip install pyinstaller 方法二:如果是下载github上的包之后手动安装 包下载 亲测可用:Pyinstaller下载地址,GitHub ...
- Delphi过程函数传递参数的几种方式
Delphi过程函数传递参数的几种方式 在Delphi过程.函数中传递参数几个修饰符为Const.Var.Out. 另一种不加修饰符的为默认按值传递参数. 一.默认方式以值方式传递参数 proced ...