项目总结10:通过反射解决springboot环境下从redis取缓存进行转换时出现ClassCastException异常问题
通过反射解决springboot环境下从redis取缓存进行转换时出现ClassCastException异常问题
关键字
springboot热部署 ClassCastException异常 反射 redis
前言
正文
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)
- <!-- spring boot 的调试模块 -->
- <!-- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-devtools</artifactId>
- <optional>true</optional>
- </dependency> -->
方案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
项目总结10:通过反射解决springboot环境下从redis取缓存进行转换时出现ClassCastException异常问题的更多相关文章
- Windows环境下启动Redis报错:Could not create server TCP listening socket 127.0.0.1:6379: bind: 操作成功完成。(已解决)
问题描述: 今天在windows环境下启动Redis时启动失败报错: 解决方案: ①运行命令:redis-cli.exe ②退出Redis ③运行命令:redis-server.exe redis.w ...
- centos / Linux 服务环境下安装 Redis 5.0.3
原文:centos / Linux 服务环境下安装 Redis 5.0.3 1.首先进入你要安装的目录 cd /usr/local 2.下载目前最新稳定版本 Redis 5.0.3 wget http ...
- Ubuntu环境下的Redis 配置与C++使用入门
Redis是一个高性能的key-value数据库. Redisedis的出现,非常大程度补偿了memcached这类key/value存储的不足,在部分场合能够对关系数据库起到非常好的补充作用.它 ...
- 在windows环境下安装redis和phpredis的扩展
在windows环境下安装redis和phpredis的扩展 1.首先配置php: 需要在windows的集成环境中找到php的扩展文件夹,ext,然后在网上寻找自己的php对应的.dll文件 比如说 ...
- 在linux环境下安装redis并且搭建自己的redis集群
此文档主要介绍在linux环境下安装redis并且搭建自己的redis集群 搭建环境: ubuntun 16.04 + redis-3.0.6 本文章分为三个部分:redis安装.搭建redis集群 ...
- Linux环境下安装Redis
记录一下Linux环境下安装Redis,按顺序执行即可,这里下载的是Redis5,大家可根据自己的需求,修改版本号就好了,亲测可行. 1.下载Redis安装包cd /usr/local/wget ht ...
- Springboot使用Shiro-整合Redis作为缓存 解决定时刷新问题
说在前面 (原文链接: https://blog.csdn.net/qq_34021712/article/details/80774649)本来的整合过程是顺着博客的顺序来的,越往下,集成的越多,由 ...
- 解决中文环境下zabbix监控图形注释乱码
zabbix监控的图形界面能够更直观的查看监控状态,当我们把zabbix的语言切换为中文的时候,会发现监控图形中一些中文参数会乱码,例如下面的效果 但是图形界面在原生的英文环境下完全没有乱码问题.为了 ...
- 解决win10环境下python Selenuim调用Chrome时提示data 及Chrome正在受自动软件控制的方法
用python自动访问谷歌浏览器时会出现data界面,很是烦人.在网上搜索,有说是因为webdriver和google版本不匹配导致的,就下过各种版本,结果都一样. 后来明白了,出现data的原因只是 ...
随机推荐
- 在Ubuntu下利用Eclipse调试FFmpeg《转》
参考原贴,其中编译命令有略微改动. 第一步:准备编译环境 #sudoapt-get update #-dev libspeex-dev libtheora-dev libtool libva-dev ...
- DJango之视图函数
一)Django WEB框架 2) request.path和request.get_full_path() 是请求的路径 3)render:页面渲染 4)redirect:页面跳转 3)模板语法: ...
- Apache tica详述
Tika是一个内容抽取的工具集合(a toolkit for text extracting).它集成了POI, Pdfbox 并且为文本抽取工作提供了一个统一的界面.其次,Tika也提供了便利的扩展 ...
- 自定义 mapper的实现
json格式,要想好看直接百度,json,将字符放进去就可 一步:将mapper复制一份,名字加一个Custom自定义 二步:mpper.xml也是一样,设置里面的namespace映射关系 自定义m ...
- HTML框架、列表、表格
本章内容一.列表1.有序列表ol <ol> <li></li> </ol>type的值有3个 默认为1(阿拉伯数字), 还有A/a(大小写字母),I/i ...
- 认识bash和shell
各个 shell 的功能都差不多, Linux 默认使用 bash ,所以我们主要学习bash的使用. 1.bash命令格式 命令 [-options] [参数],如:tar zxvf demo. ...
- python小数据池概念以及具体范围
= 赋值符号: == 比较值是否相等: is 比较,比较的是内存地址 ID(内容) 数字,字符串的小数据池 小数据池现象产生的原因,作用: 为了节省内存空间. &l ...
- 2018面向对象程序设计(Java)第15周学习指导及要求
2018面向对象程序设计(Java)第15周学习指导及要求 (2018.12.6-2018.12.9) 学习目标 (1) 掌握Java应用程序打包操作: (2) 了解应用程序存储配置信息的两种方法 ...
- “2017面向对象程序设计(Java)第就九周学习总结”存在问题的反馈
对于“2017面向对象程序设计(Java)第就九周学习总结”存在问题的反馈 1.博文未写者:高树平 高俊梅 冯小丽 缪召召 王瑞强 宗鹏新 李向龙 马润韬 米奇辉 卯保云——不及时提交博客的同学人数出 ...
- Java将一个字符串的首位改为大写后边改为小写的实现,String
Java将一个字符串的首位改为大写后边改为小写的实现,String 思路: 获取首字母, charAt(0) substring(0,1) 转成大写 toUpperCase() 转大写hellO=== ...