当前位置: 首页 > 工具软件 > Saturn > 使用案例 >

java中的saturn_saturn java 热加载(一)

萧允晨
2023-12-01

背景:

每次启用都要重启executor,在此之前还要确保该executor下没有任务在执行,很麻烦

方案:

将不常更换的公共包放在executor classpath下,仍然由saturn加载

将常变动的包放在executor类加载器可见范围外,使其不能加载

public class ProxyJdsJavaJob extends AbstractSaturnJavaJob {

private static final Logger LOGGER = LoggerFactory.getLogger(ProxyJdsJavaJob.class);

@Override

public SaturnJobReturn handleJavaJob(final String jobName, final Integer shardItem, final String shardParam, final SaturnJobExecutionContext context) {

LOGGER.info("saturn 传入 java job 文件路径及参数:{}", shardParam);

AbstractJdsJavaJob abstractJdsJavaJob = null;

try {

String [] temp = shardParam.split(";");

String dir = "file:"+temp[0];

LOGGER.info("saturn 传入 java job 文件路径:{}", temp[0]);

URL url = new URL(dir);

URL[] urls2 = {url};

MyUrlClassLoader myUrlClassLoader = new MyUrlClassLoader(urls2);

LOGGER.info("saturn 传入 java job 类名:{}", temp[1]);

// Class CA = myUrlClassLoader.loadClass(temp[1]);

Class CA = myUrlClassLoader.findClass(temp[1]);

abstractJdsJavaJob = (AbstractJdsJavaJob)CA.newInstance();

abstractJdsJavaJob.runJdsJob(jobName, shardItem, shardParam);

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

return new SaturnJobReturn(Common.createJobResString("", jobName, shardItem, shardParam, true));

} catch (Exception e) {

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

LOGGER.error(ExceptionUtils.getStackTrace(e));

new SendMailThread(new Date(), e, jobName, shardItem, shardParam).execute();

throw new JdsFastJobException(e);

} catch (Throwable throwable) {

LOGGER.error(throwable.getMessage(), throwable);

throw new JdsFastJobException("类加载器失败");

} finally {

if(abstractJdsJavaJob != null)

abstractJdsJavaJob.release();

}

}

}

public class MyUrlClassLoader extends URLClassLoader {

private static final Logger LOGGER = LoggerFactory.getLogger(MyUrlClassLoader.class);

public MyUrlClassLoader(URL[] urls) {

super(urls, Thread.currentThread().getContextClassLoader());

// 父加载器不一定是系统类加载器,可能是saturn自定义加载器

ClassLoader parent = Thread.currentThread().getContextClassLoader();

LOGGER.info("parent class loader {}", parent);

}

@Override

protected Class> findClass(final String name) throws ClassNotFoundException {

return super.findClass(name);

}

}

1. 如果待热加载jar包(后称为A)在executor类加载器可见范围内,必须使用findclass避开缓存

如果在可见范围外,loadclass与findclass都可以,但请注意:

findclass(A),A中引用B,那么jvm使用loadclass加载B,这就意味着,如果B在父加载器缓存中,则不会热加载

所以如果B也要热加载,则1)将B也放到主程序类加载可见范围外,2)在A中使用findclass避开主类加载器缓存,当然同时也打破双亲委派

2. 这里有个坑,executor的类加载器并不是AppClassLoader,日志显示

(MyUrlClassLoader.java:21) -> parent class loader com.vip.saturn.job.executor.JobClassLoader@5fd764ee

所以,自定义类加载器必须指定parent class loader为saturn的类加载器,而不是使用默认null作为参数,这将导致AppClassLoader作为父加载器,否则

AbstractJdsJavaJob是由saturn类加载器加载

而具体的被热加载的类继承AbstractJdsJavaJob,由自定义类加载器加载,缺省情况下,其parent为系统类加载器,导致自定义加载器和saturn类加载器变为平行关系,saturn加载的类,对我们要热加载的类不可见,从而抛出error

3. 永远要确保finally中的语句是安全的,因为如果它抛异常,将覆盖catch中所有代码

详细见:https://www.cnblogs.com/silyvin/p/9993458.html

4. 由第2点抛出的异常,catch Exception是catch不到的,详细见:https://www.cnblogs.com/silyvin/p/10274898.html

