Jak načíst JAR soubory dynamicky za Běhu v JDK11+?

0

Otázka

Mám aplikaci, která načítá soubory jar v běhu dynamicky pomocí následujících řešení:

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

To bylo provedeno pomocí odpověď na to, Jak nahrát JAR soubory dynamicky za Běhu?

Nyní chci řešení, které funguje JDK11+, který je ekvivalentní k původnímu řešení, které jsem použil. Tak to dělá programově, bez nutnosti knihoven třetích stran/rámců nebo načítání/vyvolání jedné třídy.

Zkoušel jsem následující:

  1. Vytvořil DynamicClassLoader, který rozšiřuje UrlClassLoader:
public final class DynamicClassLoader extends URLClassLoader {


    public DynamicClassLoader(URL[] urls, ClassLoader parent) {
        super(urls, parent);
    }

    public DynamicClassLoader(String name, ClassLoader parent) {
        super(name, new URL[0], parent);
    }
    
    public DynamicClassLoader(ClassLoader parent) {
        this("classpath", parent);
    }

    public void addURL(URL url) {
        super.addURL(url);
    }
    
}
  1. Pak jsem se začít svou žádost vtip java.systém.třídy.nakladač flag:

java -Djava.system.class.loader=com.example.DynamicClassLoader

Pak mám soubor jar jako cesta objektu, který mám následující metodu:

public void loadJar(Path path) throws Exception {
        
        URL url = path.toUri().toURL();
        
        DynamicClassLoader classLoader = (DynamicClassLoader)ClassLoader.getSystemClassLoader();
        Method method = DynamicClassLoader.class.getDeclaredMethod("addURL", URL.class);
        method.setAccessible(true);
        method.invoke(classLoader, url);        
    }

Když tato metoda je volána jsem si následující obsazení třídy výjimka:

class jdk.internal.loader.ClassLoaders$AppClassLoader cannot be cast to class com.example.classloader.DynamicClassLoader (jdk.internal.loader.ClassLoaders$AppClassLoader is
 in module java.base of loader 'bootstrap'; com.example.classloader.DynamicClassLoader is in unnamed module of loader 'app')

Já používám Spring Boot (2.4) na OpenJDK11 (build 11.0.10+9).

classloader jar java java-11
2021-11-22 13:58:45
2
0

Změna ClassLoader pro běh aplikace je špatný nápad. Speciálně změně systému ClassLoader jak se snažíte.

JVM zatížení třídy líně, jak to provede, pak ukládá je na prvním zatížení. Zasahování do tohoto procesu může způsobit vážné problémy pro vaše aplikace stability.

Použití reflexe pro přístup k interní metody URLClassLoader,, jak to děláte, bude to jen způsobí, že vaše aplikace, aby se stal vázána na konkrétní JDK verze, kde se váš kód stane do práce, ale to není zaručeno pracovat, jak jste upgrade na novější JDK verze, nebo dokonce JDK od různých dodavatelů.

Existují způsoby, jak to udělat i když, ale to není zdaleka triviální.

Například OSGi je systém, který přidává tato funkce do JVM, ale musíte změnit nastavení aplikace, aby se vešly do OSGi model, který není velmi snadné. Jedním z jeho hlavních atrakcí je přesně to, že vám umožní nainstalovat balíčky (sklenice s OSGi metadata), odstranit, a dokonce i upgrade je vše bez restartování aplikace.

To znamená, že izolace všech sklenic do jejich vlastní ClassLoaders (pro zjednodušení věci trochu) a výpočtu, které třídy jsou exportovány/importovány do každého svazku, pak se ujistěte se, že každý ClassLoader můžete vidět potřebné třídy od ostatních, jak je to nutné (což je těžké!).

Můžete mít jednoduché dynamické načítání mechanismus, i když, tím, že vytvoří nové instance URLClassLoader pro každou skupinu sklenice, kterou chcete načíst za běhu.

Funguje to takhle, přibližně:

URL[] jars = { ... };

// you can pass a parent loader to the constructor...
// it will delegate first, so already loaded classes are not tried to reload
var loader = new URLClassLoader(jars);

// once you have a ClassLoader, you may use it to load classes provided
// by the new jars
Class<?> type = loader.loadClass("DynamicClass");

// do something with this class?
// But you must cast it to some type that's known in the current class-path,
// otherwise this class is only usable by reflection!

// For example, if you know this class must implement Runnable and it has
// a default constructor, you may be able to do this:
var runnable = (Runnable) type.newInstance();

// as we now know the type, we can actually use it type-safely
runnable.run();

2021-11-26 21:59:58
0

Na základě diskuse v sekci komentář, byl jsem schopen najít řešení. To možná není tak obecný, jak jsem doufal a předpokládá, že víte, třída použití (naproti stačí přidat Maven závislost na své pom.xml nebo staré JDK8 řešení).

  1. Stáhnout Maven dependencies (pomocí Jeka)

List<Path> paths = resolveDependency(groupId, artifactId, version);

public List<Path> resolveDependency(String groupId, String artifactId, String version) throws Exception {
        
        String dependency = groupId + ":" + artifactId + ":" + version;
        
        JkDependencySet deps = JkDependencySet.of()
                .and(dependency)
                .withDefaultScopes(COMPILE_AND_RUNTIME);

        JkDependencyResolver resolver = JkDependencyResolver.of(JkRepo.ofMavenCentral());
        List<Path> paths = resolver.resolve(deps, RUNTIME).getFiles().getEntries();

        return paths;

    }
  1. Načíst soubory jar

List<Class> classes = loadDependency(paths);

public List<Class> loadDependency(List<Path> paths) throws Exception {

        List<Class> classes = new ArrayList<>();

        for(Path path: paths){

            URL url = path.toUri().toURL();
            URLClassLoader child = new URLClassLoader(new URL[] {url}, this.getClass().getClassLoader());

            ArrayList<String> classNames = getClassNamesFromJar(path.toString());

            for (String className : classNames) {
                Class classToLoad = Class.forName(className, true, child);
                classes.add(classToLoad);
            }
        }

        return classes;

    }


    // Returns an arraylist of class names in a JarInputStream
    private ArrayList<String> getClassNamesFromJar(JarInputStream jarFile) throws Exception {
        ArrayList<String> classNames = new ArrayList<>();
        try {
            //JarInputStream jarFile = new JarInputStream(jarFileStream);
            JarEntry jar;

            //Iterate through the contents of the jar file
            while (true) {
                jar = jarFile.getNextJarEntry();
                if (jar == null) {
                    break;
                }
                //Pick file that has the extension of .class
                if ((jar.getName().endsWith(".class"))) {
                    String className = jar.getName().replaceAll("/", "\\.");
                    String myClass = className.substring(0, className.lastIndexOf('.'));
                    classNames.add(myClass);
                }
            }
        } catch (Exception e) {
            throw new Exception("Error while getting class names from jar", e);
        }
        return classNames;
    }


// Returns an arraylist of class names in a JarInputStream
// Calls the above function by converting the jar path to a stream
private ArrayList<String> getClassNamesFromJar(String jarPath) throws Exception {
        return getClassNamesFromJar(new JarInputStream(new FileInputStream(jarPath)));
    }

  1. Použít třídu

Jako Renato poukazuje na to, vědět, potřebujete vědět třídy, aby ji používat. V mém případě je to Velbloud komponenty, které potřebuji k obsazení a přidat do tohoto rámce. Třídy jsou to, co jste získali v druhém kroku a schéma je název komponenty.


Component camelComponent = getComponent(classes, scheme);
context.addComponent(scheme, camelComponent);

public Component getComponent(List<Class> classes, String scheme) throws Exception {

        Component component = null;
        for(Class classToLoad: classes){
            String className = classToLoad.getName().toLowerCase();
            if(className.endsWith(scheme + "component")){
                Object object =  classToLoad.newInstance();
                component = (Component) object;
            }
        }

        return component;
    }

Tak, druhá část je, aby se to dynamicky k dispozici. Přidána první a třetí část na příklad kompletního řešení.

2021-11-28 19:07:37

URLClassLoader měla by se zobrazit JAR Url, ne jednotlivých tříd, tak jako to děláš tady. Pouze jednu třídu loader stupně by měly být vytvořeny tak, aby zatížení třídy z jar (nebo mnoho). Poznamenal jsem o tom ve své GitHub úložiště.
Renato

V jiných jazycích

Tato stránka je v jiných jazycích

Русский
..................................................................................................................
Italiano
..................................................................................................................
Polski
..................................................................................................................
Română
..................................................................................................................
한국어
..................................................................................................................
हिन्दी
..................................................................................................................
Français
..................................................................................................................
Türk
..................................................................................................................
Português
..................................................................................................................
ไทย
..................................................................................................................
中文
..................................................................................................................
Español
..................................................................................................................
Slovenský
..................................................................................................................