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,简直完