Chen's Blog

守得云开见月明

上一篇文章Spring:简单摆弄一下SpringIoC容器简单介绍了SpringIoC容器,并创建了一个Demo项目,演示了两种配置元数据的创建方式,以及利用相应的ApplicationContext读取配置元数据初始化SpringIoC容器,最后在测试方法中启动IoC容器并从容器中根据名称、类型获取Bean的过程。本篇总结一下如何在配置元数据中配置Bean的实例化方式以及Bean所需的外部依赖注入方式(依赖输入),换句话说,以哪些方式告诉SpringIoC容器实例化Bean的方法,以及Bean所需的外部依赖如何注入到Bean中。

阅读全文 »

Spring框架本身实现了一套完整的IoC容器模型和依赖注入过程,在Spring框架的源码中org.springframework.beansorg.springframework.context包是Spring框架的基础,BeanFactory接口则是IoC的核心,它被描述为抽象的IoC容器,可以管理所以类型的Java对象,它定义了如何管理和配置IoC容器中的对象(例如:根据名称获取对象等)。我们常用的ApplicationContext是其子类,他完全实现了BeanFactory的所有功能,并且他还增加更多企业级特定的功能,例如:事件发布/监听机制、国际化等,在WEB应用程序生态中更是引入了WebApplicationContext提供全面的WEB应用程序开发支持。

阅读全文 »

Spring是一套完整的Java企业级开发框架,其内部完整的实现了一套IoC容器体系和面向切面编程(AOP)特性,可以基于SpringIoC特性完成不同框架之间的完美整合,加之轻量级的特点和完善的社区支持,目前仍是使用最多的Java开发框架。

阅读全文 »

当使用SpringMVC开发项目时,经常会出现对请求参数处理的困扰,例如:字符串前后端空格、前后端需要约定日期时间的传递方式、返回等。诸多问题我们的解决方式通常是前后端通过约定好统一的数据格式来保证系统暂时的稳定,或通过哪里出错改哪里的笨拙方式处理,这些都是不可取的。通过这篇文章,希望可以以优雅的方式彻底解决这个问题。

阅读全文 »

问题现象

对类级别采用Lombok注解@Data(图省事儿,代码还简洁)。

该注解可以为对象提供属性的访问器、toString等方法。详细可以了解Lombok

问题在于,类A与类B存在继承关系时,在调用toString、Equals以及HashCode等方法时,无法自动调用父类。代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Data
public class A {
private String name;
}

@Data
public class B extends A{
private Integer age;
}

// ~ 测试代码(简略)
B b = new B();
b.setAge(11);
b.setName("小强");
System.out.println(b.toString()); // out: B(age=11)

对象b的输出结果只有age属性,why?

此问题在于,Lombok的@Data注解自动生成的toString方法并不支持调用父类方法,需要手动设置调用父类的标记

1
2
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)

这两个注解定义在类级别上,显示声明对于toString、equals和hashCode方法自动调用父类

再次执行单元测试,完美解决。

异常现象

在使用Springboot 配合 Redis做缓存处理的时候,单元测试中对象的序列化和反序列化操作均正常,但是项目Runing后,接口操作出现类型转换异常,最可笑的是同一类型转换出了错😄。

java.lang.ClassCastException: OauthCodeRedisCacheEntity cannot be cast to OauthCodeRedisCacheEntity

image

解决思路

第一、我排查了一下单元测试和实际API接口的代码逻辑是否相同,然而是相同的。

第二、通过Debug模式检查了一下单元测试和实际API接口对本系统下Redis反序列化方法入参的参数值,发现也均值一样的

第三、先后执行单元测试和实际API,查看Redis缓存中的数据是否相同,经检验也是相同的。

第四、我考虑到是否是因多线程引起的,所以对调用该序列化方法的Service层进行了并发控制,这里仅加了可重入锁。然而并没有成功

第五、MDZZ,心态爆炸,歇了一会,和媳妇做做饭、收拾收拾家,开心

第六、Google 搜索关键词“Springboot Redis 反序列化类型转换失败” …. 未果,看来中文search不行

第七、Google 直接粘贴报错信息。 果然,SF大法好

刨根问底

When you use DevTools with caching, you need to be aware of this limitation.
When the object is serialized into the cache, the application class loader is C1. Then after you change some code/configuration, devtools automatically restart the context and creates a new classloader (C2). When you hit that cache method, the cache abstraction finds an entry in the cache and it deserializes it from the store. If the cache library doesn’t take the context classloader into account, that object will have the wrong classloader attached to it (which explains that weird exception A cannot be cast to A).

上面这段话是Stack Overflow社区一大佬就此问题的回答,大意是说,当使用SpringBoot 的 DevTools时,其实该工具是具有缓存效果的,这点需要注意,而且该大佬也提供的注意事项的连接地址 this limitation.

当对象被序列化到缓存里时,当前应用的类加载器是C1,当你改变了一些代码或者配置文件的时候,DevTools 工具将会自动重新启动这个容器,并且创建一个新的类加载器 C2. 这时候调用这个具有缓存的方法时,缓存管理将会从缓存里找到该条缓存记录并进行反序列化操作。如果缓存库不考虑上下文的话,也就是没注意到类加载器的变化时,该对象将会有错误的类加载器(这解释了奇怪的异常)。

