문제

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);

고통스럽지만 거기까지입니다.

다른 팁

다음 솔루션은 리플렉션을 사용하여 캡슐화를 우회하므로 해킹적이지만 완벽하게 작동합니다.

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);

당신은 살펴 봐야합니다 OSGi, 예를 들어에서 구현 이클립스 플랫폼.정확히 그렇게합니다.사실상 JAR 파일인 소위 번들을 설치, 제거, 시작 및 중지할 수 있습니다.그러나 예를 들어 제공하는 것처럼 조금 더 많은 작업을 수행합니다.런타임 시 JAR 파일에서 동적으로 검색할 수 있는 서비스입니다.

또는 해당 사양을 참조하세요. 자바 모듈 시스템.

어때? 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();
    }
}

와 함께 자바 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());
    }
}

내가 찾은 최고는 org.apache.xbean.classloader.JarFileClassLoader 이는의 일부입니다. XBean 프로젝트.

다음은 특정 디렉토리의 모든 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);

Android에서 작업하는 경우 다음 코드가 작동합니다.

String jarFile = "path/to/jarfile.jar";
DexClassLoader classLoader = new DexClassLoader(jarFile, "/data/data/" + context.getPackageName() + "/", null, getClass().getClassLoader());
Class<?> myClass = classLoader.loadClass("MyClass");

jodonnell이 제안한 솔루션은 훌륭하지만 약간 향상되어야 합니다.나는 이 게시물을 사용하여 애플리케이션을 성공적으로 개발했습니다.

현재 스레드 할당

먼저 우리는 추가해야

Thread.currentThread().setContextClassLoader(classLoader);

그렇지 않으면 jar에 저장된 리소스(예: spring/context.xml)를 로드할 수 없습니다.

포함하지 않음

항아리를 상위 클래스 로더에 넣지 않으면 누가 무엇을 로드하는지 이해할 수 없습니다.

또한보십시오 URLClassLoader를 사용하여 jar을 다시 로드하는 중에 문제가 발생했습니다.

그러나 OSGi 프레임워크는 여전히 최선의 방법입니다.

JDK 11에서도 작동하는 Allain의 또 다른 hackish 솔루션 버전은 다음과 같습니다.

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를 사용하고 있습니다. 이 게시물 매니페스트.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>));

문서에서 이것을 사용하여 어떤 문제도 발견하지 못했습니다.

다음은 최신 버전의 Java와 호환되도록 하는 Alllain의 방법에 대한 빠른 해결 방법입니다.

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 버전이 출시되면 어느 시점에 중단될 수도 있으므로 이를 염두에 두어야 합니다.

응답이 늦어질 수 있습니다. DataMelt의 jhplot.Web 클래스를 사용하여 다음과 같이(fastutil-8.2.2.jar에 대한 간단한 예) 수행할 수 있습니다(http://jwork.org/dmelt)

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 파일의 클래스를 사용하여 즉시 시작할 수 있습니다.

제가 시작한 이 프로젝트를 한번 봐주세요: 프록시 객체 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 페이지에 더 많은 예제를 게시할 예정입니다.

나는 개인적으로 그렇게 생각한다. java.util.ServiceLoader 일을 꽤 잘해요.예시를 들어보실 수 있습니다 여기.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top