博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
项目总结10:通过反射解决springboot环境下从redis取缓存进行转换时出现ClassCastException异常问题...
阅读量:6984 次
发布时间:2019-06-27

本文共 7955 字,大约阅读时间需要 26 分钟。

通过反射解决springboot环境下从redis取缓存进行转换时出现ClassCastException异常问题

 

关键字

  springboot热部署  ClassCastException异常 反射 redis

 

前言

  最近项目出现一个很有意思的问题,用户信息(token)储存在redis中;在获取token,反序列化的类型转换的时候,明明是同一个类却总是抛出ClassCastException的异常;

 

正文

 1-问题

异常日志

java.lang.ClassCastException: com.hs.web.common.token.AccessToken cannot be cast to com.hs.web.common.token.AccessToken    at com.hs.web.common.token.AccessTokenManager.getToken(AccessTokenManager.java:31) ~[classes/:na]    at com.hs.web.controller.base.AppBaseController.getTokenUser(AppBaseController.java:35) ~[classes/:na]    at com.hs.web.app.controller.AppShopCartController.listShopcart(AppShopCartController.java:66) ~[classes/:na]    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_102]    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) ~[na:1.8.0_102]    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[na:1.8.0_102]    at java.lang.reflect.Method.invoke(Unknown Source) ~[na:1.8.0_102]    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) ~[spring-web-4.3.16.RELEASE.jar:4.3.16.RELEASE]    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133) ~[spring-web-4.3.16.RELEASE.jar:4.3.16.RELEASE]

对应代码

