- 浏览: 209956 次
- 性别:
- 来自: 深圳
文章分类
- 全部博客 (391)
- java (18)
- python (3)
- ruby (4)
- linux (48)
- 网络 (9)
- 前端 (2)
- 社会、文化、哲学、人生、百态 (0)
- 工具 (10)
- 下载 (0)
- 常用地址 (0)
- tracert (0)
- mysql (8)
- 开源相关收藏 (1)
- 模块查看依懒 (1)
- watch使用 (1)
- Tcpdump (2)
- easy_install安装 (1)
- 构造redis批量删除脚本 (1)
- MYSQL 性能测试 (1)
- JAVA code encode utf-8 (1)
- linux nginx awk 实时 每妙 (1)
- mkpasswd (1)
- spring security oauth (1)
- jmap dump java memory Analyzer (1)
- JAVA DUMP (1)
- swap linux 过高 解决 (1)
- SWAP (1)
- jmap jstat jstack dump (1)
- java jconsole 的使用 (1)
- git 常用 (1)
- MYSQL 索引 动态 唯一 (1)
- TCP 三次握手 四次挥手 (1)
- linux date (1)
- 删除 空行 注释行 (1)
- maven3 yum linux install repository (1)
- linux git 搭建 (1)
- linux sar eth1 查看 流量 (1)
- sar (1)
- netstat ip 过滤 常用脚本 (1)
- Tcpdump 包分析网络连接过程 (1)
- net ipv4 tcp time wait tw recycle (0)
- /etc/sysctl.conf linux 网络 配置 (1)
- ss 网络连接查看 (比netstat 快很多,实时性牺牲) (1)
- MYSQL 关键字 (1)
- Linux 下多核CPU知识 (1)
- top (1)
- 令牌 证书 (1)
- mysql unix timestamp (1)
- 端口扫描 nc nmap (1)
- 204 http code 状态码 (1)
- ss -s ss -l (1)
- linux 常用 curl (1)
- linux sed 替换 换行 (1)
- centos yum install rpm install (1)
- spring-mvc源码解读 (1)
- 使用iftop查看实时的网络流量 (0)
- linux 命令 expect (1)
- HTTP (1)
- openssl ddif 加密 (1)
- iptables 详解 (1)
- python 虚拟化 VirtualEnv virtualenvwrapper (1)
- nginx (2)
- more less 实用技巧 (1)
- linux nginx (2)
- linux curl https ssl 证书 ca (1)
- openssl (1)
- php mysql linux (1)
- linux 虚拟机 虚拟 xen (0)
- linux 虚拟机 虚拟 xen kvm (1)
- linux perl 单行执行技巧 (1)
- mysql 查看库占用空间 表查用空间 (1)
- linux tcpdump (1)
- maven (1)
- sun.misc.Unsafe (1)
- OpenSSL生成证书 (1)
- http://blog.csdn.net/zzulp/article/details/8018751 (1)
- maven 本地 jar dependency (1)
- 计算JAVA代码行数最简单命令 sed (1)
- 常用的证书格式转换 rsa eg (1)
- 加密 解密 签名 (1)
- 分析jar包冲突 (1)
- 使用JMockit编写java单元测试 (1)
- Linux 技巧:让进程在后台可靠运行的几种方法 (1)
- 环境变量控制 (1)
- 5+ 个 tar 命令的用法,附示例 (1)
- scp自动输入密码 (1)
- ps axo pid (1)
- ppid (1)
- comm (1)
- pmem (1)
- lstart|grep mysql (0)
- lstart (1)
- etime|grep mysql (1)
- UML类图字少好理解 (1)
- HTTP经典文章 (1)
- git (1)
- Git常用命令 (1)
- LINUX 系统被攻击的分析过程 (1)
- NIO (1)
- LINUX 操作快捷键使用 (1)
- openSSL命令、PKI、CA、SSL证书原理 (1)
- shell (2)
- 转载 (1)
- mysqldump 可以直接dump->xml (1)
- VIM比较全面的文章 (1)
- eclipse regex 正则表达式 (1)
- synchronized (1)
- 锁 (1)
- java 正则表达式 regex (1)
- Reference Queue 引用 源码 (1)
- spring aop 源码 分析 (1)
- java @Cache @Transaction 注解 (1)
- spring aop (1)
- spring jdk proxy cglib 动态代理 性能比较 (1)
- spring proxy private public 代理限制 (1)
- spring transaction aop 事务 (1)
- spring autowire 注解注入 (1)
- 桥接 NAT NAT地址转换 内部网络 虚拟网络 (1)
- spring-web-mvc 源码解读 之 RequestMappingHandlerMapping (1)
- find atime mtime ctime -n n +n (1)
- android studio 快捷键初探 (1)
- android 源码阅读的计划 (1)
- 计算机网络学习-VLAN (1)
- sed 高级 合并行 (1)
- CAP 一致性 可用性 分布式容错性 (1)
- android lib so 库文件 (0)
- android lib so 库文件 移植 (1)
- android 不错的博文 (1)
- sourceinsight 源码 阅读 (1)
- Android Tab UI (1)
- 诗 (1)
- mysql 批处理 (0)
- netty 堆外内存 DirectByteBuffer (1)
- netty 并发 百万 推送 (1)
- Linux操作系统中内存buffer和cache的区别 (1)
- maven intellij target bytecode version (1)
- linux sleep()的实现原理 (1)
- android (2)
- javadoc 代码注释规范 (1)
- spring 自动注入bean auto (1)
- Photoshop CS6常用快捷键 (1)
- 股票 数据 机器 分析 (1)
- 批处理 (1)
- mysql -e (1)
- char (1)
- Unicode (1)
- 编码 (1)
- utf8 (1)
- utf-8 (1)
- utf16 (1)
- utf-16 (1)
- IntelliJ IDEA (1)
- ide (1)
- idea (1)
- intellij (1)
- 文件 (1)
- 目录 (1)
- 源代码 (1)
- CountDownLatch (1)
- CyclicBarrier (1)
- Semaphore (1)
- spring (1)
- linux 查看不同进制文件 (1)
- WebMvcConfigurationSupport (1)
- sdkman工具的使用 (1)
- http header (1)
- LINUX系统优化 (1)
最新评论
-
gelongmei:
威武我大酒神
shell脚本不换行刷新数据
Java 自定义 ClassLoader 实现隔离运行不同版本jar包的方式
Java 自定义 ClassLoader 实现隔离运行不同版本jar包的方式
1. 应用场景
有时候我们需要在一个 Project 中运行多个不同版本的 jar 包,以应对不同集群的版本或其它的问题。如果这个时候选择在同一个项目中实现这样的功能,那么通常只能选择更低版本的 jar 包,因为它们通常是向下兼容的,但是这样也往往会失去新版本的一些特性或功能,所以我们需要以扩展的方式引入这些 jar 包,并通过隔离执行,来实现版本的强制对应。
2. 实现
在 Java 中,所有的类默认通过 ClassLoader 加载,而 Java 默认提供了三层的 ClassLoader,并通过双亲委托模型的原则进行加载,其基本模型与加载位置如下(更多ClassLoader相关原理请自行搜索):
ClassLoader
Java 中默认的 ClassLoader 都规定了其指定的加载目录,一般也不会通过 JVM 参数来使其加载自定义的目录,所以我们需要自定义一个 ClassLoader 来加载装有不同版本的 jar 包的扩展目录,同时为了使运行扩展的 jar 包时,与启动项目实现绝对的隔离,我们需要保证他们所加载的类不会有相同的 ClassLoader,根据双亲委托模型的原理可知,我们必须使自定义的 ClassLoader 的 parent 为 null,这样不管是 JRE 自带的 jar 包或一些基础的 Class 都不会委托给 App ClassLoader(当然仅仅是将 Parent 设置为 null 是不够的,后面会说明)。与此同时这些实现了不同版本的 jar 包,是经过二次开发后的可以独立运行的项目。
2.1 实例
现在假定有这样一个需求,实现针对集群(比如 Hadoop 集群)版本为 V1 与 V2 的对应的执行程序,那么假定有如下项目:
Executor-Parent: 提供基础的 Maven 引用,可利用 Maven 一键打包所有的子模块/项目
Executor-Common: 提供基础的接口,已经有公有的实现等
Executor-Proxy: 执行不同版本程序的代理程序
Executor-V1: 版本为V1的执行程序
Executor-V2: 版本为V2的执行程序
这里为了更凸显 ClassLoader 的实现,不做 Executor-Parent 的实现,同时为了简便,也没有设置包名。
1) Executor-Common
在 Executor-Common 中提供一个接口,声明执行的具体方法:
public interface Executor {
void execute(final String name);
}
这里的方法使用了基础类型 String,实际中可能会使用自定义的类型,那么在 Porxy 的实现中则需要使用自定义的 ClassLoader 来加载参数,并使用反射来获取方法(后面会有一个简单的示例)。回到之前的示例,这里同时提供一个抽象的实现类:
public class AbstractExecutor implements Executor {
@Override
public void execute(final String name) {
this.handle(new Handler() {
@Override
public void handle() {
System.out.println("V:" + name);
}
});
}
protected void handle(Handler handler) {
handler.call();
}
protected abstract class Handler {
public void call() {
ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
// 临时更改 ClassLoader
Thread.currentThread().setContextClassLoader(AbstractExecutor.class.getClassLoader());
handle();
// 还原为之前的 ClassLoader
Thread.currentThread().setContextClassLoader(oldClassLoader);
}
public abstract void handle();
}
}
这里需要临时更改当前线程的 ContextClassLoader, 以应对扩展程序中可能出现的如下代码:
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
classLoader.loadClass(...);
因为它们会获取当前线程的 ClassLoader 来加载 class,而当前线程的ClassLoader极可能是App ClassLoader而非自定义的ClassLoader, 也许是为了安全起见,但是这会导致它可能加载到启动项目中的class(如果有),或者发生其它的异常,所以我们在执行时需要临时的将当前线程的ClassLoader设置为自定义的ClassLoader,以实现绝对的隔离执行。
2) Executor-V1 & Executor-V2
Executor-V1 和 Executor-V2 依赖了 Executor-Common.jar,并实现了 Executor 接口的方法:
public class ExecutorV1 extends AbstractExecutor {
@Override
public void execute(final String name) {
this.handle(new Handler() {
@Override
public void handle() {
System.out.println("V1:" + name);
}
});
}
}
public class ExecutorV2 extends AbstractExecutor {
@Override
public void execute(final String name) {
this.handle(new Handler() {
@Override
public void handle() {
System.out.println("V2:" + name);
}
});
}
}
这里仅仅是打印了它们的版本信息,实际中,它们可能需要引入不同的版本的 Jar 包,然后根据这些 Jar 包完成相应的操作。
3) Executor-Proxy
Executor-Proxy 利用自定义的 ClassLoader 和反射来实现加载与运行 ExecutorV1 和 ExecutorV2 中 Executor 接口的实现,而 ExecutorV1 和 ExecutorV2 将以 jar 包的形式被分别放置在 ${Executor-Proxy_HOME}\ext\v1 和 ${Executor-Proxy_HOME}\ext\v2 目录下,其中自定义的 ClassLoader 实现如下:
public class StandardExecutorClassLoader extends URLClassLoader {
private final static String baseDir = System.getProperty("user.dir") + File.separator + "ext" + File.separator;
public StandardExecutorClassLoader(String version) {
super(new URL[] {}, null); // 将 Parent 设置为 null
loadResource(version);
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
// 测试时可打印看一下
System.out.println("Class loader: " + name);
return super.loadClass(name);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
return super.findClass(name);
} catch(ClassNotFoundException e) {
return StandardExecutorClassLoader.class.getClassLoader().loadClass(name);
}
}
private void loadResource(String version) {
String jarPath = baseDir + version;
// 加载对应版本目录下的 Jar 包
tryLoadJarInDir(jarPath);
// 加载对应版本目录下的 lib 目录下的 Jar 包
tryLoadJarInDir(jarPath + File.separator + "lib");
}
private void tryLoadJarInDir(String dirPath) {
File dir = new File(dirPath);
// 自动加载目录下的jar包
if (dir.exists() && dir.isDirectory()) {
for (File file : dir.listFiles()) {
if (file.isFile() && file.getName().endsWith(".jar")) {
this.addURL(file);
continue;
}
}
}
}
private void addURL(File file) {
try {
super.addURL(new URL("file", null, file.getCanonicalPath()));
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
StandardExecutorClassLoader 在实例化时,会自动加载扩展目录下与其lib目录下的 jar 包,这里之所以要加载 lib 目录下的 jar,是为了加载扩展的依赖包。
有了StandardExecutorClassLoader,我们还需要一个调用各版本程序的代理类ExecutorPorxy,其实现如下:
import java.lang.reflect.Method;
public class ExecutorProxy implements Executor {
private String version;
private StandardExecutorClassLoader classLoader;
public ExecutorProxy(String version) {
this.version = version;
classLoader = new StandardExecutorClassLoader(version);
}
@Override
public void execute(String name) {
try {
// Load ExecutorProxy class
Class<?> executorClazz = classLoader.loadClass("Executor" + version.toUpperCase());
Object executorInstance = executorClazz.newInstance();
Method method = executorClazz.getMethod("execute", String.class);
method.invoke(executorInstance, name);
} catch (Exception e) {
e.printStackTrace();
}
}
}
这里是一个比较简单的实现,因为通过反射调用的方法的参数是基本类型,在实际中,更多的可能是自定义的参数,那么这时候则需要先通过自定义的 ClassLoader 加载其 Class,然后才能去获取对应的方法,下面是一个省去上下文的一个例子(不能直接运行):
public void call() throws IOException {
try {
// Load HBaseApi class
Class<?> hbaseApiClazz = loadHBaseApiClass();
Object hbaseApiInstance = hbaseApiClazz.newInstance();
// Load parameter class
Class<?> paramClazz = classLoader.loadClass(VO_PACKAGE_PATH + "." + sourceParame.getClass().getSimpleName());
// Transition parameter to targeParameter from sourceParameter
Object targetParam = BeanUtils.transfrom(paramClazz, sourceParame);
// Get function
Method method = hbaseApiClazz.getMethod(methodName, paramClazz);
// Invoke function by targetParam
method.invoke(hbaseApiInstance, targetParam);
} catch(ClassNotFoundException | NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
3. 运行
将ExecutorV1 和 ExecutorV2分别打包,并将其打包后的 jar包与其依赖(lib目录下)放入 Executor-Proxy 项目的 ext\v1 和 ext\v2 目录下,在 Executor-Proxy 项目中则可以使用 Junit 进行测试:
public class ExecutorTest {
@Test
public void testExecuteV1() {
Executor executor = new ExecutorProxy("v1");
executor.execute("TOM");
}
@Test
public void testExecuteV2() {
Executor executor = new ExecutorProxy("v2");
executor.execute("TOM");
}
}
打印结果最终分别如下:
execute testExecuteV1():
V1:TOM
execute testExecuteV2():
V2:TOM
4. 总结
总的来说,实现隔离允许指定 jar 包,主要需要做到以下几点:
自定义 ClassLoader,使其 Parent = null,避免其使用系统自带的 ClassLoader 加载 Class。
在调用相应版本的方法前,更改当前线程的 ContextClassLoader,避免扩展包的依赖包通过Thread.currentThread().getContextClassLoader()获取到非自定义的 ClassLoader 进行类加载
通过反射获取 Method 时,如果参数为自定义的类型,一定要使用自定义的 ClassLoader 加载参数获取 Class,然后在获取 Method,同时参数也必须转化为使用自定义的 ClassLoade 加载的类型(不同 ClassLoader 加载的同一个类不相等)
实际运用中,往往容易做到第一点或第三点,而忽略第二点,比如使用 HBase 相关包时。
当然,这只是一种解决的方式,我们仍然可以使用微服务来达到同样甚至更棒的效果,
以上。
版权声明:本文为博主原创文章,转载请注明出处 https://blog.csdn.net/t894690230/article/details/73252331
1. 应用场景
有时候我们需要在一个 Project 中运行多个不同版本的 jar 包,以应对不同集群的版本或其它的问题。如果这个时候选择在同一个项目中实现这样的功能,那么通常只能选择更低版本的 jar 包,因为它们通常是向下兼容的,但是这样也往往会失去新版本的一些特性或功能,所以我们需要以扩展的方式引入这些 jar 包,并通过隔离执行,来实现版本的强制对应。
2. 实现
在 Java 中,所有的类默认通过 ClassLoader 加载,而 Java 默认提供了三层的 ClassLoader,并通过双亲委托模型的原则进行加载,其基本模型与加载位置如下(更多ClassLoader相关原理请自行搜索):
ClassLoader
Java 中默认的 ClassLoader 都规定了其指定的加载目录,一般也不会通过 JVM 参数来使其加载自定义的目录,所以我们需要自定义一个 ClassLoader 来加载装有不同版本的 jar 包的扩展目录,同时为了使运行扩展的 jar 包时,与启动项目实现绝对的隔离,我们需要保证他们所加载的类不会有相同的 ClassLoader,根据双亲委托模型的原理可知,我们必须使自定义的 ClassLoader 的 parent 为 null,这样不管是 JRE 自带的 jar 包或一些基础的 Class 都不会委托给 App ClassLoader(当然仅仅是将 Parent 设置为 null 是不够的,后面会说明)。与此同时这些实现了不同版本的 jar 包,是经过二次开发后的可以独立运行的项目。
2.1 实例
现在假定有这样一个需求,实现针对集群(比如 Hadoop 集群)版本为 V1 与 V2 的对应的执行程序,那么假定有如下项目:
Executor-Parent: 提供基础的 Maven 引用,可利用 Maven 一键打包所有的子模块/项目
Executor-Common: 提供基础的接口,已经有公有的实现等
Executor-Proxy: 执行不同版本程序的代理程序
Executor-V1: 版本为V1的执行程序
Executor-V2: 版本为V2的执行程序
这里为了更凸显 ClassLoader 的实现,不做 Executor-Parent 的实现,同时为了简便,也没有设置包名。
1) Executor-Common
在 Executor-Common 中提供一个接口,声明执行的具体方法:
public interface Executor {
void execute(final String name);
}
这里的方法使用了基础类型 String,实际中可能会使用自定义的类型,那么在 Porxy 的实现中则需要使用自定义的 ClassLoader 来加载参数,并使用反射来获取方法(后面会有一个简单的示例)。回到之前的示例,这里同时提供一个抽象的实现类:
public class AbstractExecutor implements Executor {
@Override
public void execute(final String name) {
this.handle(new Handler() {
@Override
public void handle() {
System.out.println("V:" + name);
}
});
}
protected void handle(Handler handler) {
handler.call();
}
protected abstract class Handler {
public void call() {
ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
// 临时更改 ClassLoader
Thread.currentThread().setContextClassLoader(AbstractExecutor.class.getClassLoader());
handle();
// 还原为之前的 ClassLoader
Thread.currentThread().setContextClassLoader(oldClassLoader);
}
public abstract void handle();
}
}
这里需要临时更改当前线程的 ContextClassLoader, 以应对扩展程序中可能出现的如下代码:
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
classLoader.loadClass(...);
因为它们会获取当前线程的 ClassLoader 来加载 class,而当前线程的ClassLoader极可能是App ClassLoader而非自定义的ClassLoader, 也许是为了安全起见,但是这会导致它可能加载到启动项目中的class(如果有),或者发生其它的异常,所以我们在执行时需要临时的将当前线程的ClassLoader设置为自定义的ClassLoader,以实现绝对的隔离执行。
2) Executor-V1 & Executor-V2
Executor-V1 和 Executor-V2 依赖了 Executor-Common.jar,并实现了 Executor 接口的方法:
public class ExecutorV1 extends AbstractExecutor {
@Override
public void execute(final String name) {
this.handle(new Handler() {
@Override
public void handle() {
System.out.println("V1:" + name);
}
});
}
}
public class ExecutorV2 extends AbstractExecutor {
@Override
public void execute(final String name) {
this.handle(new Handler() {
@Override
public void handle() {
System.out.println("V2:" + name);
}
});
}
}
这里仅仅是打印了它们的版本信息,实际中,它们可能需要引入不同的版本的 Jar 包,然后根据这些 Jar 包完成相应的操作。
3) Executor-Proxy
Executor-Proxy 利用自定义的 ClassLoader 和反射来实现加载与运行 ExecutorV1 和 ExecutorV2 中 Executor 接口的实现,而 ExecutorV1 和 ExecutorV2 将以 jar 包的形式被分别放置在 ${Executor-Proxy_HOME}\ext\v1 和 ${Executor-Proxy_HOME}\ext\v2 目录下,其中自定义的 ClassLoader 实现如下:
public class StandardExecutorClassLoader extends URLClassLoader {
private final static String baseDir = System.getProperty("user.dir") + File.separator + "ext" + File.separator;
public StandardExecutorClassLoader(String version) {
super(new URL[] {}, null); // 将 Parent 设置为 null
loadResource(version);
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
// 测试时可打印看一下
System.out.println("Class loader: " + name);
return super.loadClass(name);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
return super.findClass(name);
} catch(ClassNotFoundException e) {
return StandardExecutorClassLoader.class.getClassLoader().loadClass(name);
}
}
private void loadResource(String version) {
String jarPath = baseDir + version;
// 加载对应版本目录下的 Jar 包
tryLoadJarInDir(jarPath);
// 加载对应版本目录下的 lib 目录下的 Jar 包
tryLoadJarInDir(jarPath + File.separator + "lib");
}
private void tryLoadJarInDir(String dirPath) {
File dir = new File(dirPath);
// 自动加载目录下的jar包
if (dir.exists() && dir.isDirectory()) {
for (File file : dir.listFiles()) {
if (file.isFile() && file.getName().endsWith(".jar")) {
this.addURL(file);
continue;
}
}
}
}
private void addURL(File file) {
try {
super.addURL(new URL("file", null, file.getCanonicalPath()));
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
StandardExecutorClassLoader 在实例化时,会自动加载扩展目录下与其lib目录下的 jar 包,这里之所以要加载 lib 目录下的 jar,是为了加载扩展的依赖包。
有了StandardExecutorClassLoader,我们还需要一个调用各版本程序的代理类ExecutorPorxy,其实现如下:
import java.lang.reflect.Method;
public class ExecutorProxy implements Executor {
private String version;
private StandardExecutorClassLoader classLoader;
public ExecutorProxy(String version) {
this.version = version;
classLoader = new StandardExecutorClassLoader(version);
}
@Override
public void execute(String name) {
try {
// Load ExecutorProxy class
Class<?> executorClazz = classLoader.loadClass("Executor" + version.toUpperCase());
Object executorInstance = executorClazz.newInstance();
Method method = executorClazz.getMethod("execute", String.class);
method.invoke(executorInstance, name);
} catch (Exception e) {
e.printStackTrace();
}
}
}
这里是一个比较简单的实现,因为通过反射调用的方法的参数是基本类型,在实际中,更多的可能是自定义的参数,那么这时候则需要先通过自定义的 ClassLoader 加载其 Class,然后才能去获取对应的方法,下面是一个省去上下文的一个例子(不能直接运行):
public void call() throws IOException {
try {
// Load HBaseApi class
Class<?> hbaseApiClazz = loadHBaseApiClass();
Object hbaseApiInstance = hbaseApiClazz.newInstance();
// Load parameter class
Class<?> paramClazz = classLoader.loadClass(VO_PACKAGE_PATH + "." + sourceParame.getClass().getSimpleName());
// Transition parameter to targeParameter from sourceParameter
Object targetParam = BeanUtils.transfrom(paramClazz, sourceParame);
// Get function
Method method = hbaseApiClazz.getMethod(methodName, paramClazz);
// Invoke function by targetParam
method.invoke(hbaseApiInstance, targetParam);
} catch(ClassNotFoundException | NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
3. 运行
将ExecutorV1 和 ExecutorV2分别打包,并将其打包后的 jar包与其依赖(lib目录下)放入 Executor-Proxy 项目的 ext\v1 和 ext\v2 目录下,在 Executor-Proxy 项目中则可以使用 Junit 进行测试:
public class ExecutorTest {
@Test
public void testExecuteV1() {
Executor executor = new ExecutorProxy("v1");
executor.execute("TOM");
}
@Test
public void testExecuteV2() {
Executor executor = new ExecutorProxy("v2");
executor.execute("TOM");
}
}
打印结果最终分别如下:
execute testExecuteV1():
V1:TOM
execute testExecuteV2():
V2:TOM
4. 总结
总的来说,实现隔离允许指定 jar 包,主要需要做到以下几点:
自定义 ClassLoader,使其 Parent = null,避免其使用系统自带的 ClassLoader 加载 Class。
在调用相应版本的方法前,更改当前线程的 ContextClassLoader,避免扩展包的依赖包通过Thread.currentThread().getContextClassLoader()获取到非自定义的 ClassLoader 进行类加载
通过反射获取 Method 时,如果参数为自定义的类型,一定要使用自定义的 ClassLoader 加载参数获取 Class,然后在获取 Method,同时参数也必须转化为使用自定义的 ClassLoade 加载的类型(不同 ClassLoader 加载的同一个类不相等)
实际运用中,往往容易做到第一点或第三点,而忽略第二点,比如使用 HBase 相关包时。
当然,这只是一种解决的方式,我们仍然可以使用微服务来达到同样甚至更棒的效果,
以上。
版权声明:本文为博主原创文章,转载请注明出处 https://blog.csdn.net/t894690230/article/details/73252331
相关推荐
自定义Classloader允许开发者根据特定需求定制类的加载逻辑,例如加密类文件、隔离不同版本的库或者动态加载代码。本文将深入探讨自定义Classloader的使用。 一、Classloader的工作原理 Java的类加载机制遵循双亲...
1. ClassLoader隔离:通过自定义ClassLoader,为每个模块加载特定版本的jar包,确保各模块间的类加载互不影响。 2. OSGi(Open Service Gateway Initiative)框架:OSGi提供了模块化系统,允许在同一JVM中动态加载和...
这里我们将详细讨论ClassLoader的运行机制,特别是自定义ClassLoader的设计与实现。 ClassLoader的基本职责是根据类名动态加载对应的类文件。在Java中,类加载过程遵循双亲委派模型(Parent Delegation Model)。这...
Java允许用户自定义ClassLoader,这在某些场景下非常有用,比如动态加载插件或者实现热更新。自定义ClassLoader需要继承`java.lang.ClassLoader`类,并重写`findClass()`或`loadClass()`方法。通过这两个方法,你...
- **取消双亲委派模型**:改为当前`ClassLoader`优先加载,这样可以避免不一致的JAR被父`ClassLoader`加载,从而实现隔离。 - **便捷地共享依赖**:允许共享那些没有`serialVersionUID`冲突的类库,提高效率。 - **...
用户还可以自定义ClassLoader,插入这个树结构中,以满足特定的加载需求。 三、ClassLoader的装载策略 从Java 1.2版本开始,引入了双亲委派模型(Delegation Model)。在该模型下,当一个ClassLoader收到加载类的...
总结来说,Java动态加载JAR或ZIP包是通过自定义类加载器实现的,它可以让我们在运行时按需加载外部库,提高系统的可扩展性和灵活性。这个过程涉及到类加载器的创建、文件的读取、类的解析和实例化等多个步骤,是一项...
自定义ClassLoader是Java灵活性的一个体现,开发者可以通过继承ClassLoader类并重写findClass()方法来自定义类的加载方式,例如从网络、数据库或者特定目录加载类。 接下来,我们讨论类变量初始化的顺序。Java中,...
了解Java ClassLoader不仅有助于理解JVM的运作机制,还能帮助开发者解决一些特定场景下的问题,比如实现模块化的类加载、动态加载代码、隔离不同版本的库等。因此,它是Java程序员必备的知识点之一。
通过对Java ClassLoader的深入了解,我们可以更好地理解Java类的加载机制以及如何通过自定义ClassLoader来满足特定的应用需求。淘宝网的成功实践为我们提供了宝贵的参考案例,展示了ClassLoaders在实际项目中的重要...
本文将深入探讨如何通过自定义ClassLoader实现动态加载Apk和Jar包的功能。 首先,我们要理解ClassLoader的基本概念。在Java中,ClassLoader是负责加载类到JVM(Java虚拟机)的核心组件。它按照类名查找并加载相应的...
通常,我们可以创建一个子类继承`java.lang.ClassLoader`,并重写`loadClass()`方法来实现特定版本JDBC驱动的加载。 2. **加载驱动**: 在自定义类加载器中,使用`Class.forName()`方法加载特定版本的JDBC驱动。例如...
但有时我们可能需要打破这种模型,比如实现类的版本控制或插件系统,这时可以通过自定义ClassLoader来实现。 5. 类加载器的关系图 Java中的ClassLoader形成了一个树状结构,Bootstrap ClassLoader位于顶端,其他类...
2. **Extension ClassLoader**:扩展类加载器,负责加载`<JAVA_HOME>\lib\ext`目录下的JAR包,或者被`-Djava.ext.dirs`指定的路径中的类。 3. **System ClassLoader**:也称为应用类加载器,负责加载`CLASSPATH`...
4. 类隔离:通过自定义ClassLoader实现不同模块之间的类隔离,避免类冲突。 总的来说,深入理解ClassLoader的工作原理对于优化程序性能、构建灵活的插件系统和解决类冲突问题具有重要意义。通过测试和实践,我们...
编写自定义ClassLoader的主要原因是扩展加载类的方式,以满足特殊需求。例如,你可以: 1. **从网络加载类**:这使得Java applets可以从Web服务器动态下载并执行代码。 2. **验证数字签名**:在加载类之前进行安全...
自定义`ClassLoader`能够帮助我们实现特定的加载策略,比如隔离不同版本的库或动态加载类。 `ClassLoader`的工作原理主要是通过读取`.class`文件并将其转换为`Class`对象。默认情况下,Java使用`Bootstrap ...
例如,可以创建自定义类加载器来实现按需加载、隔离不同版本的库,或者实现动态加载插件机制。通过重写`loadClass()`方法,开发者可以控制类的加载过程,实现特定的加载策略。 **多平台选择性配置**: Java的一个...
总之,ClassLoader是Java运行时环境中的关键组件,理解其工作原理和如何自定义ClassLoader对于提高开发者的技术深度和解决问题的能力具有重要意义。通过阅读源码和实践,我们可以更深入地掌握这一领域,从而在实际...