首页 > 其他分享 >SpringBoot配置多CacheManager

SpringBoot配置多CacheManager

时间:2024-06-20 17:31:56浏览次数:27  
标签:11 缓存 SpringBoot user0 配置 _. CacheManager public

SpringCache配置多CacheManager
背景
​ Spring为了减少数据的执行次数(重点在数据库查询方面), 在其内部使用aspectJ技术,为执行操作的结果集做了一层缓存的抽象。这极大的提升了应用程序的性能。由于其切面注入的特性,所以不会对我们的程序造成任何的影响。对于一些实时性要求不那么高的业务数据,我们可以在Service上进行一些缓存的操作。这样就可以减少访问数据库的频率。

默认缓存的使用顺序
​ 在Spring内部,缓存的实现,依赖org.springframework.cache.Cache与org.springframework.cache.CacheManager共同协作,它们只是定义了一种规范接口,实际的存储规则,需要用户自己定义,当没有提供用户自定义Bean对象,SpringBoot会自动执行以下的检测顺序:

Generic

JCache (JSR-107) (EhCache 3, Hazelcast, Infinispan, and others)

EhCache 2.x

Hazelcast

Infinispan

Couchbase

Redis

Caffeine

Simple

具体的用法请参考:Spring Caching

Cache

cache接口的作用是Spring提供的一种抽象操作规范,里面包含的crud操作

CacheManager

cacheManager接口的作用是用来获取Cache,类似一种对象工厂,所有的Cache,必须依赖与CacheManager来获取。

在Spring内部提供了三个默认的实现:

SimpleCacheManager 简单的集合实现
ConcurrentMapCacheManager 内部使用ConcurrentHashMap作为缓存容器
NoOpCacheManager 一个空的实现,不会保存任何记录。
当然其他的依赖需要我们引入三方的Jar,以及一些自定义的配置。

下面看下,执行切面的具体实现:

