《Spring 高手系列》(40)(Spring Cache)笔记

前言

参考链接1

https://docs.spring.io/spring-framework/docs/5.3.3/reference/html/integration.html#cache-annotations

https://docs.spring.io/spring-data/redis/docs/current/reference/html

涉及知识点

spring cache

@EnableCaching

在这里插入图片描述

源码

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {

	boolean proxyTargetClass() default false;
	
	AdviceMode mode() default AdviceMode.PROXY;
	
	int order() default Ordered.LOWEST_PRECEDENCE;
}

public enum AdviceMode {	
	PROXY,
	ASPECTJ
}

注解的主要作用是引入一个选择器 CachingConfigurationSelector ,选择器会根据注解的 mode 参数使用不同的方法,默认为 PROXY ,执行 getProxyImports 方法。

	@Override
	public String[] selectImports(AdviceMode adviceMode) {
		switch (adviceMode) {
			case PROXY:
				return getProxyImports();
			case ASPECTJ:
				return getAspectJImports();
			default:
				return null;
		}
	}
	
	private String[] getProxyImports() {
		List<String> result = new ArrayList<>(3);		
		result.add(AutoProxyRegistrar.class.getName());
		result.add(ProxyCachingConfiguration.class.getName());		
		if (jsr107Present && jcacheImplPresent) {
			result.add(PROXY_JCACHE_CONFIGURATION_CLASS);
		}
		return StringUtils.toStringArray(result);
	}

	private String[] getAspectJImports() {
		List<String> result = new ArrayList<>(2);
		result.add(CACHE_ASPECT_CONFIGURATION_CLASS_NAME);
		if (jsr107Present && jcacheImplPresent) {
			result.add(JCACHE_ASPECT_CONFIGURATION_CLASS_NAME);
		}
		return StringUtils.toStringArray(result);
	}

getProxyImports 方法导入的三个类名如图所示
在这里插入图片描述

@Cacheable

在这里插入图片描述

源码

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {
	//value 和 cacheNames 互为别名
	@AliasFor("cacheNames")
	String[] value() default {};

	@AliasFor("value")
	String[] cacheNames() default {};
	
	String key() default "";
	
	String keyGenerator() default "";
	
	String cacheManager() default "";
	
	String cacheResolver() default "";
	
	String condition() default "";
	
	String unless() default "";
	
	boolean sync() default false;
}

在这里插入图片描述

案例1 缓存list

@Component
public class ArticleService {

    @Cacheable(cacheNames = {"cache1"})
    public List<String> list() {
        System.out.println("获取文章列表!");
        return Arrays.asList("spring", "mysql", "java高并发", "maven");
    }
}

在这里插入图片描述

@EnableCaching
@ComponentScan
@Configuration
public class Client {

    @Bean
    public CacheManager cacheManager() {
        ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager("cache1");
        return cacheManager;
    }

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(Client.class);
        context.refresh();
        ArticleService articleService = context.getBean(ArticleService.class);
        System.out.println(articleService.list());
        System.out.println(articleService.list());
    }

}
获取文章列表!
[spring, mysql, java高并发, maven]
[spring, mysql, java高并发, maven]

在这里插入图片描述

key属性:自定义key

在这里插入图片描述

案例2 自定义key

    @Cacheable(cacheNames = {"cache1"}, key = "#root.target.class.name+'-'+#page+'-'+#pageSize")
    public String getPage(int page, int pageSize) {
        String msg = String.format("page-%s-pageSize-%s", page, pageSize);
        System.out.println("从db中获取数据:" + msg);
        return msg;
    }
@EnableCaching
@ComponentScan
@Configuration
public class Client2 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(Client2.class);
        context.refresh();
        ArticleService articleService = context.getBean(ArticleService.class);

        //page=1,pageSize=10调用2次
        System.out.println(articleService.getPage(1, 10));
        System.out.println(articleService.getPage(1, 10));

        //page=2,pageSize=10调用2次
        System.out.println(articleService.getPage(2, 10));
        System.out.println(articleService.getPage(2, 10));

        {
            System.out.println("下面打印出cache1缓存中的key列表");
            ConcurrentMapCacheManager cacheManager = context.getBean(ConcurrentMapCacheManager.class);
            ConcurrentMapCache cache1 = (ConcurrentMapCache) cacheManager.getCache("cache1");
            cache1.getNativeCache().keySet().stream().forEach(System.out::println);
        }
    }
}
从db中获取数据:page-1-pageSize-10
page-1-pageSize-10
page-1-pageSize-10
从db中获取数据:page-2-pageSize-10
page-2-pageSize-10
page-2-pageSize-10
下面打印出cache1缓存中的key列表
com.example.lurenjia.spring.c40.cacheable.d1.ArticleService-1-10
com.example.lurenjia.spring.c40.cacheable.d1.ArticleService-2-10

