SpringBoot从Redis中取对象缓存强转发生ClassCastException
今天在优化单点登录代码时,将登录用户存进Redis,并在登录拦截器根据Token取出来时,发现Object转换居然报了强转异常?这让我有点措手不及,从来没有遇到过这种情况
异常日志信息
java.lang.ClassCastException: com.model.user.entity.UserEntity cannot be cast to com.model.user.entity.UserEntity at com.zlhj.re.common.config.web.interceptor.LoginAuthInterceptor.preHandle(LoginAuthInterceptor.java:77) at org.springframework.web.servlet.HandlerExecutionChain.applyPreHandle(HandlerExecutionChain.java:136) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1034) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005) at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897) at javax.servlet.http.HttpServlet.service(HttpServlet.java:634) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882) at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
仔细看可以发现两边转换的包名和类名是完全一样的,【java.lang.ClassCastException: com.model.user.entity.UserEntity cannot be cast to com.model.user.entity.UserEntity】
这说明两者确实是同一个类。但是为什么还会出现强转异常
代码片段
从上面图片可以看到我将对象从Redis中取出来时,类的类型已经是UserEntity类型了
报错原因
从*中找到了一篇文章:ArticleOf*, 发现确实是这样,因为项目中用到了SpringBoot热部署devtools的依赖包
我们先来了解一下spring-boot-devtools是个什么东西吧:
spring-boot-devtools是spring为开发者提供的一个服务模块,用来使Spring Boot应用支持热部署,也就是修改代码后自动启动springboot服务,无需手动重启Spring Boot应用,以提高开发者的开发效率
那么它的实现原理:
主要是使用了两个ClassLoader,一个Classloader加载不会改变的类(第三方Jar包),另一个ClassLoader加载会更改的类,称为 restart ClassLoader,这样在有代码更改的时候,原来的restartClassLoader 被丢弃,重新创建一个restartClassLoader,由于需要加载的类相比较少,所以实现了较快的重启时间(5秒以内)。
由此我们可以知道,使用了两个ClassLoader,两个不同的类加载器,举个栗子: 当对象序列化到缓存中时,应用程序类加载器是C1。然后,更改一些代码或者配置后,devtools会自动重新启动上下文并创建一个新的类加载器C2。所以当你通过redis操作获取缓存反序列化的时候应用的类加载器是C2,虽然包名及其来类名完全一致,但是序列化与反序列化是通过不同的类加载器加载则在JVM中它们也不是同一个类。如果缓存库没有考虑上下文类加载器,那么这个对象会附加错误的类加载器 ,也就是我们常见的类强制转换异常(ClassCastException)
所以出现强制转换异常的原因是使用的类加载器不一样。
报错的同学请自己查看一下POM文件是否有引用这一依赖吧
解决方法
解决方法就是将这个依赖注释掉,不使用这一工具。我原本是用的一个叫Dozer的转换工具来转换的,原本觉得因为一个报错而去放弃使用一个工具有点激进。但是同事说以后肯定不止这一个地方要从Redis中取对象,难道每个地方你都得用工具转一下吗?我想了下确实有道理,热部署其实是属于一个可有可无的功能,但是因为这个功能让我们代码重复量更大就有点不值得了。所以还是果断抛弃这工具把它注释了