public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {

@Override
@Nullable
public Object invoke(final MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();

CacheOperationInvoker aopAllianceInvoker = () -> {
try {
return invocation.proceed();
}
catch (Throwable ex) {
throw new CacheOperationInvoker.ThrowableWrapper(ex);
}
};

try {
return execute(aopAllianceInvoker, invocation.getThis(), method, invocation.getArguments());
}
catch (CacheOperationInvoker.ThrowableWrapper th) {
throw th.getOriginal();
}
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
使用AOP的方式,将拦截添加缓存注解的方法,然后会调用父类的方法去执行:

execute方法中,会去寻找缓存的操作源CacheOperationSource,操作源中包含一个CacheOperation的集合。CacheOperation代表着@Cachable上注解信息的的实体类。然后我们可以用过cacheManager属性去寻找对应的cacheManager实现,获取Cache来完成缓存操作。

实现多CacheManager
​ 前面分析了,缓存的大致实现过程,我们就可以使用cacheManager去有选择性的选择我们需要使用的缓存实现。

​ 但是这里有个注意的点,我们需要给CacheManager一个默认的实现,这是由于Spring容器初始化机制造成的。CacheAspectSupport抽象实现了SmartInitializingSingleton接口,这个接口在Spring容器中,是单例对象的回调接口,当所有的单例对象实例化完毕,就会调用此方法。

​ 我们观察下CacheAspectSupport的逻辑:

public void afterSingletonsInstantiated() {
if (getCacheResolver() == null) {
// Lazily initialize cache resolver via default cache manager...
Assert.state(this.beanFactory != null, "CacheResolver or BeanFactory must be set on cache aspect");
try {
setCacheManager(this.beanFactory.getBean(CacheManager.class));
}
catch (NoUniqueBeanDefinitionException ex) {
throw new IllegalStateException("No CacheResolver specified, and no unique bean of type " +
"CacheManager found. Mark one as primary or declare a specific CacheManager to use.");
}
catch (NoSuchBeanDefinitionException ex) {
throw new IllegalStateException("No CacheResolver specified, and no bean of type CacheManager found. " +
"Register a CacheManager bean or remove the @EnableCaching annotation from your configuration.");
}
}
this.initialized = true;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
​ setCacheManager(this.beanFactory.getBean(CacheManager.class));,默认是从容器中拿到CacheManager对象,这就会出现一个问题,当配置多个CacheManager实例bean,并暴露给容器后,会出现Bean装载的错误,因为getBean,默认只会返回一个对象,当出现了两个Bean实例就会报错,找不到Bean对象。

​ 所以,当对于自定义的多个Bean,我们需要指定一个打上@Primary注解,这样就可以解决冲突。

具体实现
​ 接下来,贴出具体示例:

我们选择ehcache与redis作为多种缓存源。

以下示例全部基于2.1.4.RELEASE版本,不同版本的代码差异较大

pom文件

<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>

<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>27.1-jre</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
我选择使用内置的H2+JPA作为数据持久层。因为这样比较方便。。不用连接MYSQL。

application.yml配置

spring:
application:
name: authority-management
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:~/test
password:
username: sa
h2:
console:
enabled: true
jpa:
generate-ddl: true
hibernate:
ddl-auto: create-drop
show-sql: true
redis:
host: XXXX
port: 6379
lettuce:
pool:
max-active: 8
max-wait: 200
max-idle: 8
min-idle: 2
timeout: 2000
cache:
ehcache:
config: classpath:ehcache.xml
type: EHCACHE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
编写配置类

@Configuration
@EnableCaching
@EnableConfigurationProperties(CacheProperties.class)
public class CacheManagerConfiguration {
private final CacheProperties cacheProperties;
public CacheManagerConfiguration(CacheProperties cacheProperties) {
this.cacheProperties = cacheProperties;
}
public interface CacheManagerNames {
String REDIS_CACHE_MANAGER = "redisCacheManager";
String EHCACHE_CACHE_MANAGER = "ehCacheManager";
}

@Bean(name = CacheManagerNames.REDIS_CACHE_MANAGER)
public RedisCacheManager redisCacheManager(RedisConnectionFactory factory) {
Map<String, RedisCacheConfiguration> expires = ImmutableMap.<String, RedisCacheConfiguration>builder()
.put("15", RedisCacheConfiguration.defaultCacheConfig().entryTtl(
Duration.ofMillis(15)
))
.put("30", RedisCacheConfiguration.defaultCacheConfig().entryTtl(
Duration.ofMillis(30)
))
.put("60", RedisCacheConfiguration.defaultCacheConfig().entryTtl(
Duration.ofMillis(60)
))
.put("120", RedisCacheConfiguration.defaultCacheConfig().entryTtl(
Duration.ofMillis(120)
))
.build();

RedisCacheManager redisCacheManager = RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(factory)
.withInitialCacheConfigurations(expires)
.build();
return redisCacheManager;
}

@Bean(name = CacheManagerNames.EHCACHE_CACHE_MANAGER)
@Primary
public EhCacheCacheManager ehCacheManager() {
Resource resource = this.cacheProperties.getEhcache().getConfig();
resource = this.cacheProperties.resolveConfigLocation(resource);
EhCacheCacheManager ehCacheManager = new EhCacheCacheManager(
EhCacheManagerUtils.buildCacheManager(resource)
);
ehCacheManager.afterPropertiesSet();
return ehCacheManager;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
以上就是具体配置信息,需要主要的是别忘记@Primary

Service实现

@Override
@Cacheable(key = "#userId", cacheNames = CacheManagerConfiguration.CacheNames.CACHE_15MINS,
cacheManager = CacheManagerConfiguration.CacheManagerNames.EHCACHE_CACHE_MANAGER)
public User findUserAccordingToId(Long userId) {
return userRepository.findById(userId).orElse(User.builder().build());
}

@Override
@Cacheable(key = "#username", cacheNames = CacheManagerConfiguration.CacheNames.CACHE_15MINS,
cacheManager = CacheManagerConfiguration.CacheManagerNames.REDIS_CACHE_MANAGER)
public User findUserAccordingToUserName(String username) {
return userRepository.findUserByUsername(username);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
现在,就可以使用cacheManager属性来选择缓存源,用户可以灵活配置。

除了在方法上,使用注解外,我们还可以直接指定到类上,下面的示例,表示该类的全部方法都使用Encache作为缓存源。

@CacheConfig(cacheManager = CacheManagerConfiguration.CacheManagerNames.EHCACHE_CACHE_MANAGER)
1
测试类+日志信息

userService.findUserAccordingToId(save.getId());
userService.findUserAccordingToId(save.getId());
userService.findUserAccordingToUserName(save.getUsername());
userService.findUserAccordingToUserName(save.getUsername());
1
2
3
4
Hibernate: select user0_.id as id1_11_0_, user0_.email as email2_11_0_, user0_.image_url as image_ur3_11_0_, user0_.introduction as introduc4_11_0_, user0_.last_login_at as last_log5_11_0_, user0_.level as level6_11_0_, user0_.login_ip as login_ip7_11_0_, user0_.nickname as nickname8_11_0_, user0_.password as password9_11_0_, user0_.retry_login_count as retry_l10_11_0_, user0_.sex as sex11_11_0_, user0_.status as status12_11_0_, user0_.telephone as telepho13_11_0_, user0_.username as usernam14_11_0_ from user user0_ where user0_.id=?
2019-05-12 20:43:06.486 INFO 12020 --- [ main] io.lettuce.core.EpollProvider : Starting without optional epoll library
2019-05-12 20:43:06.488 INFO 12020 --- [ main] io.lettuce.core.KqueueProvider : Starting without optional kqueue library
2019-05-12 20:43:06.807 INFO 12020 --- [ main] o.h.h.i.QueryTranslatorFactoryInitiator : HHH000397: Using ASTQueryTranslatorFactory
Hibernate: select user0_.id as id1_11_, user0_.email as email2_11_, user0_.image_url as image_ur3_11_, user0_.introduction as introduc4_11_, user0_.last_login_at as last_log5_11_, user0_.level as level6_11_, user0_.login_ip as login_ip7_11_, user0_.nickname as nickname8_11_, user0_.password as password9_11_, user0_.retry_login_count as retry_l10_11_, user0_.sex as sex11_11_, user0_.status as status12_11_, user0_.telephone as telepho13_11_, user0_.username as usernam14_11_ from user user0_ where user0_.username=?

Process finished with exit code -1
1
2
3
4
5
6
7
​ 可以看到,两次查询,都只是用了一次select语句,还出现了一次redis连接操作,证明上述的配置是生效的。

注意:
我在此使用的lettuce作为redis客户端,使用连接池时,它依赖commons-pool2
测试时,当redis作为缓存时,发现有时候还是会去数据库查询两次,怀疑是配置的超时时间问题。
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/qq_26440803/article/details/90145543

标签:11,缓存,SpringBoot,user0,配置,_.,CacheManager,public
From: https://www.cnblogs.com/fswhq/p/18138184

相关文章

  • MoneyPrinterPlus:AI自动短视频生成工具-阿里云配置详解
    MoneyPrinterPlus是一个很好的自动短视频生成工具,虽然是一个非常好的工具,但是有些小伙伴可能不太清楚具体应该如何配置才能让它跑起来。因为MoneyPrinterPlus依赖一些具体的配置信息,所以还是很有必要给大家讲解清楚如何进行配置。项目已开源,代码地址:https://github.com/ddean200......
  • java基于SpringBoot+Vue的失踪人员信息发布与管理系统(源码+lw+部署文档+讲解等)
    文章目录前言详细视频演示项目运行截图技术框架后端采用SpringBoot框架前端框架Vue可行性分析系统测试系统测试的目的系统功能测试数据库表设计代码参考数据库脚本为什么选择我?获取源码前言......
  • k8s探针类型及探针配置
    探针类型:存活探针(LivenessProbe):用于判断容器是否存活(running状态),如果LivenessProbe探针探测到容器不健康,则kubelet杀掉该容器,并根据容器的重启策略做相应的处理。如果一个容器不包含LivenessProbe探针,则kubelet认为该容器的LivenessProbe探针返回的值永远是“Success”。......
  • KAFKA配置 SASL_SSL双重认证
    1.背景kafka提供了多种安全认证机制,主要分为SASL和SSL两大类。SASL:是一种身份验证机制,用于在客户端和服务器之间进行身份验证的过程,其中SASL/PLAIN是基于账号密码的认证方式。SSL:是一种加密协议,用于在网络通信中提供数据的保密性和完整性。它使用公钥和私钥来建立安全的连接,并......
  • Spring Cloud Gateway网关下Knife4j文档聚合,以及动态路由的读取和代码配置
    SpringCloudGateway网关下Knife4j文档聚合,以及动态路由的读取和配置一.Knife4j文档聚合1.1基础环境明细1.2集成knife4j1.2.1maven1.2.2yml配置1.2.2.1其他模块配置1.2.2.2manual手动配置模式1.2.2.3discover服务发现模式1.2.2.3==这里请注意==:如果你使用了:S......
  • SpringBoot开发中的日志级别
    文章目录前言一、日志级别是什么?二、使用步骤1.**添加依赖**:2.**配置日志级别**:3.**在代码中使用日志**:总结前言在SpringBoot开发中,日志系统是一个不可或缺的部分,它帮助我们跟踪应用程序的运行状态、调试代码以及监控性能。然而,随着日志信息的不断增加,如何合......
  • 窥探Mybatis配置到执行源码剖析
    mybatis自动配置过程首先我们项目中使用mybatis如果是mybatis的话会引入依赖<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>${mybatis-plus.version}</ve......
  • Docker配置与使用详解
    一、引言随着云计算和微服务的兴起,Docker作为一种轻量级的容器化技术,越来越受到开发者和运维人员的青睐。Docker通过容器化的方式,将应用程序及其依赖项打包成一个可移植的镜像,从而实现了应用程序的快速部署和扩展。本文将详细介绍Docker的配置与使用,包括Docker的安装、镜像......
  • 为了保证openGauss的正确安装,请首先对主机环境进行配置
    初始化安装环境为了保证openGauss的正确安装,请首先对主机环境进行配置。准备安装用户及环境手工建立互信配置操作系统参数准备安装用户及环境创建完openGauss配置文件后,在执行安装前,为了后续能以最小权限进行安装及openGauss管理操作,保证系统安全性,需要运行安装前置脚本gs_......
  • springboot——https请求异常Invalid character found in method name. HTTP method n
    遇到问题的情况接口没有配置https,请求时用https会此异常。其他情况1、问题现象java.lang.IllegalArgumentException:Invalidcharacterfoundinmethodname.HTTPmethodnamesmustbetokensatorg.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11Inp......