一羣熱愛技術並且嚮往優秀的程序猿同學,不喜歡水文,不喜歡販賣焦慮,只喜歡談技術,分享的都是技術乾貨。Talk is cheap. Show me the code
Spring Boot 20天入門(day8)
Springboot 緩存
緩存使用
首先我們需要引入相關依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
然後在Springboot的配置類上(一般是啓動類)標註@EnableCaching註解開啓緩存
@EnableCaching
@MapperScan(value = "com.github.springbootcache.mapper",basePackageClasses = Repository.class)
@SpringBootApplication
public class SpringBootCacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootCacheApplication.class, args);
}
}
最後在需要使用緩存的方法上添加下列註解:
@Cacheable //將方法的運行結果進行緩存;第二次再要相同的數據,直接從緩存中獲取,不再調用方法;
@CacheEvict //移除緩存
@CachePut //修改了數據庫的某個數據,同時更新緩存
1、Cacheable:
value/cacheNames :表明緩存存放在哪個命名空間下
key : 緩存數據時的key,默認使用方法參數的值,編寫 SPEL表達式 : #id,參數id的值 #a0 #p0 #rrot.args[0]
keyGenerator : key生成器,可以自己指定的組件id
key/keyGenerator 二選一
cacheManager : 指定緩存管理器
condition : 指定符合條件的情況下緩存,condition = "#id>0"
unless : 否定緩存,當unless爲true,方法的返回值不會緩存,可以獲取到結果進行判斷,unless = "#result==null"
sync : 是否使用異步模式
2、@CacheEvit:
@CacheEvit:緩存清除
@CacheEvit和@Cacheable的相同屬性就不再贅述。
1、allEntries = true 每次刪除,將指定緩存中的所有數據全都刪除
2、beforeInvocation=false ,緩存的清除是否是在方法之前執行,默認false, 即在方法之後清除,當方法執
行出現異常時,緩存不會清除。
beforeInvocation=true ,方法之前清除,無論方法執行是否出現異常,緩存都會清除
3、@CachePut:
先調用目標方法,然後講方法的返回值存進緩存中,屬性和@Cacheable一致
4、@CacheConfig:
標註在類上,指定全局的屬性,簡化代碼
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheConfig {
String[] cacheNames() default {};
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
}
5、@Caching:
將@Cacheable、@CachePut、@CacheEvit組合使用,可以寫出複雜的註解邏輯
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Caching {
Cacheable[] cacheable() default {};
CachePut[] put() default {};
CacheEvict[] evict() default {};
}
緩存實戰
實體類:
import java.io.Serializable;
/**
* @Description : TODO
* @Author : Weleness
* @Date : 2020/05/22
*/
public class User implements Serializable {
private static final long serialVersionUID = 3564291823518067604L;
private Integer id;
private String username;
private String password;
public User(Integer id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
public User() {
}
public static long getSerialVersionUID() {
return serialVersionUID;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
User服務:
@Service
public class UserSevice {
@Autowired
private UserMapper userMapper;
@Cacheable(cacheNames = "user")
public User getUser(Integer id){
System.out.println("id");
return userMapper.getUserById(id);
}
}
controller:
**
* @Description : TODO
* @Author : Weleness
* @Date : 2020/05/22
*/
@RestController
public class UserController {
@Autowired
private UserSevice userSevice;
@GetMapping("/user/{id}")
public User getUser(@PathVariable("id")Integer id){
return userSevice.getUser(id);
}
}
啓動項目,訪問接口:
可以看到,第一次訪問的時候,執行了sql語句進行查詢了。
清空控制檯,再次訪問:
可以看到控制檯沒有打印任何日誌信息,但是網頁獲取到了數據,緩存開啓成功。
緩存原理
瞭解過Springboot自動配置原理的同學都清楚,Springboot有各式各樣的的xxxAutoConfiguration來幫我們自動配置和引入一些必要的組件。
CacheConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(CacheManager.class)
@ConditionalOnBean(CacheAspectSupport.class)
@ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver")//當我們沒有自己配置緩存管理器時
@EnableConfigurationProperties(CacheProperties.class)//從配置文件中獲取配置
@AutoConfigureAfter({ CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class,
HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class })
@Import({ CacheConfigurationImportSelector.class, CacheManagerEntityManagerFactoryDependsOnPostProcessor.class })//導入組件
public class CacheAutoConfiguration {
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;
}
}
}
這個類我們就需要關注一個方法:CacheConfigurationImportSelector
,這個方法會獲取緩存管理器類型,Springboot2.x一共是10個:
然後會爲我們逐一獲取這些自動配置類的全類名
0 = "org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration"
1 = "org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration"
2 = "org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration"
3 = "org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration"
4 = "org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration"
5 = "org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration"
6 = "org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration"
7 = "org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration"
8 = "org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration"【默認】
9 = "org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration"
默認是使用SimpleCacheConfiguration
SimpleCacheConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class SimpleCacheConfiguration {
@Bean
ConcurrentMapCacheManager cacheManager(CacheProperties cacheProperties,
CacheManagerCustomizers cacheManagerCustomizers) {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
List<String> cacheNames = cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return cacheManagerCustomizers.customize(cacheManager);
}
}
該類會返回一個ConcurrentMapCacheManager
對象,來作爲緩存的cacheManager
這個ConcurrentMapCacheManager
對象,會初始化一個ConcurrentMap
以鍵值對存儲緩存組件。key就是自己指定的CacheName,值就是緩存對象。
public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {
private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
private boolean dynamic = true;
private boolean allowNullValues = true;
private boolean storeByValue = false;
@Nullable
private SerializationDelegate serialization;
}
@Cacheable運行原理
在方法執行前,會先去cacheManager中查詢Cache(緩存組件),根據cacheNames來獲取,若不存在相應的緩存對象,返回一個新的Cache對象。
@Override
@Nullable
public Cache getCache(String name) {
Cache cache = this.cacheMap.get(name);
if (cache == null && this.dynamic) {
synchronized (this.cacheMap) {
cache = this.cacheMap.get(name);
if (cache == null) {
cache = createConcurrentMapCache(name);
this.cacheMap.put(name, cache);
}
}
}
return cache;
}
去Cache中查找緩存的內容,使用一個Key, 默認使用的key是方法的參數。
key是按照某種策略生成的: 默認使用SimpleKeyGenerator生成key.
public static Object generateKey(Object... params) {
if (params.length == 0) {
return SimpleKey.EMPTY;
} else {
if (params.length == 1) {
Object param = params[0];
if (param != null && !param.getClass().isArray()) {
return param;
}
}
return new SimpleKey(params);
}
}
沒有就會執行方法
//通過key查詢緩存中有無數據,沒有的話再調用目標方法
protected Object lookup(Object key) {
return this.store.get(key);
}
然後將方法的返回值存入緩存中
總結:
@Cacheable標註的方法執行前會檢查選擇的cacheManager中的緩存有沒有對應的數據(默認方法的參數作爲key,如果有多個參數就都作爲key),沒有就執行目標方法,然後再將方法的返回值存入緩存。
Springboot整合redis
引入redis的starter:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
在配置文件中配置:
redis:
host: 127.0.0.1
password:
port: 6379
上面我們提到了Springboot在使用緩存的時候會逐一導入自動配置類,這些自動配置類的導入是有順序的,現在我們導入了redis的依賴,意味着RedisCacheConfiguration
配置類生效,就不會去執行默認的SimpleCacheConfiguration
。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
我們可以看到整個自動配置類添加了兩個組件RedisTemplate
和StringRedisTemplate
,一個是Redis用來操作對象的,一個是專門用來操作字符串的,也就是隻能存取字符串類型的值。
整合測試
存字符串
因爲這兩個組件在自動配置的時候就加入到ioc容器中了,所以我們直接就可以自動注入然後使用了
@SpringBootTest
class SpringBootCacheApplicationTests {
@Autowired
StringRedisTemplate stringRedisTemplate; // 操作字符串的
@Autowired
RedisTemplate<Object, User> redisTemplate; //k,v操作對象的
}
往redis中存入一個數據:
stringRedisTemplate.opsForValue().append("cache","hello");
查看redis客戶端,根據key:cache獲取值:
存對象
我們以1爲key,將一個user對象存入緩存中
redisTemplate.opsForValue().set(1,new User(1,"qq","87487"));
redis是會先將對象進行序列化之後,再將對象存入緩存中,但是這樣在redis就會以這種序列化之後的字符串進行顯示,不雅觀。
自定義RedisTemplate
redis默認的序列化規則是jdk的默認序列化規則
public void afterPropertiesSet() {
super.afterPropertiesSet();
boolean defaultUsed = false;
if (this.defaultSerializer == null) {
this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());
}
if (this.enableDefaultSerializer) {
if (this.keySerializer == null) {
this.keySerializer = this.defaultSerializer;
defaultUsed = true;
}
if (this.valueSerializer == null) {
this.valueSerializer = this.defaultSerializer;
defaultUsed = true;
}
if (this.hashKeySerializer == null) {
this.hashKeySerializer = this.defaultSerializer;
defaultUsed = true;
}
if (this.hashValueSerializer == null) {
this.hashValueSerializer = this.defaultSerializer;
defaultUsed = true;
}
}
我們要想讓他變好看一點,可以將他的序列化規則轉成json形式,對此我們可以對RedisTemplate進行改造:
創建一個配置類,添加Jackson2JsonRedisSerializer
這個定製器,這個定製器是spring-boot-starter-data-redis中自帶的,所以我們不需要重新引入其他的依賴
@Configuration
public class MyRedisConfig {
@Bean
public RedisTemplate<Object, User> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, User> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<User> serializer = new Jackson2JsonRedisSerializer<User>(User.class);
template.setDefaultSerializer(serializer);
return template;
}
}
測試:
redisTemplate.opsForValue().set(1,new User(1,"qq","87487"));
效果如下:
關於緩存的改變
緩存的自動配置類上有一個註冊,@ConditionalOnMissingBean(value = CacheManager.class, name = “cacheResolver”),只有當容器中沒有其他的cacheManager,自動配置類纔會生效。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(CacheManager.class)
@ConditionalOnBean(CacheAspectSupport.class)
@ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver")
@EnableConfigurationProperties(CacheProperties.class)
@AutoConfigureAfter({ CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class,
HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class })
@Import({ CacheConfigurationImportSelector.class, CacheManagerEntityManagerFactoryDependsOnPostProcessor.class })
public class CacheAutoConfiguration {
當我們引入了redis的starter,會自動引入一個cacheManager:
public class RedisCacheManager extends AbstractTransactionSupportingCacheManager {
private final RedisCacheWriter cacheWriter;
private final RedisCacheConfiguration defaultCacheConfig;
private final Map<String, RedisCacheConfiguration> initialCacheConfiguration;
private final boolean allowInFlightCacheCreation;
那麼這個cacheManager就會生效,緩存原來是存在concurrentHashMapManager中的,現在會存入redis中。