Spring Cache @Cacheable 缓存在部分Service中不生效的解决办法

1. 背景

最近开发的项目中,需要大量的使用到缓存以提升性能

其中,有个活动controller,需要查询所有的活动,代码如下:

1
2
3
4
5
@GetMapping("/list")
public RestResult<List<ActivityInfoDTO>> list() {
List<ActivityInfoDTO> list = activityService.getAllActivity();
return addRestResult(list);
}

对应的ActivityService方法如下:

1
2
3
4
5
6
@Override
@Cacheable(value = "ActivityInfoDTO", key = "'getAllActivity'")
public List<ActivityInfoDTO> getAllActivity() {
List<ActivityInfoDTO> allActivity = activityDefMapper.getAllActivity();
return allActivity;
}

结构十分简单,但是奇怪的是,ActivityService里的方法完全不会走缓存

而另一个service,BannerInfoService则可以正常缓存

调试时可以看到,正常的BannerInfoService是有被cglib代理的

而ActivityService是没有被代理的

2. 解决方法

漫长的搜索过后,没能在网上找到解决方案

最终将目光瞄向了ActivityService的其他引用,于是猜测是否是循环引用导致ActivityService没有被代理

结果发现,在另一个OpusService中,有引用该ActivityService

同时OpusService也引用了MessagePushService,而这个MessagePushService又引用了OpusService

于是选择将OpusService中的ActivityService改为懒加载,加上@Lazy注解:

发现成功触发了缓存机制,调试信息也可以看到被cglib代理

3. 总结

之前遇到的缓存不生效大多都是因为在类内部通过this进行调用

这次遇到的情况确实不太常见,循环依赖会导致cglib无法成功代理被依赖的对象,导致缓存失效。

Spring Boot 使用 spring-boot-devtools 实现热加载时出现类型转换异常

热加载问题

网上有给出了许多解决热加载的方法,比如:

在resource目录下创建META-INF/spring-devtools.properties文件

里面的内容为类似下面的内容:

1
2
restart.exclude.companycommonlibs=/mycorp-common-[\\w-]+\.jar
restart.include.projectcommon=/mycorp-myproj-[\\w-]+\.jar

网上能给出的答案一般也就这些,可是作为观众我们自然是表示一脸懵逼啊,这两行啥玩意?

那么现在给出下我的解决办法:

现在大部分的项目在eclipse中基本都是多个项目的形式,在intellij idea中则是多个module的形式

其中,web项目引用了faced项目,没有引用service项目,因为service项目是个独立的dubbo provider(提供者),web项目则为consumer(消费者)

web项目中的controller中有这么一行代码:

1
UserInfo info = userService.findInfo();

其中UserInfo来自faced项目,userService则通过dubbo的reference注入

项目引入spring-boot-devtools了以后,如果改动了web项目中的代码,则下次web项目中运行到了上面这行代码,则会出现如下报错
java.lang.ClassCastException: com.xxx.UserInfo cannot be cast to com.xxx.UserInfo
为啥明明显示的是同一个类,却给我显示类型转换异常?

那么这就要从spring-boot-devtools的工作原理说起

工作原理

spring-boot-devtools的热加载其实是这么工作的

当你启动web项目的时候,你的web项目中的代码都会交给spring-boot-devtools的restart加载器去进行加载,而jar包,则基本会交给base加载器去加载

当项目中的代码有改动时,devtools检测到代码变动,restart加载器就会被扔掉重建,这个时候,所有restart加载器加载的代码都会重新部署,而base加载器则维持不变

因为jar包的内容基本都不会变,所以用base加载器不会有什么问题

但是由于web项目中引用了faced项目,于是你引用的faced的所有代码也会被restart加载器重新加载,此时,刚刚加载的UserInfo则跟项目刚开始启动的UserInfo就是两个类了,java中判断两个类是否为同一个类不仅仅是通过包名+类名去识别,还会去看两个类的加载器是否一致,如果不一致,则识别为两个类,所以就会出现我们之前看到的转换异常

那么解决办法呢?

解决方案

首先我们需要确定我们出现转换异常的类在哪个项目中,我的则是platform-faced项目

那么回到最初的方法,我们需要在resource目录下创建META-INF/spring-devtools.properties文件

里面的内容填写:
restart.exclude.faced=/platform-faced
如果你有多个face项目,且项目名开头都是这个,那么可以使用如下的配置
restart.exclude.faced=/platform-faced[\\w-]+
网上给的答案后面有带上.jar,但是由于我们开发的时候的引用方式是项目引用,后面是不带jar的,所以我们不需要带上.jar

restart.exclude.faced这个键中的faced是可以自己命名的,只需要保证在这个配置文件中唯一即可

文件配置好后,我们可以在项目已启动状态下修改web项目中的代码,可以看到控制台有显示项目重新加载,继续访问到之前触发转换异常的地方,发现已经不会异常了,完美!

使用Spring MVC时传递FlashAttribute无法在controller中接收的问题

为了实现一个授权登录的功能,需要在两个controller中跳转,可是中间传递的参数必须要隐密,于是便使用了RedirectAttributes类进行参数的传递。

可是使用的时候发现,无论使用request.getParameter还是getAttribute以及redirectAttributes.getFlashAttributes().get()的方式都不能获取到结果。

查了很多资料后找到了方法:
public Map<String, Object> acceptAuth(HttpServletRequest request, @ModelAttribute("param") String param)
如上,把需要传递的参数放在方法中,并且加上@ModelAttribute(“xxxx”)就能取到值了,完美!