每个开发者势必都会用到一些Cache暂存工具,但依据小编在业界与各国开发者经验交手而言,大部分都是采用Guava Cache这项套件,甚少人知道Spring框架有内部提供Cache注解模式机制,小编今天就带领大家来一窥Spring框架中三项Cache注解可缓存(@Cacheable)、缓存放置(@CachePut)及缓存清除(@CacheEvict),其注解模式支援相当多种判断依据,可说是相当不错,不仅可透过各种模型内的宣告栏位当作索引,亦可透过配置的引数(argument)的指数位置当做索引等等,可是支援相当多元化的快取性注解支援,相关原理我们在下面做进一步介绍。
快取为一种一数据暂存在记忆体中,他的操作原理就是置放、删除两种行为,在Spring Cache操作原理中,会分析是否有重复的索引(Key)并检查其值(Value),若有会将其值(Value)删除掉,并在将其新值逐一至放进此Cache,所有的索引值配对(Key-Value)都会存放在一个ConcurrentMap的配置池中,其锁引键产生原理为来源类别、来源方法及注解上锁配置的索引关键词三项所组成,当组装完成後,会透过Spring框架SpelExpression核心元件中的SpelNodeImpl物件,并针对EvaluationContext进行包装後取得关键索引值物件,其EvaluationContext以包装BeanFactory,协助内部个元件快速获取相关物件值,追朔生成原理可看出相当复杂,当开发者进行取得值行为时,皆会产生一笔执行绪,并去行执行Cache.get()行为,相关生成与范例请参照下方程序码。
关键词物件产生核心程序码片段
@Override
@Nullable
public Object getValue(EvaluationContext context) throws EvaluationException {
Assert.notNull(context, "EvaluationContext is required");
CompiledExpression compiledAst = this.compiledAst;
if (compiledAst != null) {
try {
return compiledAst.getValue(context.getRootObject().getValue(), context);
}
catch (Throwable ex) {
// If running in mixed mode, revert to interpreted
if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
this.compiledAst = null;
this.interpretedCount.set(0);
}
else {
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
}
}
}
ExpressionState expressionState = new ExpressionState(context, this.configuration);
Object result = this.ast.getValue(expressionState);
checkCompile(expressionState);
return result;
}
范例一、同步储存快取范例
@Cacheable(value = "cache1", sync = true)
@Override
public List<User> listUserObject() {
System.out.println("list user Object!");
return this.userList;
}
@CacheEvict(value="cache1", allEntries=true, beforeInvocation=true)
@Override
public boolean clearUserObject() {
System.out.println("Clear cache finish");
this.userList.clear();
return true;
}
@Override
public User createUserObject(User user) {
System.out.println("create user Object!");
this.userList.add(user);
return user;
}
范例一结果、由下可得知在建立新的使用者物件,虽未在程序码中执行listUserObject(),但有触发到该方法内关联的物件,必定会执行到Spring核心中再次触发到listUserObject()方法,由结果中可看出在建立中及清空都有呼叫相关方法。
========== Before Test ==========
create user Object!
list user Object!
[{"id":10,"username":"john","password":"123456","email":"[email protected]","role":"admin","remark":"come from penghu."},{"id":20,"username":"weisting","password":"654321","email":"[email protected]","role":"sales","remark":"come from taoyuan"},{"id":30,"username":"show","password":"987765","email":"[email protected]","role":"customer","remark":"come from taipei"},{"id":0,"username":"abc0","password":"123456","email":"[email protected]","role":"customer service.","remark":"remark"}]
.....
create user Object!
.....
.....
Clear cache finish
list user Object!
[]
[]
[]
========== Finish Test ==========
范例二、透过方法引数名称当快取索引
@Cacheable(value="user", key="#name")
@Override
public User getUserObjectByUserName(String name) {
System.out.println("get user Object by user " + name + "!");
return this.userList.stream().filter(user -> {
return user.getUsername().equalsIgnoreCase(name);
}).findAny().orElse(null);
}
范例二结果:仅有第一次触发方法进行分析暂存,後续相关结果皆会采用快取内的直,由测试後可得知
========== Before Test ==========
get user Object by user weisting!
{"id":20,"username":"weisting","password":"654321","email":"[email protected]","role":"sales","remark":"come from taoyuan"}
get user Object by user show!
{"id":30,"username":"show","password":"987765","email":"[email protected]","role":"customer","remark":"come from taipei"}
......
......
......
{"id":30,"username":"show","password":"987765","email":"[email protected]","role":"customer","remark":"come from taipei"}
{"id":30,"username":"show","password":"987765","email":"[email protected]","role":"customer","remark":"come from taipei"}
{"id":20,"username":"weisting","password":"654321","email":"[email protected]","role":"sales","remark":"come from taoyuan"}
========== Finish Test ==========
范例三、透过方法引数模型中的宣告栏位(Field)当快取索引
@Cacheable(value="userCache", key="#user.email")
@Override
public User verifyUserEmail(User user) {
System.out.println("Verify User Email success.");
if (user.getEmail().contains("@"))
return user;
user.setEmail("N/A");
return user;
}
@Test
public void testCacheByObjectField() {
Random random = new Random();
for ( int i = 0 ; i < 20 ; i++ ) {
int rand = random.nextInt(500)*100;
User user = new User();
if (i % 5 == 0) {
user.setId(rand).setUsername("cache")
.setPassword("3383838")
.setRole("sale")
.setRemark("cache is get")
.setEmail("test"+ i+(rand % 3 == 0 ? '@' : "") + "abbbb.com");
} else {
user.setId(rand).setUsername("cache")
.setPassword("3383838")
.setRole("sale")
.setRemark("cache is get")
.setEmail("test"+ (rand % 3 == 0 ? '@' : "") + "abbbb.com");
}
System.out.println(new Gson().toJson(userService.verifyUserEmail(user)));
}
}
范例三结果:虽用email当索引值,但依旧内部模型值有变动,依旧会进行更新相关值。
========== Before Test ==========
Verify User Email success.
{"id":8100,"username":"cache","password":"3383838","email":"[email protected]","role":"sale","remark":"cache is get"}
Verify User Email success.
{"id":30300,"username":"cache","password":"3383838","email":"[email protected]","role":"sale","remark":"cache is get"}
Verify User Email success.
{"id":36500,"username":"cache","password":"3383838","email":"N/A","role":"sale","remark":"cache is get"}
{"id":36500,"username":"cache","password":"3383838","email":"N/A","role":"sale","remark":"cache is get"}
{"id":30300,"username":"cache","password":"3383838","email":"[email protected]","role":"sale","remark":"cache is get"}
Verify User Email success.
{"id":42000,"username":"cache","password":"3383838","email":"[email protected]","role":"sale","remark":"cache is get"}
{"id":36500,"username":"cache","password":"3383838","email":"N/A","role":"sale","remark":"cache is get"}
{"id":30300,"username":"cache","password":"3383838","email":"[email protected]","role":"sale","remark":"cache is get"}
{"id":30300,"username":"cache","password":"3383838","email":"[email protected]","role":"sale","remark":"cache is get"}
{"id":36500,"username":"cache","password":"3383838","email":"N/A","role":"sale","remark":"cache is get"}
Verify User Email success.
{"id":26900,"username":"cache","password":"3383838","email":"N/A","role":"sale","remark":"cache is get"}
{"id":36500,"username":"cache","password":"3383838","email":"N/A","role":"sale","remark":"cache is get"}
{"id":36500,"username":"cache","password":"3383838","email":"N/A","role":"sale","remark":"cache is get"}
{"id":36500,"username":"cache","password":"3383838","email":"N/A","role":"sale","remark":"cache is get"}
{"id":36500,"username":"cache","password":"3383838","email":"N/A","role":"sale","remark":"cache is get"}
Verify User Email success.
{"id":25800,"username":"cache","password":"3383838","email":"[email protected]","role":"sale","remark":"cache is get"}
{"id":36500,"username":"cache","password":"3383838","email":"N/A","role":"sale","remark":"cache is get"}
{"id":36500,"username":"cache","password":"3383838","email":"N/A","role":"sale","remark":"cache is get"}
{"id":36500,"username":"cache","password":"3383838","email":"N/A","role":"sale","remark":"cache is get"}
{"id":36500,"username":"cache","password":"3383838","email":"N/A","role":"sale","remark":"cache is get"}
========== Finish Test ==========
范例四、判断式条件快取,并以模型内栏位当索引
@Cacheable(value={"userCacheId"}, key="#user.id", condition="#user.id%2==0")
@Override
public User logUserById(User user) {
System.out.println("log user by username: " + user.getId());
logList.add(user);
return user;
}
@Test
public void testLogCacheByLogId() {
for (int i = 0 ; i < 20 ; i++) {
User user = new User();
user.setId(i%5).setUsername("cache")
.setPassword("3383838")
.setRole("sale")
.setRemark("cache is get")
.setEmail("test"+ (i%5)+ "@abbbb.com");
System.out.println(new Gson().toJson(userService.logUserById(user)));
}
}
范例四结果,可以看到执行到第一段偶数ID後,後续所有奇数笔数资料会跳过快取,并直接运行方法内在获取相关结果。
========== Before Test ==========
log user by username: 0
{"id":0,"username":"cache","password":"3383838","email":"[email protected]","role":"sale","remark":"cache is get"}
log user by username: 1
{"id":1,"username":"cache","password":"3383838","email":"[email protected]","role":"sale","remark":"cache is get"}
log user by username: 2
{"id":2,"username":"cache","password":"3383838","email":"[email protected]","role":"sale","remark":"cache is get"}
log user by username: 3
{"id":3,"username":"cache","password":"3383838","email":"[email protected]","role":"sale","remark":"cache is get"}
log user by username: 4
{"id":4,"username":"cache","password":"3383838","email":"[email protected]","role":"sale","remark":"cache is get"}
{"id":0,"username":"cache","password":"3383838","email":"[email protected]","role":"sale","remark":"cache is get"}
log user by username: 1
{"id":1,"username":"cache","password":"3383838","email":"[email protected]","role":"sale","remark":"cache is get"}
{"id":2,"username":"cache","password":"3383838","email":"[email protected]","role":"sale","remark":"cache is get"}
log user by username: 3
{"id":3,"username":"cache","password":"3383838","email":"[email protected]","role":"sale","remark":"cache is get"}
{"id":4,"username":"cache","password":"3383838","email":"[email protected]","role":"sale","remark":"cache is get"}
{"id":0,"username":"cache","password":"3383838","email":"[email protected]","role":"sale","remark":"cache is get"}
log user by username: 1
{"id":1,"username":"cache","password":"3383838","email":"[email protected]","role":"sale","remark":"cache is get"}
{"id":2,"username":"cache","password":"3383838","email":"[email protected]","role":"sale","remark":"cache is get"}
log user by username: 3
{"id":3,"username":"cache","password":"3383838","email":"[email protected]","role":"sale","remark":"cache is get"}
{"id":4,"username":"cache","password":"3383838","email":"[email protected]","role":"sale","remark":"cache is get"}
{"id":0,"username":"cache","password":"3383838","email":"[email protected]","role":"sale","remark":"cache is get"}
log user by username: 1
{"id":1,"username":"cache","password":"3383838","email":"[email protected]","role":"sale","remark":"cache is get"}
{"id":2,"username":"cache","password":"3383838","email":"[email protected]","role":"sale","remark":"cache is get"}
log user by username: 3
{"id":3,"username":"cache","password":"3383838","email":"[email protected]","role":"sale","remark":"cache is get"}
{"id":4,"username":"cache","password":"3383838","email":"[email protected]","role":"sale","remark":"cache is get"}
========== Finish Test ==========
范例五、快取置放方法
@CachePut("updateCache")
@Override
public List<User> updateUserObject(User user) {
System.out.println("update user Object!");
this.userList = this.userList.stream().map(userObj -> {
if (String.valueOf(userObj.getId()).equalsIgnoreCase(String.valueOf(user.getId()))) {
userObj.setUsername(user.getUsername());
userObj.setEmail(user.getEmail());
userObj.setPassword(user.getPassword());
userObj.setRole(user.getRole());
userObj.setRemark(user.getRemark());
}
return userObj;
}).collect(Collectors.toList());
return this.userList;
}
@Test
public void testUpdateUserObject() {
User user = new User()
.setId(10)
.setUsername("john2")
.setRole("admin2")
.setPassword("654321")
.setEmail("[email protected]")
.setRemark("come from Taipei.");
System.out.println(new Gson().toJson(userService.listUserObject()));
userService.updateUserObject(user);
System.out.println(new Gson().toJson(userService.listUserObject()));
}
范例五结果、可以看到结果,id为10这笔资料我们进行更新,会直接进行更新快取内的值,不会在触发到listUserObject()方法,可由下列结果得知。
========== Before Test ==========
list user Object!
[{"id":10,"username":"john","password":"123456","email":"[email protected]","role":"admin","remark":"come from penghu."},{"id":20,"username":"weisting","password":"654321","email":"[email protected]","role":"sales","remark":"come from taoyuan"},{"id":30,"username":"show","password":"987765","email":"[email protected]","role":"customer","remark":"come from taipei"}]
update user Object!
[{"id":10,"username":"john2","password":"654321","email":"[email protected]","role":"admin2","remark":"come from Taipei."},{"id":20,"username":"weisting","password":"654321","email":"[email protected]","role":"sales","remark":"come from taoyuan"},{"id":30,"username":"show","password":"987765","email":"[email protected]","role":"customer","remark":"come from taipei"}]
========== Finish Test ==========
由以上范例可以详细地看出@Cacheable、@CachePut及@CacheEvict三种方法运用的方式,给各位开发者做一个参考。
图一、Spring Cache运作存取架构图
此项Spring Cache为2015年提倡出来的注解行快取技术,能够减少开发者元件触发(invoke)方法的频率,透过以上架构图可得知,所有快取的拦截模组皆透过CacheInterceptor元件,其元件会透过支援角色CacheAspectSupport进行处理,会经过七道流程处理,第一道同步调曲及特殊处理,先判断是否有采用条件式(Condition)判断,并在进行产生索引值,取得快取值及回传值,第二道进行先行删除部分旧快取资料,第三道检查是否有符合条件的缓存项目,第四项寻找没有存放在可缓存(@Cacheable)的项目,第五道搜集所有确定的快取置放池(@CachePuts),第六道处理所有相关漏掉存放的快取及将要存放的快取资料,最後第七道处理相关不需要的快取将其删除,各位开发者有兴趣可从原始码得知,以上叙述提供给各位做参考。
Run test task
gradle test
Run open result html
open ./build/reports/tests/test/index.html
Cache test report
Mind-blowing cache test case information & detail
Spring缓存注解@Cacheable、@CacheEvict、@CachePut使用
>>: 爬虫怎麽爬 从零开始的爬虫自学 DAY17 python爬虫所需套件
前篇回顾 sed - 简介 读取编辑文字档的好工具 sed - 2 Pattern sed - 3 ...
前言 前几天我们完成惹user跟skill的ajax的前端界接服务 并且有成功更新资料进资料库 今天...
想当一个 Good PHPer,不但要写程序、写注解还要写 API 文件,想到要维护三个地方工程师就...
好的网站除了内容传达之外,颜色是进入网站的第一印象,可以针对文字大小、框线、背景色...等做变化,是...
简单架设 x 不失质感 目录 源起 : 开发者网站 开发工具 : Adobe Brackets 基础...