故使用了catch Throwable,成功抓取

[INFO] 2019-01-15 17:26:43 985 [jds.fast.job.ProxyJdsJavaJob] [Saturn-test_ProxyJdsJavaJob-24-thread-1] (ProxyJdsJavaJob.java:30) -> saturn 传入 java job 类名:saturn.DemoJob

[ERROR] 2019-01-15 17:26:43 987 [jds.fast.job.ProxyJdsJavaJob] [Saturn-test_ProxyJdsJavaJob-24-thread-1] (ProxyJdsJavaJob.java:45) -> jds/fast/job/AbstractJdsJavaJob

java.lang.NoClassDefFoundError: jds/fast/job/AbstractJdsJavaJob

at java.lang.ClassLoader.defineClass1(Native Method)

at java.lang.ClassLoader.defineClass(ClassLoader.java:763)

at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)

at java.net.URLClassLoader.defineClass(URLClassLoader.java:468)

at java.net.URLClassLoader.access$100(URLClassLoader.java:74)

at java.net.URLClassLoader$1.run(URLClassLoader.java:369)

at java.net.URLClassLoader$1.run(URLClassLoader.java:363)

at java.security.AccessController.doPrivileged(Native Method)

at java.net.URLClassLoader.findClass(URLClassLoader.java:362)

at jds.fast.job.MyUrlClassLoader.findClass(MyUrlClassLoader.java:17)

at jds.fast.job.ProxyJdsJavaJob.handleJavaJob(ProxyJdsJavaJob.java:32)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.lang.reflect.Method.invoke(Method.java:498)

at com.vip.saturn.job.java.SaturnJavaJob$1.internalCall(SaturnJavaJob.java:218)

at com.vip.saturn.job.basic.AbstractSaturnJob$JobBusinessClassMethodCaller.call(AbstractSaturnJob.java:205)

at com.vip.saturn.job.java.SaturnJavaJob.handleJavaJob(SaturnJavaJob.java:214)

at com.vip.saturn.job.java.SaturnJavaJob.doExecution(SaturnJavaJob.java:204)

at com.vip.saturn.job.basic.JavaShardingItemCallable.call(JavaShardingItemCallable.java:158)

at com.vip.saturn.job.basic.ShardingItemFutureTask.call(ShardingItemFutureTask.java:88)

at com.vip.saturn.job.basic.ShardingItemFutureTask.call(ShardingItemFutureTask.java:17)

at java.util.concurrent.FutureTask.run(FutureTask.java:266)

at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)

at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)

at java.lang.Thread.run(Thread.java:748)

Caused by: java.lang.ClassNotFoundException: jds.fast.job.AbstractJdsJavaJob

at java.net.URLClassLoader.findClass(URLClassLoader.java:382)

at jds.fast.job.MyUrlClassLoader.findClass(MyUrlClassLoader.java:17)

at java.lang.ClassLoader.loadClass(ClassLoader.java:424)

at java.lang.ClassLoader.loadClass(ClassLoader.java:357)

... 26 common frames omitted

5 7.13补充:

public class MyUrlClassLoader extends URLClassLoader {

private static final Logger LOGGER = LoggerFactory.getLogger(MyUrlClassLoader.class);

public MyUrlClassLoader(URL[] urls) {

super(urls, Thread.currentThread().getContextClassLoader());

// 父加载器不一定是系统类加载器,可能是saturn自定义加载器

ClassLoader parent = Thread.currentThread().getContextClassLoader();

LOGGER.info("parent class loader {}", parent);

ClassLoader grandParent = parent.getParent();

LOGGER.info("grand class loader {}", grandParent);

}

@Override

protected Class> findClass(final String name) throws ClassNotFoundException {

return super.findClass(name);

}

@Override

protected void finalize() throws Throwable {

LOGGER.info("回收MyUrlClassLoader");

super.finalize();

}

}

grandParent 居然是null,这也导致了与以premain方式注入监控的jmx_exporter所在的系统类加载器相互不可见:https://www.cnblogs.com/silyvin/articles/10600530.html

2019.12.26

这篇文章能干啥

1)任务隔离

2)热加载

3)加解密

4)任务状态统一记录

 类似资料: