PowerJob的OhMyClassLoader工作流程源碼解讀
序
本文主要研究一下PowerJob的OhMyClassLoader
OhMyClassLoader
tech/powerjob/worker/container/OhMyClassLoader.java
@Slf4j
public class OhMyClassLoader extends URLClassLoader {
public OhMyClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
/**
* 主動(dòng)加載類,否則類加載器內(nèi)空空如也,Spring IOC容器初始化不到任何東西
* @param packageName 包路徑,主動(dòng)加載用戶寫的類
* @throws Exception 加載異常
*/
public void load(String packageName) throws Exception {
URL[] urLs = getURLs();
for (URL jarURL : urLs) {
JarFile jarFile = new JarFile(new File(jarURL.toURI()));
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry jarEntry = entries.nextElement();
if (jarEntry.isDirectory()) {
continue;
}
String name = jarEntry.getName();
if (!name.endsWith(".class")) {
continue;
}
// 轉(zhuǎn)換 org/spring/AAA.class -> org.spring.AAA
String tmp = name.substring(0, name.length() - 6);
String res = StringUtils.replace(tmp, "/", ".");
if (res.startsWith(packageName)) {
loadClass(res);
log.info("[OhMyClassLoader] load class({}) successfully.", res);
}
}
}
}
}OhMyClassLoader繼承了URLClassLoader,它定義了load方法,遍歷urls,挨個(gè)根據(jù)url創(chuàng)建JarFile,然后遍歷jarFile.entries(),找到.class結(jié)尾的entry,判斷是否是packageName開頭的,是則執(zhí)行父類的loadClass方法
OmsJarContainer
tech/powerjob/worker/container/OmsJarContainer.java
@Slf4j
public class OmsJarContainer implements OmsContainer {
private final Long containerId;
private final String name;
private final String version;
private final File localJarFile;
private final Long deployedTime;
// 引用計(jì)數(shù)器
private final AtomicInteger referenceCount = new AtomicInteger(0);
private OhMyClassLoader containerClassLoader;
private ClassPathXmlApplicationContext container;
private final Map<String, BasicProcessor> processorCache = Maps.newConcurrentMap();
public OmsJarContainer(Long containerId, String name, String version, File localJarFile) {
this.containerId = containerId;
this.name = name;
this.version = version;
this.localJarFile = localJarFile;
this.deployedTime = System.currentTimeMillis();
}
@Override
public void init() throws Exception {
log.info("[OmsJarContainer-{}] start to init container(name={},jarPath={})", containerId, name, localJarFile.getPath());
URL jarURL = localJarFile.toURI().toURL();
// 創(chuàng)建類加載器(父類加載為 Worker 的類加載)
this.containerClassLoader = new OhMyClassLoader(new URL[]{jarURL}, this.getClass().getClassLoader());
// 解析 Properties
Properties properties = new Properties();
try (InputStream propertiesURLStream = containerClassLoader.getResourceAsStream(ContainerConstant.CONTAINER_PROPERTIES_FILE_NAME)) {
if (propertiesURLStream == null) {
log.error("[OmsJarContainer-{}] can't find {} in jar {}.", containerId, ContainerConstant.CONTAINER_PROPERTIES_FILE_NAME, localJarFile.getPath());
throw new PowerJobException("invalid jar file because of no " + ContainerConstant.CONTAINER_PROPERTIES_FILE_NAME);
}
properties.load(propertiesURLStream);
log.info("[OmsJarContainer-{}] load container properties successfully: {}", containerId, properties);
}
String packageName = properties.getProperty(ContainerConstant.CONTAINER_PACKAGE_NAME_KEY);
if (StringUtils.isEmpty(packageName)) {
log.error("[OmsJarContainer-{}] get package name failed, developer should't modify the properties file!", containerId);
throw new PowerJobException("invalid jar file");
}
// 加載用戶類
containerClassLoader.load(packageName);
// 創(chuàng)建 Spring IOC 容器(Spring配置文件需要填相對(duì)路徑)
// 需要切換線程上下文類加載器以加載 JDBC 類驅(qū)動(dòng)(SPI)
ClassLoader oldCL = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(containerClassLoader);
try {
this.container = new ClassPathXmlApplicationContext(new String[]{ContainerConstant.SPRING_CONTEXT_FILE_NAME}, false);
this.container.setClassLoader(containerClassLoader);
this.container.refresh();
}finally {
Thread.currentThread().setContextClassLoader(oldCL);
}
log.info("[OmsJarContainer-{}] init container(name={},jarPath={}) successfully", containerId, name, localJarFile.getPath());
}
//......
}OmsJarContainer的init方法會(huì)使用構(gòu)造器傳入的localJarFile作為url創(chuàng)建OhMyClassLoader,然后讀取oms-worker-container.properties,以其中的PACKAGE_NAME屬性作為packageName,然后使用containerClassLoader.load進(jìn)行加載
deployContainer
tech/powerjob/worker/container/OmsContainerFactory.java
public static synchronized void deployContainer(ServerDeployContainerRequest request) {
Long containerId = request.getContainerId();
String containerName = request.getContainerName();
String version = request.getVersion();
log.info("[OmsContainer-{}] start to deploy container(name={},version={},downloadUrl={})", containerId, containerName, version, request.getDownloadURL());
OmsContainer oldContainer = CARGO.get(containerId);
if (oldContainer != null && version.equals(oldContainer.getVersion())) {
log.info("[OmsContainer-{}] version={} already deployed, so skip this deploy task.", containerId, version);
return;
}
String filePath = CONTAINER_DIR + containerId + "/" + version + ".jar";
// 下載Container到本地
File jarFile = new File(filePath);
try {
if (!jarFile.exists()) {
FileUtils.forceMkdirParent(jarFile);
FileUtils.copyURLToFile(new URL(request.getDownloadURL()), jarFile, 5000, 300000);
log.info("[OmsContainer-{}] download jar successfully, path={}", containerId, jarFile.getPath());
}
// 創(chuàng)建新容器
OmsContainer newContainer = new OmsJarContainer(containerId, containerName, version, jarFile);
newContainer.init();
// 替換容器
CARGO.put(containerId, newContainer);
log.info("[OmsContainer-{}] deployed new version:{} successfully!", containerId, version);
if (oldContainer != null) {
// 銷毀舊容器
oldContainer.destroy();
}
} catch (Exception e) {
log.error("[OmsContainer-{}] deployContainer(name={},version={}) failed.", containerId, containerName, version, e);
// 如果部署失敗,則刪除該 jar(本次失敗可能是下載jar出錯(cuò)導(dǎo)致,不刪除會(huì)導(dǎo)致這個(gè)版本永久無法重新部署)
CommonUtils.executeIgnoreException(() -> FileUtils.forceDelete(jarFile));
}
}OmsContainerFactory提供了deployContainer方法,它會(huì)根據(jù)containerId以及version去下載指定的jar包,然后創(chuàng)建OmsJarContainer,執(zhí)行init方法
小結(jié)
OhMyClassLoader繼承了URLClassLoader,它定義了load方法,遍歷urls,挨個(gè)根據(jù)url創(chuàng)建JarFile,然后遍歷jarFile.entries(),找到.class結(jié)尾的entry,判斷是否是packageName開頭的,是則執(zhí)行父類的loadClass方法。不過這里貌似沒有對(duì)JarFile進(jìn)行關(guān)閉,可能會(huì)導(dǎo)致資源泄露。
以上就是PowerJob的OhMyClassLoader工作流程源碼解讀的詳細(xì)內(nèi)容,更多關(guān)于PowerJob OhMyClassLoader的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Hadoop?MapReduce實(shí)現(xiàn)單詞計(jì)數(shù)(Word?Count)
這篇文章主要為大家詳細(xì)介紹了如何利用Hadoop實(shí)現(xiàn)單詞計(jì)數(shù)(Word?Count)的MapReduce,文中的示例代碼講解詳細(xì),感興趣的可以跟隨小編一起學(xué)習(xí)一下2023-05-05
Spring Security自定義登錄原理及實(shí)現(xiàn)詳解
這篇文章主要介紹了Spring Security自定義登錄原理及實(shí)現(xiàn)詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09
Spring Data JPA實(shí)現(xiàn)動(dòng)態(tài)查詢的兩種方法
本篇文章主要介紹了Spring Data JPA實(shí)現(xiàn)動(dòng)態(tài)查詢的兩種方法,具有一定的參考價(jià)值,有興趣的可以了解一下。2017-04-04
Java實(shí)現(xiàn)將文件或者文件夾壓縮成zip的詳細(xì)代碼
這篇文章主要介紹了Java實(shí)現(xiàn)將文件或者文件夾壓縮成zip的詳細(xì)代碼,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-11-11
IDEA 自帶的數(shù)據(jù)庫(kù)工具真的很牛(收藏版)
這篇文章主要介紹了IDEA 自帶的數(shù)據(jù)庫(kù)工具真的很牛(收藏版),本文以 IntelliJ IDEA/ Mac 版本作為演示,其他版本的應(yīng)該也差距不大,需要的朋友可以參考下2021-04-04
Java中不得不知的Collection接口與Iterator迭代器
這篇文章主要介紹了Java中的Collection接口與Iterator迭代器,文中有詳細(xì)的代碼示例供大家參考,對(duì)我們的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2023-06-06
Java并發(fā)編程之Semaphore(信號(hào)量)詳解及實(shí)例
這篇文章主要介紹了Java并發(fā)編程之Semaphore(信號(hào)量)詳解及實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-06-06
java動(dòng)態(tài)代理(jdk與cglib)詳細(xì)解析
靜態(tài)代理:由程序員創(chuàng)建或特定工具自動(dòng)生成源代碼,再對(duì)其編譯。在程序運(yùn)行前,代理類的.class文件就已經(jīng)存在了2013-09-09