public class AccessTokenManager {        private static AccessTokenManager instance = new AccessTokenManager();        private AccessTokenManager(){    }        public static AccessTokenManager getInstance(){        return instance;    }    public AccessToken getToken(String token){        if(!StringUtils.isBlank(token) &&             RedisUtil.exists(RedisKeySuffixEnum.USER_TOKEN.getKey() + token)){            AccessToken accessToken = (AccessToken) RedisUtil.get(RedisKeySuffixEnum.USER_TOKEN.getKey() + token);//类转换异常出现在这里            //AccessToken accessToken = convertAccessToken(RedisUtil.get(RedisKeySuffixEnum.USER_TOKEN.getKey() + token));            return accessToken;        }        return null;    }            public String putToken(String userId){        AccessToken token = new AccessToken(userId);        RedisUtil.set(RedisKeySuffixEnum.USER_TOKEN.getKey() + token.getToken(),                 token, RedisKeySuffixEnum.USER_TOKEN.getExpireTime());                return token.getToken();    }        public void updateToken(String token){        if(!StringUtils.isBlank(token) &&             RedisUtil.exists(RedisKeySuffixEnum.USER_TOKEN.getKey() + token)){            AccessToken assessToken = (AccessToken) RedisUtil.get(RedisKeySuffixEnum.USER_TOKEN.getKey() + token);            if(assessToken == null){                return;            }            RedisUtil.set(RedisKeySuffixEnum.USER_TOKEN.getKey() + token, token,                     RedisKeySuffixEnum.USER_TOKEN.getExpireTime());        }    }}

 

2-原因分析

简单来说:就是类加载机制出了问题

具体分析如下(参考:https://www.jianshu.com/p/e6d5a3969343)

1.  JVM判断两个类对象是否相同的依据:一是类全称;一个是类加载器.(具体原理请自行百度,在此不再赘述)。

2. 大家都知道虚拟机的默认类加载机制是通过双亲委派实现的。springboot为了实现程序动态性(比如:代码热替换、模块热部署等,白话讲就是类文件修改后容器不重启),“破坏或牺牲” 了双亲委派模型。springboot通过强行干预-- “截获”了用户自定义类的加载(由jvm的加载器AppClassLoader变为springboot自定义的加载器RestartClassLoader,一旦发现类路径下有文件的修改,springboot中的spring-boot-devtools模块会立马丢弃原来的类文件及类加载器,重新生成新的类加载器来加载新的类文件,从而实现热部署。比较流行的OSGI也能实现热部署)。

3-解决方案

根据原因分析,问题处在springboot热部署,那么解决问题也是从这个方面入手

方案1:关掉springboot的热部署即可(在pom中注释掉springboot的spring-boot-devtools)

方案1,简单快速有效,但本质是回避了问题,如果想用springboot热部署,这样做就无法实现热部署,如果想继续用springboot热部署,可以参考方案2。

方案2:通过反射,手动转换对应的类对象

直接上源码解决方案

package com.hs.web.common.token;import java.lang.reflect.Field;import org.apache.commons.lang.StringUtils;import org.apache.commons.lang3.reflect.FieldUtils;import com.hs.common.util.redis.RedisUtil;import com.hs.web.model.RedisKeySuffixEnum;/** * 用户Token管理工具 *  * @comment * @update */public class AccessTokenManager {        private static AccessTokenManager instance = new AccessTokenManager();        private AccessTokenManager(){    }        public static AccessTokenManager getInstance(){        return instance;    }    public AccessToken getToken(String token){        if(!StringUtils.isBlank(token) &&             RedisUtil.exists(RedisKeySuffixEnum.USER_TOKEN.getKey() + token)){            //AccessToken accessToken = (AccessToken) RedisUtil.get(RedisKeySuffixEnum.USER_TOKEN.getKey() + token);            AccessToken accessToken = convertAccessToken(RedisUtil.get(RedisKeySuffixEnum.USER_TOKEN.getKey() + token));//使用反射,进行对象转换(方法在下面)            return accessToken;        }        return null;    }            public String putToken(String userId){        AccessToken token = new AccessToken(userId);        RedisUtil.set(RedisKeySuffixEnum.USER_TOKEN.getKey() + token.getToken(),                 token, RedisKeySuffixEnum.USER_TOKEN.getExpireTime());                return token.getToken();    }        public void updateToken(String token){        if(!StringUtils.isBlank(token) &&             RedisUtil.exists(RedisKeySuffixEnum.USER_TOKEN.getKey() + token)){            AccessToken assessToken = (AccessToken) RedisUtil.get(RedisKeySuffixEnum.USER_TOKEN.getKey() + token);            if(assessToken == null){                return;            }            RedisUtil.set(RedisKeySuffixEnum.USER_TOKEN.getKey() + token, token,                     RedisKeySuffixEnum.USER_TOKEN.getExpireTime());        }    }        /**     * 反射转换:解决因类加载器不同导致的转换异常     * com.hs.web.common.token.AccessToken cannot be cast to com.hs.web.common.token.AccessToken     *      */    private AccessToken convertAccessToken(Object redisObject){        AccessToken at = new AccessToken();        at.setToken(ReflectUtils.getFieldValue(redisObject,"token")+"");        at.setUserId(ReflectUtils.getFieldValue(redisObject,"userId")+"");        return at;    }} //本类私用反射方法class ReflectUtils{ public static Object getFieldValue(Object obj, String fieldName){     if(obj == null){           return null ;       }         Field targetField = getTargetField(obj.getClass(), fieldName);              try {           return FieldUtils.readField(targetField, obj, true ) ;       } catch (IllegalAccessException e) {           e.printStackTrace();       }        return null ; }  public static Field getTargetField(Class
targetClass, String fieldName) { Field field = null; try { if (targetClass == null) { return field; } if (Object.class.equals(targetClass)) { return field; } field = FieldUtils.getDeclaredField(targetClass, fieldName, true); if (field == null) { field = getTargetField(targetClass.getSuperclass(), fieldName); } } catch (Exception e) { } return field; }}

 

 相关非核心源码

/** * Token WMS管理实体 *  * @comment * @update */public class AccessToken implements Serializable {    /**      *       */    private static final long serialVersionUID = 4759692267927548118L;    private String token;// AccessToken字符串    private String userId;        public AccessToken(){    }        public AccessToken(String userId){        this.userId = userId;//        this.token = EncryptUtil.encrypt(userId, System.currentTimeMillis() + "");        this.token = EncryptUtil.encrypt(userId);    }    public String getToken() {        return token;    }    public void setToken(String token) {        this.token = token;    }    public String getUserId() {        return userId;    }    public void setUserId(String userId) {        this.userId = userId;    }}

方案3:

在resources目录下面创建META_INF文件夹,然后创建spring-devtools.properties文件,文件加上类似下面的配置:
restart.exclude.companycommonlibs=/mycorp-common-[\w-]+.jar
restart.include.projectcommon=/mycorp-myproj-[\w-]+.jar

但是这种方法没有凑效(目前原因不明)

 

总结

  因项目发现springboot环境下相同类进行转换出现ClassCastException异常问题,分析原因,并提出两种解决方案:卸载springboot热部署,或通过反射强转类对象,从而解决问题

 

参考文献

1- https://www.jianshu.com/p/e6d5a3969343

2- https://www.cnblogs.com/ldy-blogs/p/8671863.html

转载于:https://www.cnblogs.com/wobuchifanqie/p/9908243.html

你可能感兴趣的文章
cxf+spring+数字签名开发webservice(一)
查看>>
asp.net dataset 判断是否为空 ?
查看>>
CSS div的三种结构水平垂直包含margin的计算
查看>>
计时器
查看>>
switch语句
查看>>
several useful Store Procedures in MSSQL
查看>>
对半搜索
查看>>
关于移动端弹窗内容滑动底部页面不滑动的问题
查看>>
游戏2048源代码
查看>>
TOJ3039: 材质贴图
查看>>
基于FPGA的VGA显示静态图片
查看>>
shell之脚本练习
查看>>
版本控制git之五-标签管理 tags 标签 代码版本 如: v1.0
查看>>
分享Silverlight/WPF/Windows Phone一周学习导读(1月9日-1月16日)
查看>>
rsync生产排错FAQ整理16
查看>>
Ruby和SHELL中如何遍历指定目录的文件
查看>>
NSLocalizedString 实现国际化
查看>>
迎接“云”时代的全面到来
查看>>
梭子鱼邮件归档设备配置
查看>>
论“性能需求分析”系列专题(一)之 性能需求剖析
查看>>