在我们平常的工作当中,有好多场景需要用到缓存技术,如redis,ehcache等来加速数据的访问。作为浅谈缓存的第一篇笔者不想谈论具体的缓存技术,我更想介绍一下Spring中每次阅读都会使我心中泛起波澜的一个东西,那就是基于注解的缓存技术。我们先看Spring参考文档中的一句话。
Since version 3.1, Spring Framework provides support for transparently adding caching into an existing Spring application. Similar to the
transaction
support, the caching abstraction allows consistent use of various caching solutions with minimal impact on the code.
简单翻译一下,基于注解的缓存技术是Spring3.1版本引入的,其目的是为了给一个已有的Spring应用最小侵入的引入缓存。其原理与Spring的事务类似都是基于Spring的AOP技术。他本质上不是一个具体的缓存实现方案(例如redis EHCache等),而是一个对缓存使用的抽象,通过在既有代码中添加少量的annotation,即能达到缓存方法返回对象的效果。她支持开箱即用的缓存临时存储方案,也支持集成主流的专业缓存,如:redis,EHCache。好,废话少说,下面笔者通过几个简单例子来总结看看Spring Cache的优点,并通过源码对其原理做一个简单介绍。
注意:笔者的代码都是基于Spring boot 1.5.9.release版本,代码的风格也都是Spring boot式的。
先建一个基于Spring Boot 的web项目,本文的编程用例都会在此项目中完成。项目的pom.xml文件如下。
<?
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>
<groupId>com.snow.cache
</groupId>
<artifactId>snow-cache
</artifactId>
<version>0.0.1-SNAPSHOT
</version>
<packaging>jar
</packaging>
<name>snow-cache
</name>
<description>Demo for Spring Cache
</description>
<parent>
<groupId>org.springframework.boot
</groupId>
<artifactId>spring-boot-starter-parent
</artifactId>
<version>1.5.9.RELEASE
</version>
<relativePath/>
<!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8
</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8
</project.reporting.outputEncoding>
<java.version>1.8
</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot
</groupId>
<artifactId>spring-boot-starter-web
</artifactId>
</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>
partI.传统缓存的用法
在聊Spring Cache之前,我们先来看看我们以前是怎么使用缓存的。这里笔者不打算引入任何第三方缓存而是自己实现一个简易的缓存。
案例:电商的场景中,商品信息需要经常被用户浏览,不做缓存数据库肯定吃不消。我们就来对商品信息的查询做缓存,以商品信息的编号为key,商品信息对象为value,当以相同的编号查询商品时,若缓存中有,直接从缓存中返回结果,否则从数据库中查询,并将查询结果放入缓存。
先定义一个产品实体类代码如下:
package com.snow.cache
;
import java.io.Serializable
;
/**
*
*
@author
snowwolf-louis
*
@date
18/4/10
*/
public class Product
implements Serializable{
private Long
id
;
private String
name
;
private String
desc
;
public Long
getId() {
return
id
;
}
public void
setId(Long id) {
this.
id = id
;
}
public String
getName() {
return
name
;
}
public void
setName(String name) {
this.
name = name
;
}
public String
getDesc() {
return
desc
;
}
public void
setDesc(String desc) {
this.
desc = desc
;
}
@Override
public String
toString() {
return
"Product{" +
"id=" +
id +
", name='" +
name +
'
\'
' +
", desc='" +
desc +
'
\'
' +
'}'
;
}
}
在定义一个缓存管理器,负责缓存逻辑,支持对象的增加,修改和删除。代码如下:
package com.snow.cache.tradition
;
import java.util.Map
;
import java.util.concurrent.ConcurrentHashMap
;
/**
*
*
@author
snowwolf-louis
*
@date
18/4/10
*/
public class SimpleCacheManager<
T> {
/**
* 用ConcurrentHashMap做为缓存
*/
private Map<String
,
T>
cache =
new ConcurrentHashMap<String
,
T>()
;
/**
* 从缓存中获取给定key的值
*
@param
key
*
@return
返回缓存中指定key的值,若无则返回null
*/
public
T
get(String key){
return
cache.get(key)
;
}
/**
* 将数据(key,value)放入缓存
*
@param
key
*
@param
value
*/
public void
put(String key
,
T value){
cache.put(key
,value)
;
}
/**
* 清除缓存中指定key的的数据
*
@param
key
*/
public void
evict(String key){
if (
cache.containsKey(key)){
cache.remove(key)
;
}
}
/**
* 清空缓存
*/
public void
clearAll(){
cache.clear()
;
}
}
将我们自定义的缓存管理器交给Spring容器管理
package com.snow.cache.tradition.config
;
import com.snow.cache.Product
;
import com.snow.cache.tradition.SimpleCacheManager
;
import
org.springframework.context.annotation.Bean
;
import
org.springframework.context.annotation.Configuration
;
/**
* 传统缓存用法的配置类
*
@author
snowwolf-louis
*
@date
18/4/10
*/
@Configuration
public class SimpleCacheConfig {
@Bean
public SimpleCacheManager<Product>
cacheManager(){
return new SimpleCacheManager<Product>()
;
}
}
对商品的业务操作接口
package com.snow.cache.tradition.service
;
import com.snow.cache.Product
;
/**
* 对商品的业务操作接口
*
@author
snowwolf-louis
*
@date
18/4/10
*/
public interface ProductService {
/**
* 根据id查询商品信息
*
@param
id
*
@return
*/
public Product
getProductById(Long id)
;
}
对商品的业务操作实现
package com.snow.cache.tradition.service
;
import com.snow.cache.Product
;
import com.snow.cache.tradition.SimpleCacheManager
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.stereotype.Service
;
import java.util.Objects
;
/**
*
@author
snowwolf-louis
*
@date
18/4/10
*/
@Service
public class ProductServiceImpl
implements ProductService {
@Autowired
private SimpleCacheManager<Product>
cacheManager
;
@Override
public Product
getProductById(Long id) {
if (Objects.
isNull(id)){
throw new NullPointerException(
"id不能为空")
;
}
Product product =
null;
//首先从缓存查询
product =
cacheManager.get(id.toString())
;
if (product !=
null) {
return product
;
}
//缓存中没有在从数据库中查找,我这里就不从数据库中查了,而是调用一个私有方法模拟从数据库中查
product = getproductFromDB(id)
;
//将数据库中查到数据装载进缓存
if (product !=
null && product.getId() !=
null){
cacheManager.put(product.getId().toString()
,product)
;
}
return product
;
}