Maison  >  Article  >  Java  >  Une introduction détaillée au chargement et au rechargement de classes dynamiques Java

Une introduction détaillée au chargement et au rechargement de classes dynamiques Java

不言
不言avant
2018-10-17 16:28:275136parcourir

Cet article vous apporte une introduction détaillée au chargement et au rechargement de classes dynamiques Java. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer.

Les classes peuvent être chargées et rechargées au moment de l'exécution en Java, même si ce n'est pas aussi simple qu'on le pense. Cet article expliquera quand et comment charger et recharger des classes en Java.
Vous pouvez vous demander si le chargement dynamique des classes fait partie de la réflexion Java ou du noyau Java. Quoi qu'il en soit, je l'ai mis dans Java Reflection faute d'un meilleur endroit pour le mettre.

Class Loader

Toutes les classes d'un programme Java sont chargées à l'aide de certaines sous-classes de java.lang.ClassLoader. Par conséquent, les classes chargées dynamiquement doivent également utiliser une sous-classe de java.lang.ClassLoader.
Lorsqu'une classe est chargée, les classes auxquelles elle fait référence seront également chargées. Le mode de chargement de classe se charge de manière récursive jusqu'à ce que toutes les classes requises soient chargées. Il se peut que ce ne soient pas toutes les classes de l'application. Les classes non référencées ne sont pas chargées tant qu'elles ne sont pas référencées.

Hiérarchie de chargement des classes

Le chargement des classes en Java est organisé en hiérarchies. Lorsque vous créez un ClassLoader autonome, vous devez fournir un ClassLoader parent. Si un ClassLoader est invité à charger une classe, il demandera à son ClassLoader parent de la charger. Si le chargeur de classe parent ne trouve pas la classe, le chargeur de classe enfant essaiera de la charger lui-même.

Chargement de classe

Les étapes permettant à un chargeur de classe de charger une classe sont les suivantes :

  1. Vérifiez si la classe a été chargée

  2. Si la classe n'est pas chargée, demandez au chargeur de classe parent de la charger

  3. Si le chargeur de classe parent ne peut pas charger la classe, essayez d'utiliser le chargeur de classe actuel pour le charger

Lorsque vous implémentez un chargeur de classe qui peut surcharger les classes, vous devez vous écarter un peu de cette séquence. Il ne faut pas demander au chargeur de classe parent de charger la classe à recharger. Nous en reparlerons plus tard.

Chargement dynamique des classes

Le chargement dynamique des classes est très simple. Tout ce que vous avez à faire est d’obtenir un ClassLoader et d’appeler sa méthode loadClass(). Un exemple est le suivant :

public class MainClass {

  public static void main(String[] args){

    ClassLoader classLoader = MainClass.class.getClassLoader();

    try {
        Class aClass = classLoader.loadClass("com.jenkov.MyClass");
        System.out.println("aClass.getName() = " + aClass.getName());
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }

}

Rechargement dynamique des classes

Le rechargement dynamique des classes présente certains défis. Le chargeur de classe intégré à Java vérifie toujours si la classe a été chargée avant de la charger. Par conséquent, il n'est pas possible de recharger une classe à l'aide du chargeur de classe intégré à Java. Pour recharger une classe, vous devez implémenter votre propre sous-classe ClassLoader.
Même avec des sous-classes personnalisées de chargeurs de classes, il existe des défis. Toutes les classes chargées doivent être liées. Cette méthode est définitive et ne peut donc pas être remplacée par vos sous-classes ClassLoader. La méthode solve() ne permet pas à une instance ClassLoader de se lier deux fois à une classe. Par conséquent, chaque fois que vous devez recharger une classe, vous devez recréer une instance de la classe ClassLoader. Ce n'est pas impossible, mais il faut savoir quand concevoir la classe à recharger.

Conception de code de surcharge de classe

Comme mentionné ci-dessus, le ClassLoader qui charge la classe spécifiée ne peut pas être utilisé pour recharger cette classe. Par conséquent, un ClassLoader différent doit être utilisé pour charger cette classe. Cependant, cela crée de nouveaux problèmes.
Chaque classe chargée dans un programme Java est identifiée par son nom complet (nom du package + nom de la classe) et est chargée par une instance ClassLoader. Cela signifie que la classe MyObject chargée par le chargeur de classe A est différente de la même classe MyObject chargée par le chargeur de classe B. Le code de simulation est le suivant :

MyObject object = (MyObject)
    myClassReloadingFactory.newInstance("com.jenkov.MyObject");

Notez comment la classe MyObject est référencée dans le code en tant que variable de type objet. Cela provoque le chargement de la classe MyObject par le chargeur de classe qui a déjà chargé le code résident de cette classe.
Si la fabrique d'objets myClassReloadingFactory charge MyObject à l'aide d'un chargeur de classe différent de celui du code résident, vous ne pouvez pas convertir la variable de type Object rechargée MyObject en type MyObject. Étant donné que les deux MyObjects sont chargés par des chargeurs de classes différents, ils sont considérés comme des classes différentes, même s'ils portent le même nom complet. Tenter de convertir la classe d'un objet en référence à une autre classe lèvera une ClassCastException.
Il est possible de contourner cette limitation, mais vous devez modifier votre code de deux manières :