condition属性:控制缓存的使用条件

在这里插入图片描述

案例3 condition

    @Cacheable(cacheNames = "cache1", key = "'getById'+#id", condition = "#cache")
    public String getById(Long id, boolean cache) {
        System.out.println("获取数据!");
        return "spring缓存:" + UUID.randomUUID().toString();
    }
@EnableCaching
@ComponentScan
@Configuration
public class Client3 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(Client3.class);
        context.refresh();
        ArticleService articleService = context.getBean(ArticleService.class);

        System.out.println(articleService.getById(1L, true));
        System.out.println(articleService.getById(1L, true));
        System.out.println(articleService.getById(1L, false));
        System.out.println(articleService.getById(1L, true));
    }
}
获取数据!
spring缓存:0320318f-7cff-4efb-91ac-e7ed2da8eadb
spring缓存:0320318f-7cff-4efb-91ac-e7ed2da8eadb
获取数据!
spring缓存:04ffafe0-fc36-43a3-b400-2243dca2c545
spring缓存:0320318f-7cff-4efb-91ac-e7ed2da8eadb

在这里插入图片描述

unless属性:控制是否需要将结果丢到缓存中

在这里插入图片描述

案例4 unless 当返回结果为null的时候,不要将结果进行缓存

    java.util.Map<Long, String> articleMap = new HashMap<>();

    @Cacheable(cacheNames = "cache1", key = "'findById'+#id", unless = "#result==null")
    public String findById(Long id) {
        this.articleMap.put(1L, "spring系列");
        System.out.println("----获取文章:" + id);
        return articleMap.get(id);
    }
@EnableCaching
@ComponentScan
@Configuration
public class Client4 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(Client4.class);
        context.refresh();
        ArticleService articleService = context.getBean(ArticleService.class);

        System.out.println(articleService.findById(1L));
        System.out.println(articleService.findById(1L));
        System.out.println(articleService.findById(3L));
        System.out.println(articleService.findById(3L));

        {
            System.out.println("下面打印出缓cache1缓存中的key列表");
            ConcurrentMapCacheManager cacheManager = context.getBean(ConcurrentMapCacheManager.class);
            ConcurrentMapCache cache1 = (ConcurrentMapCache) cacheManager.getCache("cache1");
            cache1.getNativeCache().keySet().stream().forEach(System.out::println);
        }
    }
}
----获取文章:1
spring系列
spring系列
----获取文章:3
null
----获取文章:3
null
下面打印出缓cache1缓存中的key列表
findById1

condition和unless对比

在这里插入图片描述

@CachePut:将结果放入缓存

在这里插入图片描述

源码

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CachePut {

	@AliasFor("cacheNames")
	String[] value() default {};

	@AliasFor("value")
	String[] cacheNames() default {};
		
	String key() default "";
	
	String keyGenerator() default "";
	
	String cacheManager() default "";
	
	String cacheResolver() default "";
	
	String condition() default "";

	String unless() default "";
}

案例1

    @CachePut(cacheNames = "cache1", key = "'findById'+#id")
    public String add(Long id, String content) {
        System.out.println("新增文章:" + id);
        this.articleMap.put(id, content);
        return content;
    }
@EnableCaching
@ComponentScan
@Configuration
public class Client6 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(Client6.class);
        context.refresh();
        ArticleService articleService = context.getBean(ArticleService.class);

        //新增3个文章,由于add方法上面有@CachePut注解,所以新增之后会自动丢到缓存中
        articleService.add(1L, "java高并发系列");
        articleService.add(2L, "Maven高手系列");
        articleService.add(3L, "MySQL高手系列");

        //然后调用findById获取,看看是否会走缓存
        System.out.println("调用findById方法,会尝试从缓存中获取");
        System.out.println(articleService.findById(1L));
        System.out.println(articleService.findById(2L));
        System.out.println(articleService.findById(3L));

        {
            System.out.println("下面打印出cache1缓存中的key列表");
            ConcurrentMapCacheManager cacheManager = context.getBean(ConcurrentMapCacheManager.class);
            ConcurrentMapCache cache1 = (ConcurrentMapCache) cacheManager.getCache("cache1");
            cache1.getNativeCache().keySet().stream().forEach(System.out::println);
        }
    }
}

