我经历了很多帖子和问题,但没有一个人有一个绝对的Java程序来实现它。
要求:由于某些原因,我的应用程序加载了Common-codec 1.3.jar,
以后,在同一个jvm中,进程需要使用不同版本的Common-code 1.10.jar。
但是由于先前的类已加载并且它们具有相同的包名称,因此使用Java程序重新加载它们并不会替换现有的类。
这是我用来重新加载(替换)现有密钥的代码(示例),但没有找到预期的运气。请让我知道这可以通过Java示例完成。
String pathToJar=/root/Desktop/commons-codec-1.10.jar";
JarFile jarFile = null;
try {
jarFile = new JarFile(pathToJar);
} catch (IOException e) {
e.printStackTrace();
}
Enumeration<JarEntry> e = jarFile.entries();
URL[] urls = new URL[0];
try {
urls = new URL[]{ new URL("jar:file:" + pathToJar+"!/") };
} catch (MalformedURLException e1) {
e1.printStackTrace();
}
URLClassLoader cl = URLClassLoader.newInstance(urls);
while (e.hasMoreElements()) {
JarEntry je = e.nextElement();
if(je.isDirectory() || !je.getName().endsWith(".class")){
continue;
}
// -6 because of .class
String className = je.getName().substring(0,je.getName().length()-6);
className = className.replace(File.separatorChar, '.');
String check="org.apache.commons.codec.binary.Base32";
try {
Class c = cl.loadClass(className); // Excepting it to replace old files,but thats not happening
} catch (ClassNotFoundException e1) {
e1.printStackTrace();
}
我被建议编写自定义类加载器并通过它可以卸载。 有些人可能会展示一些相关的代码或过程。
答案 0 :(得分:1)
Class&#34;卸载&#34;只有在定义它的Class
和ClassLoader
都符合垃圾回收条件时才会发生。此外,任何给定的加载器只能定义一个特定名称的类一次。因此(除了诸如检测或JDK实现内部的异乎寻常的变通方法之外),没有像#34;重新加载的类加载器那样的东西&#34; - 至少不是真正意义上的;为了获得 n 不同的&#34;版本&#34;必须在 n 不同defineClass(C)
个对象上调用 C ,ClassLoader
。
让我们来看看以下简单的用例:我们有一个app.jar
,(不出所料)包含一个声明main
方法的类;和lib.jar
的两个副本,每个副本都有一个类LibApiImpl
,封装了一个只将版本相关的字符串打印到stdout的方法。我们的目标是仅仅参考&#34;库的两个副本&#34;来自main
的类,看到输出两个不同的字符串。本文的其余部分仅展示了满足此要求的所有可能方法中的两种。
ClassLoader
s 直接的解决方案是,每次需要加载不同的URLClassLoader
时,只需创建一个新的LibApiImpl
。不需要自定义类加载器实现,默认的父级优先委托模型同时为应用程序和库提供服务。有几点需要注意:
URLClassLoader
子项将无法覆盖其父母对此事的看法。Class::forName
,指定手动实例化的类加载器。单arg版本将委托给应用程序类加载器,(根据前一点)当然不知道是否存在相应的.class文件。package com.example.app;
import java.net.URL;
import java.net.URLClassLoader;
import com.example.lib.api.LibApi;
public class App {
public static void main(String... args) throws Exception {
try (URLClassLoader loader = new URLClassLoader(new URL[] { new URL("file:/path/to/lib1.jar") })) {
((LibApi) Class.forName("com.example.lib.impl.LibApiImpl", true, loader).newInstance()).printVersionInfo();
}
try (URLClassLoader loader = new URLClassLoader(new URL[] { new URL("file:/path/to/lib2.jar") })) {
((LibApi) Class.forName("com.example.lib.impl.LibApiImpl", true, loader).newInstance()).printVersionInfo();
}
}
}
package com.example.lib.api;
public interface LibApi {
void printVersionInfo();
}
package com.example.lib.impl;
import com.example.lib.api.LibApi;
public class LibApiImpl implements LibApi {
@Override
public void printVersionInfo() {
System.out.println("\n** lib " + getClass() + " / loaded by " + getClass().getClassLoader() + " **\n");
}
}
纠正com.example.app.App
中的路径;然后产生以下4个JAR:
app.jar
包的com.example.app
。lib-api.jar
包的com.example.lib.api
。lib1.jar
和lib2.jar
,每个都包含com.example.lib.impl
包。运行如下:
java -cp '/path/to/app.jar:/path/to/lib-api.jar' com.example.app.App
示例输出:
** lib class com.example.lib.impl.LibApiImpl / loaded by java.net.URLClassLoader@55f96302 **
** lib class com.example.lib.impl.LibApiImpl / loaded by java.net.URLClassLoader@135fbaa4 **
有时&#34;父母优先&#34;模型不足。作为一个人为的例子,假设我们想要获得&#34; copy&#34;由应用程序类加载器的某个子级加载的 last 的LibApiImpl
,而不必关心实际定义该副本的子项。换句话说,我们希望调用Class.forName("com.example.lib.impl.LibApiImpl")
返回&#34;最新鲜的&#34; LibApiImpl
版本。但是,除非我们使用自定义实现覆盖应用程序类加载器,或者更一般地,App
类的类加载器,否则该调用将始终失败,因为在默认委托模型下,委派单向流动较低级别的装载机,而不是反之亦然。
下面给出的应用程序类加载器实现的行为如下(有关具体说明,请参阅Loaders
类的Javadoc概述):有一个&#34; master&#34; loader,作为应用程序类加载器,可能有一个孩子,称为&#34; slave&#34;。 master负责从类路径(在这种情况下为app.jar
和lib-api.jar
)中加载不可重新加载的应用程序类,而slave则负载加载可重新加载的非类路径的应用程序类({{1 }和lib1.jar
)。两者之间的通信是双向的,并且最终将定义任何给定类的加载器,&#34; fixed&#34;或者&#34; reloadable&#34;,总是分别是主人和奴隶,无论是&#34;发起&#34;加载器,即应用程序名为lib2.jar
的加载程序,或传递给loadClass
。当然,这只不过是一个玩具实现,旨在(希望)说明一个不同的授权方案可能会是什么样子,并且可能存在我尚未想象的方式的缺陷。例如,实际的实现必须提供适当的并发性;提供Class::forName
等的合规实现;解决代码可访问性,验证以及可能的代码库权限分配问题;并允许多个从属的可扩展性和配置,甚至可以将任意实现的其他子节点附加到主节点,同时保留明确定义的委托语义。当然,当有OSGi之类的东西(以及其他许多东西)时,编写正确的实现通常需要付出太大的努力。
getResource
package com.example.app;
import java.net.URL;
import com.example.app.Loaders.MasterLoader;
import com.example.lib.api.LibApi;
public class App2 {
public static void main(String... args) throws Exception {
MasterLoader loader = (MasterLoader) ClassLoader.getSystemClassLoader();
loader.setDebug(true);
loader.refresh(new URL[] { new URL("file:/path/to/lib1.jar") });
newLibApi().printVersionInfo();
loader.refresh(new URL[] { new URL("file:/path/to/lib2.jar") });
newLibApi().printVersionInfo();
}
static LibApi newLibApi() throws Exception {
return (LibApi) Class.forName("com.example.lib.impl.LibApiImpl").newInstance();
}
}
再次纠正package com.example.app;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
/**
* Contains a "Master (parent)/Slave (child)" <code>ClassLoader</code> "duo".<br/>
* <br/>
* The class loading "protocol" adhered by the respective {@code loadClass(String, boolean)}
* implementations of the two is as follows:
* <ol>
* <li>If the name argument matches the name of the loader's class itself, return that class.
* Otherwise proceed.</li>
* <li>If a call to {@code findLoadedClass(name)} yields a non-null class, return that class.
* Otherwise proceed.</li>
* <li>Let <em>C</em> be either this loader's parent, if this is a "slave", or its child, if this is
* a "master". If <em>C</em> is non-null, have it execute steps (1), (2) itself. If a class gets
* produced, return that class. Otherwise (i.e., if there either is no result, or <em>C</em> is
* null) proceed.</li>
* <li>If the name argument refers to a potential bootstrap classpath class name, call
* {@code loadClass(name)} on the default system classloader (the "master's" parent). If the call
* succeeds, return that class. Otherwise proceed.</li>
* <li>If the name argument refers to a .class file under this loader's search path, read it, define
* and link a new class off of its contents, and return that "freshly-fefined" class. Otherwise
* proceed.</li>
* <li>Once again, let <em>C</em> be the loader specified in step (3). If non-null, have it execute
* step (5) on itself. If a class gets produced, return that class. Otherwise fail.</li>
* </ol>
*/
public class Loaders {
private static class SlaveLoader extends URLClassLoader {
static final Pattern BOOT_CLASS_PATH_RES_NAMES = Pattern.compile("((com\\.)?sun|java(x)?)\\..*");
static final URL[] EMPTY_SEARCH_PATH = new URL[0];
static final URL[] createSearchPath(String pathNames) {
if (pathNames != null) {
List<URL> searchPath = new ArrayList<>();
for (String pathName : pathNames.split(File.pathSeparator)) {
try {
searchPath.add(Paths.get(pathName).toUri().toURL());
}
catch (MalformedURLException e) {
e.printStackTrace();
}
}
return searchPath.toArray(new URL[0]);
}
return EMPTY_SEARCH_PATH;
}
static final byte[] readClassData(URL classResource) throws IOException {
try (InputStream in = classResource.openStream(); ByteArrayOutputStream out = new ByteArrayOutputStream()) {
while (in.available() > 0) {
out.write(in.read());
}
return out.toByteArray();
}
}
final String loadClassOutcomeMsgFmt = "loadClass return '{'\n\tloader = {0}\n\ttarget = {1}\n\tresult : {2}\n'}'";
volatile boolean debug;
SlaveLoader(URL[] searchPath, ClassLoader parent) {
super(searchPath, parent);
}
@Override
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
validateName(name);
Class<?> ret = loadFromCache(name);
if (ret != null) {
return ret;
}
MasterLoader parent = (MasterLoader) getParent();
if ((ret = parent.loadFromCache(name)) != null) {
log(loadClassOutcomeMsgFmt, this, name, "3 - early return - pre-loaded/cached - via " + parent);
return ret;
}
if ((ret = loadFromBootClasspath(name)) != null) {
return ret;
}
if ((ret = loadFromSearchPath(name, resolve)) != null) {
return ret;
}
if ((ret = parent.loadFromSearchPath(name, resolve)) != null) {
log(loadClassOutcomeMsgFmt, this, name,
"6 - common/non-reloadable classpath delegation - via " + parent);
return ret;
}
if ((ret = parent.loadFromSearchPath(name, resolve)) != null) {
return ret;
}
throw createCnfe(name, null);
}
void validateName(String name) throws ClassNotFoundException {
log("loadClass entry '{'\n\tloader = {0}\n\ttarget = {1}\n'}'", this, name);
if ((name == null) || name.trim().isEmpty()) {
throw createCnfe(name, null);
}
}
Class<?> loadFromCache(String name) {
Class<?> ret = getClass();
if (ret.getName().equals(name)) {
log(loadClassOutcomeMsgFmt, this, name, "1 - early return - own class");
return ret;
}
if ((ret = findLoadedClass(name)) != null) {
log(loadClassOutcomeMsgFmt, this, name, "2 - early return - pre-loaded/cached");
return ret;
}
return null;
}
Class<?> loadFromBootClasspath(String name) {
if (BOOT_CLASS_PATH_RES_NAMES.matcher(name).matches()) {
ClassLoader defSysCl = ClassLoader.getSystemClassLoader().getParent();
try {
Class<?> ret = ClassLoader.getSystemClassLoader().getParent().loadClass(name);
log(loadClassOutcomeMsgFmt, this, name, "4 - bootstrap classpath delegation - via " + defSysCl);
return ret;
}
catch (ClassNotFoundException cnfe) {
}
}
return null;
}
Class<?> loadFromSearchPath(String name, boolean resolve) throws ClassNotFoundException {
Class<?> ret = null;
URL res = findResource(name.replace(".", "/") + ".class");
if (res != null) {
byte[] b;
try {
b = readClassData(res);
}
catch (IOException ioe) {
throw createCnfe(name, ioe);
}
ret = defineClass(name, b, 0, b.length);
if (resolve) {
resolveClass(ret);
}
log(loadClassOutcomeMsgFmt, this, name, "5 - freshly-defined from local search path");
return ret;
}
return null;
}
ClassNotFoundException createCnfe(String name, Throwable cause) throws ClassNotFoundException {
return new ClassNotFoundException(MessageFormat.format("Class loading : {0} : {1} : FAILED", this,
(name == null) ? "null" : name, cause));
}
void log(String msg, Object... args) {
if (debug) {
System.out.println(MessageFormat.format("\n" + msg + "\n", args));
}
}
public void setDebug(boolean debug) {
this.debug = debug;
}
@Override
protected void finalize() throws Throwable {
try {
close();
}
finally {
super.finalize();
}
log("ClassLoader finalization : {0}", this);
}
}
public static class MasterLoader extends SlaveLoader {
static final URL[] DEFAULT_CLASS_PATH = createSearchPath(System.getProperty("java.class.path"));
private URL[] reloadableSearchPath = EMPTY_SEARCH_PATH;
private volatile SlaveLoader slave;
public MasterLoader(ClassLoader parent) {
super(DEFAULT_CLASS_PATH, parent);
}
public synchronized void refresh(URL[] reloadableSearchPath) {
int len;
if ((reloadableSearchPath != null) && ((len = reloadableSearchPath.length) > 0)) {
List<URL> path = new ArrayList<>(len + 1);
for (int i = 0; i < len; i++) {
URL entry = reloadableSearchPath[i];
if (entry != null) {
path.add(entry);
}
}
this.reloadableSearchPath = (!path.isEmpty()) ? path.toArray(EMPTY_SEARCH_PATH) : EMPTY_SEARCH_PATH;
}
else {
this.reloadableSearchPath = EMPTY_SEARCH_PATH;
}
if (slave != null) {
try {
slave.close();
}
catch (IOException ioe) {
}
slave = null;
/*
* At least two calls to System::gc appear to be required in order for Class::forName to cease
* returning cached classes previously defined by slave and for which the master served as an
* intermediary, i.e., an "initiating loader".
*
* See also http://blog.hargrave.io/2007/09/classforname-caches-defined-class-in.html
*/
for (int i = 0; i < 2; i++) {
System.gc();
try {
Thread.sleep(100);
}
catch (InterruptedException ie) {
}
}
}
if (this.reloadableSearchPath != EMPTY_SEARCH_PATH) {
log("Class loader search path refresh : {0}\n\tSearch path = {1}", this,
Arrays.toString(this.reloadableSearchPath));
slave = new SlaveLoader(this.reloadableSearchPath, this);
slave.setDebug(debug);
}
}
@Override
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
validateName(name);
Class<?> ret = loadFromCache(name);
if (ret != null) {
return ret;
}
if ((slave != null) && ((ret = slave.loadFromCache(name)) != null)) {
log(loadClassOutcomeMsgFmt, this, name, "3 - early return - pre-loaded/cached - via " + slave);
return ret;
}
if ((ret = loadFromBootClasspath(name)) != null) {
return ret;
}
if ((ret = loadFromSearchPath(name, resolve)) != null) {
return ret;
}
if ((slave != null) && ((ret = slave.loadFromSearchPath(name, resolve)) != null)) {
log(loadClassOutcomeMsgFmt, this, name,
"6 - reloadable classpath delegation - via " + ret.getClassLoader());
return ret;
}
throw createCnfe(name, null);
}
}
}
中的路径;将com.example.app.App2
和App2
添加到com.example.app.Loaders
;和再出口。其他JAR应该保留在前一个例子中。
运行如下:
app.jar
示例输出(java -cp '/path/to/app.jar:/path/to/lib-api.jar' \
'-Djava.system.class.loader=com.example.app.Loaders$MasterLoader' \
com.example.app.App2
debug省略):
loadClass