saturn java 热加载(二)资源文件 spring & logback

全文高亮

 

背景:https://www.cnblogs.com/silyvin/articles/10274914.html  文章中的方法,加载普通类是没有问题,但是在初始化spring时:

final ApplicationContext context = new ClassPathXmlApplicationContext("classpath*:application.xml");

读不到内容:

No bean named 'dataSourceFast' is defined

 

ClassPathXmlApplicationContext会从classpath中读取jar包中的资源(*表示遍历读取所有jar包,https://www.cnblogs.com/silyvin/p/10343131.html),当前线程的类加载器打印为:

		URLClassLoader loader = (URLClassLoader)Thread.currentThread().getContextClassLoader();
		logger.info("{}", loader);
		URL[] urls = loader.getURLs();
		if(urls != null) {
			for(URL url : urls) {
				logger.info(url.getPath());
			}
		}

 

com.vip.saturn.job.executor.JobClassLoader@7c2db6b2,仍然是saturn的类加载器

ClassPathXmlApplicationContext很有可能是调用当前线程的类加载器环境下的classpath(即所有预加载jar包路径)去寻找

/data/saturn-2/saturn-executor-3.0.1/bin中的jar包均被打印出来,显然里面并没有我们此次热加载的包,

所以遍历了所有jar包也读不到application.xml,同样的情况也适用于logback

 

spring在加载资源时,有3+1个类加载器可选择

当前函数所在类的类加载器,这里是拟被热加载的类所在自定义类加载器jds classloader

当前线程指定类加载器,未显式指定的情况下,这里是父线程的类加载器saturn的classloader[spring读取时使用]

被引用类ClassPathXmlApplicationContext类实际被加载的类加载器,该类是通过loadclass加载的,其类加载器必然是引用类加载器及其父加载器,这里是jds classloader的父加载器 saturn的classloader

+1 调用当前函数的代码所在类加载器

 更详细的概念两种类别的类加载器

new操作时调用当前线程的类加载器,还是调用方的类加载器系列3篇文章后来证明了第1类与第3类相同

 

此外:

WxPreEnterpriseJob loader jds.fast.job.MyUrlClassLoader@31068f8a
[INFO] 2019-01-31 16:33:09 326 [com.jds.PreWxEnterprise.WxPreEnterpriseJob] [Saturn-wx_pre_enterprise_daily-29-thread-6] (WxPreEnterpriseJob.java:29) -> WxPreEnterpriseDataServer loader jds.fast.job.MyUrlClassLoader@31068f8a

 

我们可以看到,被热加载的类WxPreEnterpriseJob及其引用类WxPreEnterpriseDataServer都是自定义加载器加载的,而不是当前线程类加载器

 

 

 

另一方面,通过ResourceLoader.getClassLoader函数,

    /**
     * Expose the ClassLoader used by this ResourceLoader.
     * <p>Clients which need to access the ClassLoader directly can do so
     * in a uniform manner with the ResourceLoader, rather than relying
     * on the thread context ClassLoader.
     * @return the ClassLoader (only {@code null} if even the system
     * ClassLoader isn't accessible)
     * @see org.springframework.util.ClassUtils#getDefaultClassLoader()
     */
    ClassLoader getClassLoader();

 https://www.jianshu.com/p/afbb1132711a

源码定位到:spring的ClassUtils

    ClassLoader getDefaultClassLoader()

该方法用于获取默认的类加载器;方法功能很明确,平时也用的比较多,可能平时我们要获取类加载器,就一个class.getClassLoader()就完了,我们来看看spring是如何考虑的:

    public static ClassLoader getDefaultClassLoader() {
        ClassLoader cl = null;
        try {
            //获取当前线程的context class loader
            cl = Thread.currentThread().getContextClassLoader();
        }catch (Throwable ex) {
        }
        if (cl == null) {
            // 如果没有context loader,使用当前类的类加载器;
            cl = ClassUtils.class.getClassLoader();
            if (cl == null) {
                // 如果当前类加载器无法获取,获得bootstrap ClassLoader
                try {
                    cl = ClassLoader.getSystemClassLoader();
                } catch (Throwable ex) {
                }
            }
        }
        return cl;
    }

代码很简单,按照获取当前线程上下文类加载器—>获取当前类类加载器—>获取系统启动类加载器的顺序来获取;

1,通过Thread.getContextClassLoader()方法获取到的是线程绑定的类加载器,这个classloader是父线程在创建子线程的时候,通过Thread.setContextClassLoader()方法设置进去,用于该线程加载类和资源的,如果没有调用这个方法,那么直接使用父线程的classLoader;如果这个方法返回null,代表该线程直接使用的系统class loader或者bootstrap class loader;
--------------------- 
原文:https://blog.csdn.net/wolfcode_cn/article/details/80660552  

 

 

果然如此,问题找到了,我们要做的,就是开一个线程,设置它,ProxyJdsJavaJob中,改为:

    MyUrlClassLoader myUrlClassLoader = new MyUrlClassLoader(urls2);
            LOGGER.info("saturn 传入 java job 类名:{}", temp[1]);
            // loadclass 也可以
         //   Class CA = myUrlClassLoader.loadClass(temp[1]);
            Class CA = myUrlClassLoader.findClass(temp[1]);
            abstractJdsJavaJob = (AbstractJdsJavaJob)CA.newInstance();

            Callable callable = new SubTask(abstractJdsJavaJob, jobName, shardItem, shardParam);
            FutureTask futureTask = new FutureTask(callable);
            Thread thread = new Thread(futureTask);
            thread.setContextClassLoader(myUrlClassLoader);
            thread.start();
            futureTask.get();

            LOGGER.info("success:{},{},{}", jobName, shardItem, shardParam);

 

输出:

 

 

ok 比较遗憾的是,logback未输出到正确的路径,因为

1)logback在当前类加载器下寻找logback.xml(两种类别的类加载器(其实是4种)【重点】第1种),而spring在当前线程类加载器下(第3种)

2)logback是静态的,所以不能放在上层classpath,spring是new的

如果需要logback隔离,则logback 的jar包也需要从上层classpath移出来,到我们热加载的路径下

 

 

2020.1.8

关于spring一律使用当前线程的类加载器https://blog.csdn.net/yangcheng33/article/details/52631940文中有详细描述

 spring根本不会去管自己被放在哪里,它统统使用TCCL来加载类,而TCCL默认设置为了WebAppClassLoader,也就是说哪个WebApp应用调用了spring,spring就去取该应用自己的WebAppClassLoader来加载bean,简直完

posted on 2019-01-31 15:15  silyvin  阅读(429)  评论(0编辑  收藏  举报