  1. Utiliser l'interface comme type de variable et recharger uniquement la classe d'implémentation

  2. Utiliser la superclasse comme type de variable et recharger uniquement les sous-classes

Voici l'exemple de code :

MyObjectInterface object = (MyObjectInterface)
    myClassReloadingFactory.newInstance("com.jenkov.MyObject");
MyObjectSuperclass object = (MyObjectSuperclass)
    myClassReloadingFactory.newInstance("com.jenkov.MyObject");

Si le type de variable est une interface ou une superclasse, le code ci-dessus s'exécutera normalement et l'interface ou la superclasse ne sera pas rechargée lorsque l'implémentation ou la sous-classe sera rechargée.
Pour que le code ci-dessus fonctionne correctement, vous devez bien sûr implémenter votre propre chargeur de classe afin que l'interface ou la super classe soit chargée par sa classe parent. Lorsque votre chargeur de classe est invité à charger MyObject, il lui sera également demandé de charger l'interface MyObjectInterface ou la classe MyObjectSuperclass, car elles sont référencées en interne par la classe MyObject. Votre chargeur de classe doit déléguer le chargement de classe au même chargeur de classe qui a chargé l'interface ou la superclasse.

类加载器加载/重新加载示例

上文包含了很多内容。让我们看一下简单的示例。下面是一个简单的ClassLoader子类。注意它如何将类加载委托给它的父类,除了它想要重装的一个类之外。如果类加载被委派给了它的父类,它以后将不能被重新加载。记住,一个类只能被同一个ClassLoader实例加载。
如前所述,这只是一个示例,它显示了类加载器的行为的基本知识。这并不是一个你的类加载器的生产就绪的模板。你的类加载器可能并不仅限于一个类,可能是一个你想要重新加载的类的集合。此外,你也不能硬编码class path。

public class MyClassLoader extends ClassLoader{

    public MyClassLoader(ClassLoader parent) {
        super(parent);
    }

    public Class loadClass(String name) throws ClassNotFoundException {
        if(!"reflection.MyObject".equals(name))
                return super.loadClass(name);

        try {
            String url = "file:C:/data/projects/tutorials/web/WEB-INF/" +
                            "classes/reflection/MyObject.class";
            URL myUrl = new URL(url);
            URLConnection connection = myUrl.openConnection();
            InputStream input = connection.getInputStream();
            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
            int data = input.read();

            while(data != -1){
                buffer.write(data);
                data = input.read();
            }

            input.close();

            byte[] classData = buffer.toByteArray();

            return defineClass("reflection.MyObject",
                    classData, 0, classData.length);

        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }

}

下面是使用MyClassLoader的示例:

public static void main(String[] args) throws
    ClassNotFoundException,
    IllegalAccessException,
    InstantiationException {

    ClassLoader parentClassLoader = MyClassLoader.class.getClassLoader();
    MyClassLoader classLoader = new MyClassLoader(parentClassLoader);
    Class myObjectClass = classLoader.loadClass("reflection.MyObject");

    AnInterface2       object1 =
            (AnInterface2) myObjectClass.newInstance();

    MyObjectSuperClass object2 =
            (MyObjectSuperClass) myObjectClass.newInstance();

    //create new class loader so classes can be reloaded.
    classLoader = new MyClassLoader(parentClassLoader);
    myObjectClass = classLoader.loadClass("reflection.MyObject");

    object1 = (AnInterface2)       myObjectClass.newInstance();
    object2 = (MyObjectSuperClass) myObjectClass.newInstance();

}

reflection.MyObject类是由自定义类加载器加载的。注意,它是如何继承一个超类、实现一个接口的。这只是为了这个例子。在你的代码中,只需要两个中的一个,继承超类或实现接口。

public class MyObject extends MyObjectSuperClass implements AnInterface2{
    //... body of class ... override superclass methods
    //    or implement interface methods
}

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer