热部署引发的类型转换异常
起因
主要公司有个老的项目,想合并,里面有个工作流,里面需要配置监听器,然后监听器需要用到spring管理的其他的类,然后如果用@Component,@Autowired来注入的话,项目启动会报找不到这个类的问题,然后我以为是有同名类,导致注入不进去,所以我改用@Component(“”)来声明,注入的时候,也用@Resource(name =
"kaptchaProducer"``),但是注入的bean的时候,会报类型转换异常。
既然不让我用spring的主动注入,那我就用SpringContextUtils,注入ApplicationContext来获取bean。
但是就出现了奇怪的问题。
类型转换异常
这个框架用的是一个开源的框架,里面自己写了springholder,然后我用这个工具类,去拿类的时候,报了个类型转换异常,一个代理类不能转换为原类,emmm,我以为是因为用的cglib的动态代理,不是jdk的动态代理,导致转换异常,然后我就把注入的这个类实现了接口,然后我用接口来接,emm,还是转换异常,没办法了,是不是这个工具类有问题,我用SpringContextUtils自己的工具类试试。
空指针问题
如果我把需要注入的bean当做属性来管理的话,
*/
@Slf4j
@Data
public class BusinessRejectListener extends BusinessTaskBasicListener {
private ActTaskService actTaskService = (ActTaskService) SpringContextUtils.getBean(ActTaskService.class);
这里的时候,报了个空指针问题,emmm。我的理解是,这个监听器的加载时间是在类的加载的时候,而SpringContextUtils的注入,是在容器初始化后再注入容器的,导致空指针问题。
那么我就用到的地方在再来使用。
private void copyAct(String businessKey) {
ActTaskService actTaskService = (ActTaskService) SpringContextUtils.getBean(ActTaskService.class);
actTaskService.copyAct(businessKey);
}
妈的,还是报空指针问题,是我没注入ApplicationContext吗。
debug一下。
SpringContextUtils有两种写法,一种是实现ApplicationContextAware ,然后注入。一种是利用springboot的启动方法注入。
public static void main(String[] args) {
SpringApplication app = new SpringApplication(ActivityApplication.class);
ConfigurableApplicationContext ctx = app.run(args);
SpringContextUtils.setApplicationContext(ctx);
}
我debug一看,更奇怪了,注入进来了。可是一到用的地方,又是空指针。为啥啊
结果
一下子,陷入了死局,为什么已经注入了属性,可是为什么到具体使用的时候,里面的属性是又为空了,是又被注入了null?不对啊,debug的时候,只有一次。
那为什么呢。莫非这两个类不是同一个类,我用反射去拿下这个类的静态属性看看。
https://blog.csdn.net/huangshanchun/article/details/78308833
private static List<String> getStaticField() throws Exception {
List<String> result = new ArrayList<String>();
Field[] fields = Test.class.getDeclaredFields();
if (fields == null || fields.length <= 0) {
return result;
}
for (Field field : fields) {
field.setAccessible(true);
//只获取字符串类型
if (field.getType() == String.class && Modifier.isStatic(field.getModifiers())) {
result.add(String.valueOf(field.get(Test.class)));
}
}
return result;
}
emm。还是为空。那为什么呢。问题是,jvm如何确定一个类是一个类的呢。我记起来了,jvm是通过全限定类名+classloader来确定一个类是一个类,这样同名同包类不会被当做一个类。然后jdk的类也不会因此而被覆盖。
那是不是这两个类的加载器不同。
然后注意了下项目启动时加载项目中的SpringContextUtils类使用的加载器都是
org.springframework.boot.devtools.restart.classloader.RestartClassLoader
而从监听器取出来的对象的类加载器都是
sun.misc.Launcher.AppClassLoader。
问题解决了,从类加载就能看出来,org.springframework.boot.devtools.restart.classloader.RestartClassLoader是热部署的类加载器。然后百度一看,大家都遇到过这个问题,SpringBoot使用devtools导致的类型转换异常
https://blog.csdn.net/qq_35893120/article/details/85161091?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-3&spm=1001.2101.3001.4242
那么问题就很清楚了,这是因为启用热部署之后,启动时使用的类加载器和重新加载使用的类加载器不一样导致的。
那么去掉热部署的pom,问题就解决了。
那么之前的问题就都能解释了。为什么会空指针,会类型转换异常,会注入失败。都是这个类加载器不同的原因,监听器的加载器都是默认的加载器,而我们写的类,都是被热部署插件自己的加载器加载的,所以自己类注入没问题,到监听器注入,获取类都失败了。