天天看点

类加载器之-----使用spring-boot-devtools与drools导致的 com.x.y.A cannot be cast to com.x.y.A问题由来问题分析:解决办法:

我们知道判断两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义。使用spring-boot-devtools时,我们很多“”应用类“”是由spring提供的org.springframework.boot.devtools.restart.classloader.RestartClassLoader加载,而不是以前的sun.misc.Launcher$AppClassLoader。因此肯能出现, 虽然对象的类名一样,但是却无法直接赋值或者引用。

注意:关于类加载机制,请百度或者参考《深入理解Java虚拟机》一书

问题由来

springboot程序中为了调试方便添加了热加载依赖包spring-boot-devtools。 程序中线程池进行drools规则运算并将结果返回,结果返回的结果不能直接直接使用。 例如线程池返回的是class A的对象a1, 当你直接在代码中直接对返回对象 操作时,报ClassCastException, 例如当执行A a2 = a1., 无法之下只能执行属性copy.

Caused by: java.lang.ClassCastException: com.yq.domain.User cannot be cast to com.yq.domain.User
	... 11 common frames omitted
           
A a2 =  new A()
BeanUtils.copyProperties(a1, a2);
           

问题分析:

两个都是class A但是却无法执行A a2 = a1.操作,根据类加载机制,初步判断两个类加载器不一样,加上日志发现:

完整的代码在这里,欢迎加星, fork, 谢谢!

具体代码(Runnable中打印)

@Override
    public void run() {
        long threadId = Thread.currentThread().getId();
        userId = (String) dataMap.get("userId");
        long begin = System.currentTimeMillis();

        log.debug("start for userId={}, threadId={}", userId, threadId);

        DroolsProcessor processor = new DroolsProcessor();
        List<User> list = new ArrayList<>();
        String ruleContent = "this[\"S1\"]>10";
        String head = DroolsUtils.makeRuleHead();
        String body = DroolsUtils.makeRuleBody(ruleContent);
        String fullDroolRule = head + body;
        try {
            processor.initProcessor(userId, fullDroolRule);
            dataMap.put("S1", 20);
            processor.process(dataMap, list);
        } catch (Exception ex) {
            log.error("exception. userId={}, threadId={}", userId, threadId, ex);
            return;
        }
       //drools运行产生一批User对象
        for (Object obj : list) {
            User u2 = new User();
            BeanUtils.copyProperties(obj, u2);
            log.info("obj={}", obj);
            try {
                ClassLoader classLoader = obj.getClass().getClassLoader();
                String objClassName = obj.getClass().getCanonicalName();
                if (obj instanceof User) {
                    log.info("drools uses loader={}, objClassName={}, threadId={}", classLoader, objClassName, threadId);
                } else {
                    ClassLoader userClassLoader = User.class.getClassLoader();
                    log.info("objLoader={}, userLoader={}, objClassName={},threadId={}", classLoader, userClassLoader, objClassName, threadId);
                }
            } catch (Exception ex) {
                log.debug("not the same class", ex);
            }
        }

        long end = System.currentTimeMillis();
        log.debug("end for userId={}, cost={}, threadId={}", userId, end - begin, threadId);
    }
           
类加载器之-----使用spring-boot-devtools与drools导致的 com.x.y.A cannot be cast to com.x.y.A问题由来问题分析:解决办法:

drools的bug在

https://issues.jboss.org/browse/DROOLS-1540

我现在使用的6.5.0.Final, 官方在19年5月23提交fix, 在7.23.0.Final版本包含了该fix。但是6升级到7有很多改动,原有drools代码不能直接使用

解决办法:

根据分析结果, 我们的办法有一下几种:

第一种,不使用spring-boot-devtools, 这样class A的类加载器是一样。

第二种,让线程使用RestartClassLoader

第三种,就是拷贝属性

第四种,升级drools版本(需要修改自己的drools相关代码)

第一,和第三种试验可以,但是第二种,虽然可以通过setContextClassLoader方法修改线程的累加器,但是实际测试发现,线程中的class A始终是由AppClassLoader加载的。也就是第二种目前行不通。

欢迎大家查看我的源码,如果那些设置setContextClassLoader不对,欢迎指正,谢谢!