目前网上关于jetcache的使用大多是基于官网的解释,给初学者造成很大的困扰,这里就将我使用的过程中遇到的坑总结一下。
项目是一个springboot项目,目前需要在一个接口方法上加@cache注解,希望将方法返回的结果连同自定义的key一起存到远程redis中。在实现的过程中遇到了如下的问题:
1. 希望将方法中传入的参数经过处理后做为缓存的key,但是不知道jetcache中spel表达式如何调用方法。
2. 运行过程报错:org.springframework.expression.spel.SpelEvaluationException: EL1072E: An exception occurred whilst evaluating a compiled expression
4.0.0
com.example
demo
0.0.1-SNAPSHOT
jar
springboot-demo
Demo project for Spring Boot
org.springframework.boot
spring-boot-starter-parent
2.0.6.RELEASE
dev
true
com.mysql.jdbc.Driver
jdbc:mysql://localhost:3306/test?allowMultiQueries=true
root
root
UTF-8
UTF-8
1.8
Finchley.SR1
org.springframework.boot
spring-boot-starter-data-redis
org.springframework.boot
spring-boot-starter-web
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.2
com.alibaba
druid-spring-boot-starter
1.1.10
org.springframework.boot
spring-boot-devtools
true
com.alicp.jetcache
jetcache-starter-redis
2.5.9
org.apache.commons
commons-lang3
3.6
org.apache.commons
commons-collections4
4.1
mysql
mysql-connector-java
runtime
org.apache.commons
commons-lang3
3.6
commons-codec
commons-codec
1.10
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
maven-resources-plugin
3.0.2
$
false
org.apache.maven.plugins
maven-compiler-plugin
1.8
${project.build.sourceEncoding}
-parameters
spring:
datasource:
druid:
url: $db.url$
username: $db.username$
password: $db.password$
driver-class-name: $db.driver$
# jetcache使用
jetcache:
statIntervalMinutes: 60
areaInCacheName: false
local:
default:
type: linkedhashmap
keyConvertor: fastjson
remote:
default:
type: redis
keyConvertor: fastjson
valueEncoder: java
valueDecoder: java
timeout: 5000
poolConfig:
minIdle: 5
maxIdle: 10
maxTotal: 20
testOnBorrow: false
testOnReturn: true
host: 47.93.**.**
port: 6378
mybatis:
mapper-locations:
- classpath:conf/mapper/*.xml
config-location: classpath:conf/mybatis/mybatis.xml
@SpringBootApplication
@EnableCreateCacheAnnotation
@MapperScan("com.example.demo.dao")
@EnableMethodCache(basePackages = "com.example.demo")
@EnableAspectJAutoProxy(exposeProxy = true, proxyTargetClass = true)
public class SpringbootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootDemoApplication.class, args);
}
}
@EnableCreateCacheAnnotation注解用于开启jetcache中@CreateCache注解,
@EnableMethodCache(basePackages = “com.example.demo”)注解用于开启@cache注解
public interface IUserService {
String addRecord(String mobile);
String addIntRecord(String mobile);
String addUser(User user);
String processMobile(User user);
}
@Service
public class UserServiceImpl implements IUserService{
@Override
public String addRecord(String mobile) {
User user = new User();
user.setAge("22");
user.setCity("上海");
user.setMobile(mobile);
IUserService userService = (IUserService) AopContext.currentProxy();
userService.processMobile(user);
return "test liu";
}
@Override
@Cached(name="ls:dlc:int",key="targetObject.processMobile(#mobile)",cacheType=CacheType.REMOTE)
public String addIntRecord(String mobile) {
return "processMobile";
}
@Cached(name="ls:dlc:scoring:",key="#user.name",cacheType=CacheType.REMOTE)
@Override
public String addUser(User user) {
return user.getAge();
}
@Cached(name="ls:dlc:scoring:",key="#user.name",cacheType=CacheType.REMOTE)
@Override
public String processMobile(User user) {
System.out.println("调用processMobile方法。。。");
return "user:"+user.getMobile();
}
public String processMobile(String mobile){
System.out.println(mobile);
return "processMobile"+mobile;
}
}
这里注意两个processMobile方法的不同,一个是string字符作为参数,一个是对象作为参数;一个是实现接口方法,一个是实现类中自己的方法。
@RestController
public class JetcacheController {
@Autowired
private IUserService userService;
@RequestMapping("jet_spel_test")
public String JetSpelTest(){
String addIntRecord = userService.addIntRecord("22222");
return addIntRecord;
}
@RequestMapping("jet_mobile_test")
public String JetMobileTest(String mobile){
String addRecord = userService.addRecord("123456789");
return addRecord;
}
@RequestMapping("jet_object_test")
public String JetObjectTest(){
User user = new User();
user.setAge("23");
user.setCity("南京");
user.setName("zhaosi");
String result = userService.addUser(user);
System.out.println("result:"+result);
return result;
}
}
最初的需求是当调用userservice.addRecord(string mobile),希望对mobile进行特殊处理,比如md5加密后作为缓存key,所以在实现类中还定义了一个processMobile方法,用于处理手机号。不要说什么可以加密之后再传入方法,这里只是举例说明如果有这种需要。
但是试了很多,spEL的表达式都找不到当前类中的processMobile方法,在spring cache中这种情况是使用:
SpEL表达式可基于上下文并通过使用缓存抽象,提供与root独享相关联的缓存特定的内置参数。
名称 | 位置 | 描述 | 示例 |
---|---|---|---|
methodName | root对象 | 当前被调用的方法名 | #root.methodname |
method | root对象 | 当前被调用的方法 | #root.method.name |
target | root对象 | 当前被调用的目标对象实例 | #root.target |
targetClass | root对象 | 当前被调用的目标对象的类 | #root.targetClass |
args | root对象 | 当前被调用的方法的参数列表 | #root.args[0] |
caches | root对象 | 当前方法调用使用的缓存列表 | #root.caches[0].name |
Argument Name | 执行上下文 | 当前被调用的方法的参数,如findArtisan(Artisan artisan),可以通过#artsian.id获得参数 | #artsian.id |
result | 执行上下文 | 方法执行后的返回值(仅当方法执行后的判断有效,如 unless cacheEvict的beforeInvocation=false) | #result |
但是这种方式在jetcache中并不好用,最后还是在github的issues中找到了解决方案:
正如上面接口实现类中的addIntRecord所示,使用targetObject获取当前类中的方法。这里需要注意的是调用的processMobile方法需要是public修饰,需要传参使用#+参数的方式传递。
这一步是在没有找到spEL表达式的时候,所想到的替代方法,因为官网给的教程是通过对象获取参数,或者直接传递参数。
这里如addRecord方法所示,addRecord方法我并没有加@cache注解,而是使用user对象存放mobile属性,通过调用@cache注解修饰的processMobile(User user)方法去实现将结果加入远程缓存中,即将需要进行的处理逻辑和最终的返回结果放入processMobile(User user)方法中,这时候通过#user.mobile就可以获取到手机号了。
这里遇到的问题是通过一个方法调用当前类中的另一个方法的时候,不能保持事务的一致性,即所调用的方法不是spring所管理的,最终也会报错。
解决方式有两种:
即在方法上通过@autowired或者@resource注解引入userservice对象,在此中使用此对象调用service中的方法。
IUserService userService = (IUserService) AopContext.currentProxy();
userService.processMobile(user);
使用aopContext获取当前代理对象,需要在启动类上加上如下注解,即需要手动暴露代理
@EnableAspectJAutoProxy(exposeProxy = true, proxyTargetClass = true)
在使用的过程中,最初存到远程redis缓存成功,但是从缓存中取的时候就会报错的问题。错误信息大致如下:
org.springframework.expression.spel.SpelEvaluationException: EL1072E: An exception occurred whilst evaluating a compiled expression
Caused by: java.lang.ClassCastException: com.example.entity.User cannot be cast to com.example.entity.User
at spel.Ex3.getValue(Unknown Source)
at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:254)
... 42 common frames omitted
最开始看到这个错误是蒙蔽的,怎么user不能转成user呢,网上还是查不到任何有用的信息,最终还是在github的issues中找到了解释:
测试结果的时候,我将pom文件中关于devtools的依赖取消了,果然运行没有报错,至于-parameters参数的设置,我也修改了,但是并没有解决实质性的问题。
这里还是附一下-parameter参数的设置方式:
org.apache.maven.plugins
maven-compiler-plugin
1.8
${project.build.sourceEncoding}
-parameters
jetcache的文档还是太少,网上有的教程一般都是网上能够找到的,但是一些特别的坑还是要取issues中才能找到解决方法,这也算是给自己增加了使用开源框架的经验。