@CacheEvict:缓存清理

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CacheEvict {
	
	@AliasFor("cacheNames")
	String[] value() default {};
	
	@AliasFor("value")
	String[] cacheNames() default {};
	
	String key() default "";
	
	String keyGenerator() default "";
	
	String cacheManager() default "";
	
	String cacheResolver() default "";
	
	String condition() default "";

	boolean allEntries() default false;
	
	boolean beforeInvocation() default false;
}

修饰范围:类型、方法,修饰类子类继承,value和cacheNames互为别名。

案例1

    @CacheEvict(cacheNames = "cache1", key = "'findById'+#id") //@1
    public void delete(Long id) {
        System.out.println("删除文章:" + id);
        this.articleMap.remove(id);
    }
@EnableCaching
@ComponentScan
@Configuration
public class Client7 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(Client7.class);
        context.refresh();
        ArticleService articleService = context.getBean(ArticleService.class);

        //第1次调用findById,缓存中没有,则调用方法,将结果丢到缓存中
        System.out.println(articleService.findById(1L));
        //第2次调用findById,缓存中存在,直接从缓存中获取
        System.out.println(articleService.findById(1L));

        //执行删除操作,delete方法上面有@CacheEvict方法,会清除缓存
        articleService.delete(1L);

        //再次调用findById方法,发现缓存中没有了,则会调用目标方法
        System.out.println(articleService.findById(1L));
    }
}
----获取文章:1
spring系列
spring系列
删除文章:1
----获取文章:1
spring系列

@Caching:缓存注解组

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Caching {

	Cacheable[] cacheable() default {};

	CachePut[] put() default {};

	CacheEvict[] evict() default {};

}

修饰范围:类型、方法,修饰类子类继承。

Cacheable、CachePut、CacheEvict不支持重复使用,所以定义该注解存放。

@CacheConfig

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheConfig {

	String[] cacheNames() default {};
	
	String keyGenerator() default "";
	
	String cacheManager() default "";
	
	String cacheResolver() default "";
}

修饰范围:类型,用于统一设置一些属性。

Redis整合Spring Cache

yml添加注解

spring:
  cache:
    type: redis

测试方法

    @GetMapping("cache1")
    @Cacheable(cacheNames = "cache1")
    public List<String> list() {
        System.out.println("---模拟从db中获取数据---");
        return Arrays.asList("java高并发", "springboot", "springcloud");
    }

效果
在这里插入图片描述

不足

      time-to-live: 3600000

Redisson整合Spring Cache

参考链接2

        <!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.17.7</version>
        </dependency>
@Configuration
public class RedissonConfig {

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private String port;

    @Value("${spring.redis.password}")
    private String password;

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        if (org.springframework.util.ObjectUtils.isEmpty(password)) {
            config.useSingleServer().setAddress("redis://" + host + ":" + port);
        } else {
            config.useSingleServer().setAddress("redis://" + host + ":" + port).setPassword(password);
        }

        config.setCodec(new org.redisson.codec.JsonJacksonCodec());
        return Redisson.create(config);
    }

    @ConditionalOnBean(value = RedissonClient.class)
    @Bean
    public RedissonSpringCacheManager manager(@Autowired RedissonClient client) {
        RedissonSpringCacheManager manager = new RedissonSpringCacheManager(client);
        return new RedissonSpringCacheManager(client);
    }

}

    @GetMapping("cache1")
    @Cacheable(cacheNames = "cache1", key = "#root.targetClass.name+'-'+#root.method.name")
    public List<String> list() {
        System.out.println("---模拟从db中获取数据---");
        return Arrays.asList("java高并发", "springboot", "springcloud");
    }

编码问题 codec

        if (oldConf.getCodec() == null) {
            // use it by default
            oldConf.setCodec(new MarshallingCodec());
        }

默认情况下会使用 MarshallingCodec

在这里插入图片描述

在配置文件中修改为jackson

        config.setCodec(new org.redisson.codec.JsonJacksonCodec());

在这里插入图片描述

原理

CacheAutoConfiguration

源码如下,重点是导入了 CacheConfigurationImportSelector.class这一个类

@EnableConfigurationProperties(CacheProperties.class)
@AutoConfigureAfter({ CouchbaseDataAutoConfiguration.class, HazelcastAutoConfiguration.class,
		HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class })
@Import({ CacheConfigurationImportSelector.class, CacheManagerEntityManagerFactoryDependsOnPostProcessor.class })
public class CacheAutoConfiguration {}

CacheConfigurationImportSelector

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

	}

通过这个选择器来获取导入内容。 imports[i] = CacheConfigurations.getConfigurationClass(types[i]);

CacheConfigurations

final class CacheConfigurations {

	private static final Map<CacheType, Class<?>> MAPPINGS;

	static {
		Map<CacheType, Class<?>> mappings = new EnumMap<>(CacheType.class);
		mappings.put(CacheType.GENERIC, GenericCacheConfiguration.class);
		mappings.put(CacheType.EHCACHE, EhCacheCacheConfiguration.class);
		mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class);
		mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class);
		mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class);
		mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class);
		mappings.put(CacheType.REDIS, RedisCacheConfiguration.class);
		mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class);
		mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class);
		mappings.put(CacheType.NONE, NoOpCacheConfiguration.class);
		MAPPINGS = Collections.unmodifiableMap(mappings);
	}

	private CacheConfigurations() {
	}
	//如果在MAPPINGS中找到了,返回class的名字
	static String getConfigurationClass(CacheType cacheType) {
		Class<?> configurationClass = MAPPINGS.get(cacheType);
		Assert.state(configurationClass != null, () -> "Unknown cache type " + cacheType);
		return configurationClass.getName();
	}

	static CacheType getType(String configurationClassName) {
		for (Map.Entry<CacheType, Class<?>> entry : MAPPINGS.entrySet()) {
			if (entry.getValue().getName().equals(configurationClassName)) {
				return entry.getKey();
			}
		}
		throw new IllegalStateException("Unknown configuration class " + configurationClassName);
	}

}

CacheConfigurations会帮我们导入 mappings.put(CacheType.REDIS, RedisCacheConfiguration.class);

RedisCacheConfiguration

最重要的就是 RedisCacheConfiguration 中的这个bean,RedisCacheManager

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisConnectionFactory.class)
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {  

	@Bean
	RedisCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers,
			ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration,
			ObjectProvider<RedisCacheManagerBuilderCustomizer> redisCacheManagerBuilderCustomizers,
			RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) {}
}

这个类里面有一个比较重要的bean RedisCacheManager ,下面我们重点看一下这一个类。

RedisCacheManager

	@Bean
	RedisCacheManager cacheManager(CacheProperties cacheProperties, 
	CacheManagerCustomizers cacheManagerCustomizers,	
	ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration,
	ObjectProvider<RedisCacheManagerBuilderCustomizer> redisCacheManagerBuilderCustomizers,
	RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) {
	//
	RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(
				determineConfiguration(cacheProperties, redisCacheConfiguration, resourceLoader.getClassLoader()));
		List<String> cacheNames = cacheProperties.getCacheNames();
		if (!cacheNames.isEmpty()) {
			builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
		}
		if (cacheProperties.getRedis().isEnableStatistics()) {
			builder.enableStatistics();
		}
		redisCacheManagerBuilderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
		return cacheManagerCustomizers.customize(builder.build());
	}

在这个bean上定义的参数首先会去容器中查找。

	private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfiguration(
			CacheProperties cacheProperties,
			ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration,
			ClassLoader classLoader) {
		//如果没有默认创建一个新的redisCacheConfiguration
		return redisCacheConfiguration.getIfAvailable(() -> createConfiguration(cacheProperties, classLoader));
	}

createConfiguration这一个类就是创建配置的核心类

	private org.springframework.data.redis.cache.RedisCacheConfiguration createConfiguration(
			CacheProperties cacheProperties, ClassLoader classLoader) {
		Redis redisProperties = cacheProperties.getRedis();
		org.springframework.data.redis.cache.RedisCacheConfiguration config = org.springframework.data.redis.cache.RedisCacheConfiguration
				.defaultCacheConfig();
		config = config.serializeValuesWith(
				SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(classLoader)));
		if (redisProperties.getTimeToLive() != null) {
			config = config.entryTtl(redisProperties.getTimeToLive());
		}
		if (redisProperties.getKeyPrefix() != null) {
			config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());
		}
		if (!redisProperties.isCacheNullValues()) {
			config = config.disableCachingNullValues();
		}
		if (!redisProperties.isUseKeyPrefix()) {
			config = config.disableKeyPrefix();
		}
		return config;
	}

当没有 org.springframework.data.redis.cache.RedisCacheConfiguration 这个bean的时候会使用这个方法默认创建一个新的配置类。

当我们

ObjectProvider的用法

ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值