Java에서 왜 이렇게하기가 어렵습니까? 모든 종류의 모듈 시스템을 원한다면 JAR 파일을 동적으로로드 할 수 있어야합니다. 나는 당신 자신의 ClassLoader
것을 작성하여 그것을 수행하는 방법이 있다고 들었지만, 그것은 적어도 내 생각으로는 JAR 파일을 가진 메소드를 인수로 호출하는 것만 큼 쉬워야 할 일이 많이 있습니다.
이를 수행하는 간단한 코드에 대한 제안 사항이 있습니까?
Java에서 왜 이렇게하기가 어렵습니까? 모든 종류의 모듈 시스템을 원한다면 JAR 파일을 동적으로로드 할 수 있어야합니다. 나는 당신 자신의 ClassLoader
것을 작성하여 그것을 수행하는 방법이 있다고 들었지만, 그것은 적어도 내 생각으로는 JAR 파일을 가진 메소드를 인수로 호출하는 것만 큼 쉬워야 할 일이 많이 있습니다.
이를 수행하는 간단한 코드에 대한 제안 사항이 있습니까?
답변:
어려운 이유는 보안입니다. 클래스 로더는 변경 불가능합니다. 런타임에 클래스를 고의로 추가 할 수 없어야합니다. 실제로 시스템 클래스 로더와 함께 작동하는 것에 매우 놀랐습니다. 자식 클래스 로더를 만드는 방법은 다음과 같습니다.
URLClassLoader child = new URLClassLoader(
new URL[] {myJar.toURI().toURL()},
this.getClass().getClassLoader()
);
Class classToLoad = Class.forName("com.MyClass", true, child);
Method method = classToLoad.getDeclaredMethod("myMethod");
Object instance = classToLoad.newInstance();
Object result = method.invoke(instance);
고통 스럽지만 거기에 있습니다.
URLClassLoader child = new URLClassLoader (new URL[] {new URL("file://./my.jar")}, Main.class.getClassLoader());
jar 파일이 호출 될 것을 가정 my.jar
하고 같은 디렉토리에 있습니다.
다음 솔루션은 리플렉션을 사용하여 캡슐화를 우회하기 때문에 해킹 적이지만 완벽하게 작동합니다.
File file = ...
URL url = file.toURI().toURL();
URLClassLoader classLoader = (URLClassLoader)ClassLoader.getSystemClassLoader();
Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
method.setAccessible(true);
method.invoke(classLoader, url);
URLClassLoader.class.getDeclaredMethod("addURL", URL.class)
불법적 인 리플렉션 사용이며 향후 실패 할 것이라고 경고합니다 .
예를 들어 Eclipse 플랫폼 에서 구현 된 OSGi를 살펴보아야 합니다. 정확히 그렇습니다. 효과적으로 JAR 파일 인 소위 번들을 설치, 설치 제거, 시작 및 중지 할 수 있습니다. 그러나 런타임시 JAR 파일에서 동적으로 발견 될 수있는 서비스와 같은 서비스를 제공하므로 약간 더 많은 작업을 수행합니다.
또는 Java 모듈 시스템 의 사양을 참조하십시오 .
방법에 대한 JCL 클래스 로더 프레임 워크 ? 나는 그것을 사용하지 않았 음을 인정해야하지만 유망 해 보인다.
사용 예 :
JarClassLoader jcl = new JarClassLoader();
jcl.add("myjar.jar"); // Load jar file
jcl.add(new URL("http://myserver.com/myjar.jar")); // Load jar from a URL
jcl.add(new FileInputStream("myotherjar.jar")); // Load jar file from stream
jcl.add("myclassfolder/"); // Load class folder
jcl.add("myjarlib/"); // Recursively load all jar files in the folder/sub-folder(s)
JclObjectFactory factory = JclObjectFactory.getInstance();
// Create object of loaded class
Object obj = factory.create(jcl, "mypackage.MyClass");
더 이상 사용되지 않는 버전이 있습니다. 더 이상 사용되지 않는 기능을 제거하기 위해 원본을 수정했습니다.
/**************************************************************************************************
* Copyright (c) 2004, Federal University of So Carlos *
* *
* All rights reserved. *
* *
* Redistribution and use in source and binary forms, with or without modification, are permitted *
* provided that the following conditions are met: *
* *
* * Redistributions of source code must retain the above copyright notice, this list of *
* conditions and the following disclaimer. *
* * Redistributions in binary form must reproduce the above copyright notice, this list of *
* * conditions and the following disclaimer in the documentation and/or other materials *
* * provided with the distribution. *
* * Neither the name of the Federal University of So Carlos nor the names of its *
* * contributors may be used to endorse or promote products derived from this software *
* * without specific prior written permission. *
* *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS *
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT *
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR *
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR *
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, *
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, *
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR *
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF *
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING *
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS *
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
**************************************************************************************************/
/*
* Created on Oct 6, 2004
*/
package tools;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
/**
* Useful class for dynamically changing the classpath, adding classes during runtime.
*/
public class ClasspathHacker {
/**
* Parameters of the method to add an URL to the System classes.
*/
private static final Class<?>[] parameters = new Class[]{URL.class};
/**
* Adds a file to the classpath.
* @param s a String pointing to the file
* @throws IOException
*/
public static void addFile(String s) throws IOException {
File f = new File(s);
addFile(f);
}
/**
* Adds a file to the classpath
* @param f the file to be added
* @throws IOException
*/
public static void addFile(File f) throws IOException {
addURL(f.toURI().toURL());
}
/**
* Adds the content pointed by the URL to the classpath.
* @param u the URL pointing to the content to be added
* @throws IOException
*/
public static void addURL(URL u) throws IOException {
URLClassLoader sysloader = (URLClassLoader)ClassLoader.getSystemClassLoader();
Class<?> sysclass = URLClassLoader.class;
try {
Method method = sysclass.getDeclaredMethod("addURL",parameters);
method.setAccessible(true);
method.invoke(sysloader,new Object[]{ u });
} catch (Throwable t) {
t.printStackTrace();
throw new IOException("Error, could not add URL to system classloader");
}
}
public static void main(String args[]) throws IOException, SecurityException, ClassNotFoundException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException{
addFile("C:\\dynamicloading.jar");
Constructor<?> cs = ClassLoader.getSystemClassLoader().loadClass("test.DymamicLoadingTest").getConstructor(String.class);
DymamicLoadingTest instance = (DymamicLoadingTest)cs.newInstance();
instance.test();
}
}
여기에 나열된 대부분의 솔루션은 구성하기 어려운 에이전트 (JDK 9 이전)이거나 더 이상 작동하지 않습니다 (JDK 9 이후) . 명확하게 문서화 된 방법을 언급하지 않은 사람은 정말 충격적입니다 .
커스텀 시스템 클래스 로더를 만들면 원하는대로 자유롭게 할 수 있습니다. 반영이 필요하지 않으며 모든 클래스가 동일한 클래스 로더를 공유합니다.
JVM을 시작할 때이 플래그를 추가하십시오.
java -Djava.system.class.loader=com.example.MyCustomClassLoader
클래스 로더에는 클래스 로더를 허용하는 생성자가 있어야하며, 클래스 로더를 상위로 설정해야합니다. JVM 시작시 생성자가 호출되고 실제 시스템 클래스 로더가 전달되고 기본 클래스는 사용자 정의 로더에 의해로드됩니다.
항아리를 추가하려면 전화 ClassLoader.getSystemClassLoader()
하여 수업에 캐스트하십시오.
신중하게 제작 된 클래스 로더에 대해서는 이 구현 을 확인하십시오 . add()
방법을 공개로 변경할 수 있습니다 .
함께 자바 9 과 답변 URLClassLoader
현재 오류가 같이 제공 :
java.lang.ClassCastException: java.base/jdk.internal.loader.ClassLoaders$AppClassLoader cannot be cast to java.base/java.net.URLClassLoader
사용 된 클래스 로더가 변경 되었기 때문입니다. 대신 시스템 클래스 로더에 추가하기 위해 에이전트를 통해 인스 트루먼 테이션 API를 사용할 수 있습니다 .
에이전트 클래스를 작성하십시오.
package ClassPathAgent;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.util.jar.JarFile;
public class ClassPathAgent {
public static void agentmain(String args, Instrumentation instrumentation) throws IOException {
instrumentation.appendToSystemClassLoaderSearch(new JarFile(args));
}
}
META-INF / MANIFEST.MF를 추가하고 에이전트 클래스가있는 JAR 파일에 넣으십시오.
Manifest-Version: 1.0
Agent-Class: ClassPathAgent.ClassPathAgent
에이전트를 실행하십시오.
이것은 사용 바이트 친구 에이전트 실행중인 JVM에 에이전트를 추가 할 라이브러리를 :
import java.io.File;
import net.bytebuddy.agent.ByteBuddyAgent;
public class ClassPathUtil {
private static File AGENT_JAR = new File("/path/to/agent.jar");
public static void addJarToClassPath(File jarFile) {
ByteBuddyAgent.attach(AGENT_JAR, String.valueOf(ProcessHandle.current().pid()), jarFile.getPath());
}
}
내가 찾은 가장 좋은 것은 XBean 프로젝트의 일부인 org.apache.xbean.classloader.JarFileClassLoader 입니다 .
다음은 특정 디렉토리의 모든 lib 파일에서 클래스 로더를 작성하기 위해 과거에 사용한 짧은 방법입니다.
public void initialize(String libDir) throws Exception {
File dependencyDirectory = new File(libDir);
File[] files = dependencyDirectory.listFiles();
ArrayList<URL> urls = new ArrayList<URL>();
for (int i = 0; i < files.length; i++) {
if (files[i].getName().endsWith(".jar")) {
urls.add(files[i].toURL());
//urls.add(files[i].toURI().toURL());
}
}
classLoader = new JarFileClassLoader("Scheduler CL" + System.currentTimeMillis(),
urls.toArray(new URL[urls.size()]),
GFClassLoader.class.getClassLoader());
}
그런 다음 클래스 로더를 사용하려면 다음을 수행하십시오.
classLoader.loadClass(name);
최신 버전의 Java와 호환되는 Allain의 방법에 대한 빠른 해결 방법은 다음과 같습니다.
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
try {
Method method = classLoader.getClass().getDeclaredMethod("addURL", URL.class);
method.setAccessible(true);
method.invoke(classLoader, new File(jarPath).toURI().toURL());
} catch (NoSuchMethodException e) {
Method method = classLoader.getClass()
.getDeclaredMethod("appendToClassPathForInstrumentation", String.class);
method.setAccessible(true);
method.invoke(classLoader, jarPath);
}
특정 JVM의 내부 구현에 대한 지식에 의존하므로 이상적이지 않으며 보편적 인 솔루션이 아닙니다. 그러나 표준 OpenJDK 또는 Oracle JVM을 사용한다는 것을 알고 있다면 빠르고 쉬운 해결 방법입니다. 새 JVM 버전이 출시 될 때 어느 시점에서 중단 될 수도 있으므로이를 명심해야합니다.
Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make void jdk.internal.loader.ClassLoaders$AppClassLoader.appendToClassPathForInstrumentation(java.lang.String) accessible: module java.base does not "opens jdk.internal.loader" to unnamed module @18ef96
jodonnell이 제안한 솔루션은 훌륭하지만 약간 향상되어야합니다. 이 게시물을 사용하여 응용 프로그램을 성공적으로 개발했습니다.
먼저 우리는 추가해야합니다
Thread.currentThread().setContextClassLoader(classLoader);
또는 jar에 저장된 자원 (예 : spring / context.xml)을로드 할 수 없습니다.
항아리를 부모 클래스 로더에 넣거나 누가 무엇을로드하는지 이해할 수 없습니다.
URLClassLoader를 사용하여 jar를 다시로드 할 때의 문제점 도 참조하십시오.
그러나 OSGi 프레임 워크가 가장 좋은 방법입니다.
Allain의 해킹 솔루션의 다른 버전으로 JDK 11에서도 작동합니다.
File file = ...
URL url = file.toURI().toURL();
URLClassLoader sysLoader = new URLClassLoader(new URL[0]);
Method sysMethod = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{URL.class});
sysMethod.setAccessible(true);
sysMethod.invoke(sysLoader, new Object[]{url});
JDK 11에서는 사용 중단 경고가 표시되지만 JDK 11에서 Allain 솔루션을 사용하는 사람들은 임시 솔루션으로 사용됩니다.
나를 위해 작동하는 인스 트루먼 테이션을 사용하는 또 다른 작업 솔루션. 종속 클래스의 클래스 가시성에 대한 문제점을 피하면서 클래스 로더 검색을 수정하는 이점이 있습니다.
에이전트 클래스 만들기
이 예제에서는 명령 행에서 호출 한 동일한 jar에 있어야합니다.
package agent;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.util.jar.JarFile;
public class Agent {
public static Instrumentation instrumentation;
public static void premain(String args, Instrumentation instrumentation) {
Agent.instrumentation = instrumentation;
}
public static void agentmain(String args, Instrumentation instrumentation) {
Agent.instrumentation = instrumentation;
}
public static void appendJarFile(JarFile file) throws IOException {
if (instrumentation != null) {
instrumentation.appendToSystemClassLoaderSearch(file);
}
}
}
MANIFEST.MF 수정
에이전트에 대한 참조 추가 :
Launcher-Agent-Class: agent.Agent
Agent-Class: agent.Agent
Premain-Class: agent.Agent
실제로 Netbeans를 사용하므로이 게시물 은 manifest.mf를 변경하는 방법에 도움 이 됩니다.
달리는
이 Launcher-Agent-Class
에이전트는 JDK 9+에서만 지원되며 명령 행에서 에이전트를 명시 적으로 정의하지 않고 에이전트를로드해야합니다.
java -jar <your jar>
JDK 6+에서 작동하는 방식은 -javaagent
인수를 정의하는 것입니다 .
java -javaagent:<your jar> -jar <your jar>
런타임에 새로운 Jar 추가
그런 다음 다음 명령을 사용하여 필요에 따라 jar을 추가 할 수 있습니다.
Agent.appendJarFile(new JarFile(<your file>));
설명서에서 이것을 사용하는 데 아무런 문제가 없었습니다.
미래에 누군가가 이것을 검색하는 경우이 방법은 OpenJDK 13.0.2에서 작동합니다.
런타임에 동적으로 인스턴스화 해야하는 많은 클래스가 있으며 각각 잠재적으로 다른 클래스 경로가 있습니다.
이 코드에는 이미로드하려는 클래스에 대한 일부 메타 데이터를 보유하는 pack이라는 객체가 있습니다. getObjectFile () 메소드는 클래스의 클래스 파일 위치를 리턴합니다. getObjectRootPath () 메소드는 인스턴스화하려는 클래스를 포함하는 클래스 파일을 포함하는 bin / 디렉토리의 경로를 리턴합니다. getLibPath () 메소드는 클래스가 속한 모듈의 클래스 경로를 구성하는 jar 파일을 포함하는 디렉토리에 대한 경로를 리턴합니다.
File object = new File(pack.getObjectFile()).getAbsoluteFile();
Object packObject;
try {
URLClassLoader classloader;
List<URL> classpath = new ArrayList<>();
classpath.add(new File(pack.getObjectRootPath()).toURI().toURL());
for (File jar : FileUtils.listFiles(new File(pack.getLibPath()), new String[] {"jar"}, true)) {
classpath.add(jar.toURI().toURL());
}
classloader = new URLClassLoader(classpath.toArray(new URL[] {}));
Class<?> clazz = classloader.loadClass(object.getName());
packObject = clazz.getDeclaredConstructor().newInstance();
} catch (Exception e) {
e.printStackTrace();
throw e;
}
return packObject;
나는 이것을하기 위해 Maven 의존성 : org.xeustechnologies : jcl-core : 2.8을 사용하고 있었지만 JDK 1.8을 지나간 후에 때때로 멈추고 Reference :: waitForReferencePendingList ()에서 "참조 대기 중"으로 멈추지 않았다.
또한 인스턴스화하려는 클래스가 이미 인스턴스화 한 클래스와 동일한 모듈에있는 경우 재사용 할 수 있도록 클래스 로더 맵을 유지하고 있습니다.
내가 시작한이 프로젝트를 살펴보십시오 : proxy-object lib
이 lib는 파일 시스템이나 다른 위치에서 jar 파일을로드합니다. jar가 라이브러리 충돌이 없는지 확인하기 위해 클래스 로더를 전용으로 사용합니다. 사용자는로드 된 jar에서 오브젝트를 작성하고 그에 대한 메소드를 호출 할 수 있습니다. 이 lib는 Java 7을 지원하는 코드베이스에서 Java 8로 컴파일 된 jar을로드하도록 설계되었습니다.
객체를 만들려면
File libDir = new File("path/to/jar");
ProxyCallerInterface caller = ObjectBuilder.builder()
.setClassName("net.proxy.lib.test.LibClass")
.setArtifact(DirArtifact.builder()
.withClazz(ObjectBuilderTest.class)
.withVersionInfo(newVersionInfo(libDir))
.build())
.build();
String version = caller.call("getLibVersion").asString();
ObjectBuilder는 팩토리 메소드, 정적 함수 호출 및 인터페이스 구현 콜백을 지원합니다. readme 페이지에 더 많은 예제를 게시 할 것입니다.
이것은 늦은 응답 일 수 있습니다 .DataMelt ( http://jwork.org/dmelt )의 jhplot.Web 클래스를 사용 하여이 작업을 수행 할 수 있습니다 (fastutil-8.2.2.jar의 간단한 예 ).
import jhplot.Web;
Web.load("http://central.maven.org/maven2/it/unimi/dsi/fastutil/8.2.2/fastutil-8.2.2.jar"); // now you can start using this library
문서에 따르면이 파일은 "lib / user"에 다운로드 된 후 동적으로로드되므로 동일한 프로그램에서이 jar 파일의 클래스를 사용하여 즉시 시작할 수 있습니다.
java 8 및 java 9 이상 모두에 대해 런타임에 jar 파일을로드해야했습니다 (위의 주석은이 두 버전 모두에서 작동하지 않습니다). 이를 수행하는 방법은 다음과 같습니다 (Spring Boot 1.5.2를 사용하는 경우).
public static synchronized void loadLibrary(java.io.File jar) {
try {
java.net.URL url = jar.toURI().toURL();
java.lang.reflect.Method method = java.net.URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{java.net.URL.class});
method.setAccessible(true); /*promote the method to public access*/
method.invoke(Thread.currentThread().getContextClassLoader(), new Object[]{url});
} catch (Exception ex) {
throw new RuntimeException("Cannot load library from jar file '" + jar.getAbsolutePath() + "'. Reason: " + ex.getMessage());
}
}
개인적으로 java.util.ServiceLoader 가 작업을 잘 수행 한다는 것을 알았 습니다. 여기 에서 예를 얻을 수 있습니다 .