其实就是因上下文类加载器不同而产生这样的错误,那么归根结底就是因SpringBoot DevTools工具搞的鬼。

果然,在项目配置初期,为了实现所谓的热部署和热加载使用了该工具库,果断删掉。

Perfect!!

总结

但凡遇到报错信息,切记不要慌不要乱,有因才有果,可以吓你根据自己的分析按照自己的想法进行错误排查,针对出现的类型转换出现的异常,首先应该想到是不是自己代码的问题,因为此次错误我在单元测试中是可用的,所以业务代码层面不会出现问题。

尽量把自己能想到 的解决方式全都用一遍,因为此时你对这个报错已经有了大题的了解,这个了解仅仅是利用排除法了解到的,即 他不是因为什么什么而发生。所以在接下来使用搜索引擎的时候,可以避免走一些弯路。

自己能想到的解决方法都用了之后,建议直接Google 报错信息,简单粗暴。逐条查看,按照说明处理。

当然最重要的还是心态,我在掏空自己的想法之后,果断选择去和小迷妹聊会天,解放一下思路,放松一下紧绷的神经。当然,在放松之余,我也在想这个问题,只是会更放松。

总而言之,言而总之,所有的程序员们,当你写代码遇到问题遇到Bug,起立,做点别的事情去

某天,你修改了一大堆文件,这时候Leader告诉你部分功能舍弃,恰好你写的这些文件就是丢弃功能中的文件,这时候该怎么办呢?这就要看看当时的状态,分为四种情况。

  1. 工作区的修改未被添加到暂存区
  2. 工作区的修改以被添加到暂存区后,再次修改改文件,但此次修改未添加到暂存区
  3. 基于第一种情况下,暂存区的修改已经全部被提交到本地版本库
  4. 基于第二种情况下,本地版本库已经被推送到远程仓库
    下面是对以上三种情况的详细介绍

第一种:修改未添加到暂存区

对某个文件进行修改后,但未将修改的文件添加到暂存区,这时想要恢复该文件到原始状态。需要使用git checkout -- 文件名命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
chenzhihao-mac:gitlearning chenzhihao$ cat readme.md
Hello world!
Git is a very important Code Management System!
chenzhihao-mac:gitlearning chenzhihao$ vi readme.md
chenzhihao-mac:gitlearning chenzhihao$ cat readme.md
Hello world!
Git is a very important Code Management System!

Test test test test
chenzhihao-mac:gitlearning chenzhihao$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)

modified: readme.md

no changes added to commit (use "git add" and/or "git commit -a")
chenzhihao-mac:gitlearning chenzhihao$ git checkout -- readme.md
chenzhihao-mac:gitlearning chenzhihao$ git status
On branch master
nothing to commit, working tree clean
chenzhihao-mac:gitlearning chenzhihao$ cat readme.md
Hello world!
Git is a very important Code Management System!

第二种:修改暂存区内已有的文件,但本次修改未提交

当我们已经将修改的文件添加到了暂存区,或对已提交到暂存区中的文件进行二次修改时,这时,如果感觉该文件的不应该进行此修改,需要撤销修改。那该怎么做呢?
首先,使用git reset命令,将某个文件恢复到某个版本,此时文件的修改已经从暂存区删除,但工作区中的文件还是处于修改的状态,这时就回到了第一种情况,可以使用git checkout -- 文件名命令将文件恢复到修改前的状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
chenzhihao-mac:gitlearning chenzhihao$ vi readme.md
chenzhihao-mac:gitlearning chenzhihao$ cat readme.md
Hello world!
Git is a very important Code Management System!

Test
chenzhihao-mac:gitlearning chenzhihao$ git add readme.md
chenzhihao-mac:gitlearning chenzhihao$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

modified: readme.md

chenzhihao-mac:gitlearning chenzhihao$ git reset head readme.md
Unstaged changes after reset:
M readme.md
chenzhihao-mac:gitlearning chenzhihao$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)

modified: readme.md

no changes added to commit (use "git add" and/or "git commit -a")
chenzhihao-mac:gitlearning chenzhihao$ cat readme.md
Hello world!
Git is a very important Code Management System!

Test
chenzhihao-mac:gitlearning chenzhihao$ git checkout -- readme.md
chenzhihao-mac:gitlearning chenzhihao$ cat readme.md
Hello world!
Git is a very important Code Management System!
chenzhihao-mac:gitlearning chenzhihao$ git status
On branch master
nothing to commit, working tree clean

暂存区已被提交到本地版本库

对于修改已经被提交到本地代码库,直接使用上一篇【Git爬坡】后悔药之版本回退 文章讲过的内容进行版本回退。

第四种:本地版本库已被推送到远程仓库

恭喜你,出门右拐,财务领工资

总结

针对丢弃修改这个问题的四种情况:

  • 工作区的修改未被添加到暂存区。使用 git checkout -- 文件名 命令
  • 工作区的修改以被添加到暂存区后,再次修改改文件,但此次修改未添加到暂存区。使用 git reset 命令,先丢弃暂存区,然后使用1中的命令撤销对文件的修改
  • 基于第一种情况下,暂存区的修改已经全部被提交到本地版本库。使用版本回退即可解决,详情查看【Git爬坡】后悔药之版本回退
  • 基于第二种情况下,本地版本库已经被推送到远程仓库。哎,祝你好运
0%