介绍
我们以前学过咖啡因
咖啡因,本地缓存性能之王,也提到过弹簧靴
默认情况下使用的本地缓存也是咖啡因
让我们看看今天咖啡因
如何与弹簧靴
整合。
集成的咖啡因
咖啡因
而且弹簧靴
有两种整合方式:
- 一种是我们直接介绍
咖啡因
依赖,然后使用咖啡因
方法实现缓存。相当于使用本机api -
介绍
咖啡因
而且春天的缓存
依赖,使用弹簧缓存
带注释的方法实现缓存。弹簧缓存帮助我们封装咖啡因
Pom文件导入<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-缓存</artifactId> </dependency> <dependency> <groupId>com.github.ben-manes.咖啡因</groupId> <artifactId>咖啡因</artifactId> <version>2.6.0</version> </dependency>
第一种方法
首先配置
缓存
,建立一个缓存
对象,以及后续的添加、删除和关于缓存的查询都基于此缓存
对象。@Configuration public class 缓存Config { @Bean public 缓存<String, Object> 咖啡因缓存() { return 咖啡因.新Builder() // Set a fixed time to expire after the last write or access .expireAfterWrite(60, TimeUnit.SECONDS) // Initial 缓存 space size .initialCapacity(100) // The maximum number 的 缓存d items .maximumSize(1000) .build(); }
第一种方法我们就不一一介绍了,基本用一下
咖啡因缓存
根据您自己的业务,操作以下方法以这种方式使用,它对代码是侵入性的。
第二种方式
-
它需要在SpingBoot启动类上进行标记
EnableCaching
注意,这个东西和许多框架是一样的,例如,我们集成达博
还需要标记@EnableDubbole
笔记等。@弹簧靴Application @EnableCaching public class DemoApplication { public static void main(String[] args) { 春天Application.run(DemoApplication.class, args); }
-
存在
application.yml
配置我们的缓存类型,过期时间,缓存策略等。spring: pr的iles: active: dev 缓存: type: CAFFEINE 咖啡因: spec: maximumSize=500,expireAfterAccess=600s
如果我们不习惯使用这种配置方式,当然也可以使用
JavaConfig
替换配置文件的配置方法。@Configuration public class 缓存Config { @Bean public 缓存Manager 缓存Manager() { 咖啡因缓存Manager 缓存Manager = 新 咖啡因缓存Manager(); 缓存Manager.set咖啡因(咖啡因.新Builder() // Set a fixed time to expire after the last write or access .expireAfterAccess(600, TimeUnit.SECONDS) // Initial 缓存 space size .initialCapacity(100) // The maximum number 的 缓存d items .maximumSize(500)); return 缓存Manager; }
接下来是如何在代码中使用这个缓存
@Override @缓存Put(价值 = "user", 关键 = "#userDTO.id") public UserDTO save(UserDTO userDTO) { userRepository.save(userDTO); return userDTO; } @Override @缓存Evict(价值 = "user", 关键 = "#id")//2 public void remove(Long id) { logger.info("Deleted the data 缓存 whose id 而且 关键 are " + id + ""); } @Override @缓存able(价值 = "user",关键 = "#id") public UserDTO getUserById(Long id) { return userRepository.find一个(id); }
在上面的代码中,我们可以看到有几个注释
@缓存Put, @缓存Evict, @缓存able
我们只需要在方法上标记这些注释,就可以使用缓存。让我们分别介绍这些注释。@缓存able
@缓存able
它可以标记在类上或方法上。当它在类上被标记时,就意味着这个类上的所有方法都将支持缓存。相同的
当它作用于一个方法时,它表示该方法支持缓存。例如,在我们上面的代码中getUserById
在这种方法中,第一次缓存中没有数据,因此我们将进行查询DB
,但是第二个查询不会执行DB
查询,但直接从缓存获取结果并返回。值属性
-
@缓存able
的价值
必须指定该属性,该属性指示当前方法的返回值将缓存在何处缓存
On,对应于缓存
这个名字。关键
-
@缓存able
的关键
有两种方法,一种是指定我们自己的显示关键
,也有一种默认生成策略,默认生成策略为简单键Generator
这个类,这个生成关键
方法比较简单,我们可以看看它的源代码:public static Object generateKey(Object... params) { // If the method has no parameter 关键, it is a 新 简单键() if (params.length == 0) { return 简单键.EMPTY; } // If the method has only one parameter 关键 is the current parameter if (params.length == 1) { Object param = params[0]; if (param != 零 && !param.getClass().isArray()) { return param; } } // If the 关键 has multiple parameters, the 关键 is 新 简单键, but the hashCode 而且 Equals methods 的 这 简单键 object are rewritten according to the parameters passed in by the method. return 新 简单键(params); }
上面的代码还是很容易理解的,可以分为三种情况:
- 如果方法没有参数,则新使用全局空
简单键
对象作为关键
。 - 该方法只有一个参数,并且当前参数被用作
关键
-
方法参数大于
1
一,就新
一个简单键
对象作为关键
,新
这简单键
当使用传入的参数重写时简单键
的hashCode
而且=
方法,
至于你为什么要重写它,就照着做吧地图
使用自定义对象作为关键
什么时候必须重写hashCode
而且=
方法的原理是一样的,因为caffein
也在…的帮助下ConcurrentHash地图
履行,总结
从上面的代码中,我们可以发现默认生成
关键
它只与我们传入的参数相关。如果我们在一个类中有多个不带参数的方法,然后我们使用默认的缓存生成策略,缓存将丢失。
或者缓存会互相覆盖,也可能是这样ClassCastException
因为它们都使用相同的关键
. 例如,以下代码将抛出异常(ClassCastException
)@缓存able(价值 = "user") public UserDTO getUser() { UserDTO userDTO = 新 UserDTO(); userDTO.setUserName("Java Finance"); return userDTO; } @缓存able(价值 = "user") public UserDTO2 getUser1() { UserDTO2 userDTO2 = 新 UserDTO2(); userDTO2.setUserName2("javajr.cn"); return userDTO2; }
因此,一般不建议使用默认的缓存生成
关键
策略。如果我们必须使用它,我们最好自己重写它,带来方法名,等等。类似于以下代码:@Component public class MyKeyGenerator extends 简单键Generator { @Override public Object generate(Object target, Method 方法, Object... params) { Object generate = super.generate(target, 方法, params); String format = MessageFormat.format("{0}{1}{2}", method.toGenericString(), generate); return format; }
自定义键
我们可以通过
春天
EL表达式来指定我们的关键
. 这里的EL表达式可以使用方法参数及其对应的属性。
在使用方法参数时,可以直接使用“#参数名称
& # 8220;或# 8221;#p参数索引
“这是我们的建议:@缓存able(价值="user", 关键="#id") public UserDTO getUserById(Long id) { UserDTO userDTO = 新 UserDTO(); userDTO.setUserName("java finance"); return userDTO; } @缓存able(价值="user", 关键="#p0") public UserDTO getUserById1(Long id) { return 零; } @缓存able(价值="user", 关键="#userDTO.id") public UserDTO getUserById2(UserDTO userDTO) { return 零; } @缓存able(价值="user", 关键="#p0.id") public UserDTO getUserById3(UserDTO userDTO) { return 零; }
@缓存Put
@缓存Put
指定的属性是和@缓存able
是一样的,但两者之间有区别,@缓存Put
被标记的方法不会首先查询缓存是否有值,而是每次都会先执行该方法,然后返回结果,结果也会被缓存。![Insert picture description here]()
为什么是这样一个过程?我们可以去看看它的源代码。关键代码在这一行。
缓存.ValueWrapper 缓存命中 = find缓存dItem(上下文.get(缓存ableOperation.class));
当我们用这个方法@缓存able
当注释上下文
里面会放缓存ableOperation
将它添加进去,只有当context .get(缓存ableOperation.class)获取的内容不是空的时候,它才会从缓存中获取内容,否则缓存命中
会直接返回零
. 至于上下文何时加入缓存ableOperation,让我们看一下# 弹簧缓存AnnotationParser parse缓存Annotations
这种方法会理解。具体的源代码将不显示,感兴趣的人可以自己浏览。
@缓存Evict
删除缓存中的数据。它的用法与前面两个注释类似,都有值和键属性。应该注意的是,它还有一个附加属性beforeInvocation
-
beforeInvocation
注意,该属性的默认值为假,这意味着在调用方法之前不会删除缓存,只有在成功执行方法之后才会删除缓存。设置为真正的
如果调用该方法,则将在调用该方法之前删除缓存。成功执行该方法之后,还将调用以删除缓存,这是双重删除。如果方法执行异常,则不会删除缓存。 -
allEntrie
是否清除所有缓存内容,默认值为假
,如指定为真正的
,所有缓存将在方法调用后立即清除
@Caching
这是一个组合注释,集成了上述三个注释,有三个属性:可缓存,放置和驱逐
,用于指定@缓存able
、@缓存Put
而且@缓存Evict
。
总结
第二种方法是侵入式的,它的实现原理比较简单,就是通过方面的方法拦截器实现,拦截所有的方法,它的核心代码如下:它看起来像我们的业务代码。感兴趣的朋友也可以看看。
if (上下文.isSynchronized()) {
缓存OperationContext context = 上下文.get(缓存ableOperation.class).iterator().next();
if (isConditionPassing(context, 缓存OperationExpressionEvaluator.NO_RESULT)) {
Object 关键 = generateKey(context, 缓存OperationExpressionEvaluator.NO_RESULT);
缓存 缓存 = context.get缓存s().iterator().next();
try {
return wrap缓存Value(方法, 缓存.get(关键, () -> unwrapReturnValue(invokeOperation(invoker))));
}
catch (缓存.ValueRetrievalException ex) {
// The invoker wraps any Throwable in a ThrowableWrapper instance so we
// can just make sure that one bubbles up the stack.
throw (缓存OperationInvoker.ThrowableWrapper) ex.getCause();
}
}
else {
// No caching required, only call the underlying method
return invokeOperation(invoker);
}
}
// Process any early evictions
// Whether the beforeInvocation attribute is 真正的, if it is 真正的, delete the 缓存
process缓存Evicts(上下文.get(缓存EvictOperation.class), 真正的,
缓存OperationExpressionEvaluator.NO_RESULT);
// Check if we have a 缓存d item matching the conditions
缓存.ValueWrapper 缓存命中 = find缓存dItem(上下文.get(缓存ableOperation.class));
// Collect puts from any @缓存able miss, if no 缓存d item is found
List<缓存PutRequest> 缓存PutRequests = 新 LinkedList<>();
if (缓存命中 == 零) {
collectPutRequests(上下文.get(缓存ableOperation.class),
缓存OperationExpressionEvaluator.NO_RESULT, 缓存PutRequests);
}
Object 缓存Value;
Object returnValue;
if (缓存命中 != 零 && !has缓存Put(上下文)) {
// If there are no put requests, just use the 缓存 hit
缓存Value = 缓存命中.get();
returnValue = wrap缓存Value(方法, 缓存Value);
}
else {
// Invoke the method if we don't have a 缓存 hit
returnValue = invokeOperation(invoker);
缓存Value = unwrapReturnValue(returnValue);
}
// Collect any explicit @缓存Puts
collectPutRequests(上下文.get(缓存PutOperation.class), 缓存Value, 缓存PutRequests);
// Process any collected put requests, either from @缓存Put or a @缓存able miss
for (缓存PutRequest 缓存PutRequest : 缓存PutRequests) {
缓存PutRequest.apply(缓存Value);
}
// Process any late evictions
process缓存Evicts(上下文.get(缓存EvictOperation.class), 假, 缓存Value);
return returnValue;
}
完成
- 由于我的无知,难免会出错。如果您发现了错误,请留言指出给我,我会改正的。
- 如果你觉得文章还不错,你的转发、分享、欣赏、点赞、评论都是对我最大的鼓励。
- 谢谢你的阅读,非常欢迎,谢谢你的关注。
站在巨人的肩膀上摘苹果:
https://www.cnblogs.com/fashf& # 8230; !评论
