Spring Boot 20天入門(day8)

一羣熱愛技術並且嚮往優秀的程序猿同學,不喜歡水文,不喜歡販賣焦慮,只喜歡談技術,分享的都是技術乾貨。Talk is cheap. Show me the code
在這裏插入圖片描述

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;
	}

}

我們可以看到整個自動配置類添加了兩個組件RedisTemplateStringRedisTemplate,一個是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中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章