Spring 3.1 引入了激动人心的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如 EHCache 或者 OSCache),而是一个对缓存使用的抽象,通过在既有代码中添加少量它定义的各种 annotation,即能够达到缓存方法的返回对象的效果。
Spring 的缓存技术还具备相当的灵活性,不仅能够使用 SpEL(Spring Expression Language)来定义缓存的 key 和各种 condition,还提供开箱即用的缓存临时存储方案,也支持和主流的专业缓存例如 EHCache 集成。
其特点总结如下:
一句话解释Spring Cache: 通过注解的方式,利用AOP的思想来解放缓存的管理。
SpringCache
创建Spring 项目
pom.xml
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0modelVersion> <parent> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-parentartifactId> <version>2.3.1.RELEASEversion> <relativePath/> parent> <groupId>com.demo.cachegroupId> <artifactId>cacheartifactId> <version>0.0.1-SNAPSHOTversion> <name>cachename> <description>Demo project for Spring Bootdescription>
<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-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
project>
创建书籍模型
public class Book {
private String isbn;
private String title;
public Book(String isbn, String title) {
this.isbn = isbn;
this.title = title;
}
public String getIsbn() {
return isbn;
}
public void setIsbn(String isbn) {
this.isbn = isbn;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@Override
public String toString() {
return “Book{” + “isbn=’” + isbn + ‘’’ + “, title=’” + title + ‘’’ + ‘}’;
}
}
创建书库
public interface BookRepository {
Book getByIsbn(String isbn);
}
模拟延迟存储库
@Component
public class SimpleBookRepository implements BookRepository {
@Override
public Book getByIsbn(String isbn) {
simulateSlowService();
return new Book(isbn, “Some book”);
}
//simulateSlowService故意在每个getByIsbn中插入三秒钟的延迟。稍后,将通过缓存来加速该示例。
private void simulateSlowService() {
try {
long time = 3000L;
Thread.sleep(time);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
}
使用书库
CommandLineRunner注入BookRepository并使用不同参数多次调用
@Component
public class AppRunner implements CommandLineRunner {
private static final Logger logger = LoggerFactory.getLogger(AppRunner.class);
private final BookRepository bookRepository;
public AppRunner(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
@Override
public void run(String... args) throws Exception {
logger.info("… Fetching books");
logger.info(“isbn-1234 -->” + bookRepository.getByIsbn(“isbn-1234”));
logger.info(“isbn-4567 -->” + bookRepository.getByIsbn(“isbn-4567”));
logger.info(“isbn-1234 -->” + bookRepository.getByIsbn(“isbn-1234”));
logger.info(“isbn-4567 -->” + bookRepository.getByIsbn(“isbn-4567”));
logger.info(“isbn-1234 -->” + bookRepository.getByIsbn(“isbn-1234”));
logger.info(“isbn-1234 -->” + bookRepository.getByIsbn(“isbn-1234”));
}
}
此时尝试运行该应用程序,则即使您多次检索完全相同的书,也是非常的慢。以下示例输出显示了我们的(故意延迟)代码创建的三秒延迟:
接下来给获取书方法加上缓存@Cacheable
@Component
public class SimpleBookRepository implements BookRepository {
@Override
@Cacheable(“books”)
public Book getByIsbn(String isbn) {
simulateSlowService();
return new Book(isbn, “Some book”);
}
// simulateSlowService故意在每个getByIsbn中插入三秒钟的延迟。稍后,您将通过缓存来加速该示例。
private void simulateSlowService() {
try {
long time = 3000L;
Thread.sleep(time);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
}
第二步给启动类加上注解@EnableCaching开启缓存
@SpringBootApplication //@EnableCaching注释触发后处理器检查每个的Spring bean的公共方法缓存注释的存在。如果找到了这样的注释,则会自动创建一个代理来拦截方法调用并相应地处理缓存行为。 @EnableCaching public class CacheApplication {
public static void main(String[] args) { SpringApplication.run(CacheApplication.class, args); }
}
启动服务发现第一次检索书仍然需要三秒钟。但是,同一本书的第二次和后续时间要快得多,这表明缓存正在完成其工作。
恭喜你!您刚刚在Spring托管的bean上启用了缓存。
@Cacheable 的作用
@Cacheable 主要的参数
value 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 例如:
@Cacheable(value=”mycache”) 或者@Cacheable(value={”cache1”,”cache2”}
key 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 例如:@Cacheable(value=”testcache”,key=”#userName”)
condition 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存 例如:@Cacheable(value=”testcache”,condition=”#userName.length()>2”)
@CachePut 的作用
@CachePut 主要的参数
value 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 例如:
@Cacheable(value=”mycache”) 或者@Cacheable(value={”cache1”,”cache2”}
key 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 例如:@Cacheable(value=”testcache”,key=”#userName”)
condition 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存 例如:@Cacheable(value=”testcache”,condition=”#userName.length()>2”)
@CachEvict 的作用
@CacheEvict 主要的参数
value 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 例如:
@CachEvict(value=”mycache”) 或者@CachEvict(value={”cache1”,”cache2”}
key 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 例如:@CachEvict(value=”testcache”,key=”#userName”)
condition 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才清空缓存 例如:@CachEvict(value=”testcache”,condition=”#userName.length()>2”)
allEntries 是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存 例如:@CachEvict(value=”testcache”,allEntries=true)
beforeInvocation 是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存
@Caching
和 spring 的事务管理类似,spring cache 的关键原理就是 spring AOP,通过 spring AOP,其实现了在方法调用前、调用后获取方法的入参和返回值,进而实现了缓存的逻辑。我们来看一下下面这个图:
上图显示,当客户端“Calling code”调用一个普通类 Plain Object 的 foo() 方法的时候,是直接作用在 pojo 类自身对象上的,客户端拥有的是被调用者的直接的引用。
而 Spring cache 利用了 Spring AOP 的动态代理技术,即当客户端尝试调用 pojo 的 foo()方法的时候,给他的不是 pojo 自身的引用,而是一个动态生成的代理类
如上图所示,这个时候,实际客户端拥有的是一个代理的引用,那么在调用 foo() 方法的时候,会首先调用 proxy 的 foo() 方法,这个时候 proxy 可以整体控制实际的 pojo.foo() 方法的入参和返回值,比如缓存结果,比如直接略过执行实际的 foo() 方法等,都是可以轻松做到的。
1)、引入依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-cacheartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId> spring-boot-starter-data-redisartifactId>
dependency>
2)、写配置
CacheAuroConfiguration会导入RedisCacheConfiguration;自动配好了缓存管理器RedisCacheManager
spring.cache.type=redis
3)、启动类加注解打开缓存
@EnableCaching
4)、方法加注解 @Cacheable(“categorys”)
/** * 查询所有一级分类 * * @return */
@Override @Cacheable("categorys") public List<CategoryEntity> getLevel1Categorys() { System.out.println("查询所有一级分类"); List<CategoryEntity> categoryEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0)); return categoryEntities; }
默认行为
5)、生成的缓存
自定义
@Cacheable(value = “categorys”, key = “#root.method.name”)
spring.cache.redis.time-to-live=3600000
需要修改缓存管理器,spring cache缓存管理器原理:
CacheAutoConfiguration缓存配置类会导入RedisCacheConfiguration;
RedisCacheConfiguration自动配置了RedisdCacheManager;
RedisCacheConfiguration初始化所有的缓存;
每个缓存决定使用什么配置;
如果redisCacheConfiguration有就用已有的,没有就用默认配置;
想改缓存的配置,只需要给容器中放一个RedisCacheConfiguration即可,就会应用到当前RedisCacheManager管理的所有缓存中;
编写MyCacheConfig 注入容器
@EnableConfigurationProperties(CacheProperties.class)
@EnableCaching
@Configuration
public class MyCacheConfig {
// @Autowired
// CacheProperties properties;
//1、原来和配置文件绑定的配置类是这样子的
//@ConfigurationProperties(prefix = "spring.cache")
//public class CachePropertiesT
//2、要让他生效
//@EnableConfigurationProperties(CacheProperties.class)
@Bean
RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericFastJsonRedisSerializer()));
//将配置文件中的配置生效
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixKeysWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
}
application.properties
spring.cache.type=redis
spring.cache.redis.time-to-live=3600000
#如果指定了前缀就用我们指定的前缀,如果没有就默认使用缓存的名字作为前缀
spring.cache.redis.key-prefix=CACHE_
#是否开启key前缀
spring.cache.redis.use-key-prefix=true
#是否缓存空值。防止缓存穿透
spring.cache.redis.cache-null-values=true
ok
Spirng-Cache的不足
1)读模式:
2)写模式:(缓存与数据库一致)
常规数据(读多写少,即时性,一致性要求不高的数据,完全可以使用spring-cache)
特殊数据:特殊设计