Le chargeur de classe est un concept très important en Java™. Le chargeur de classe est responsable du chargement du code d'octet des classes Java dans la machine virtuelle Java. Cet article présente d'abord en détail les concepts de base des chargeurs de classes Java, y compris le mode proxy, le processus spécifique de chargement des classes et des chargeurs de classes de contexte de thread, etc., puis présente comment pour développer votre propre chargeur de classes, et introduit enfin l'application du chargeur de classes dans les conteneurs Web et OSGi™.
Développez et déployez votre prochaine application sur la plateforme cloud IBM Bluemix.
Commencez votre essai
Le chargeur de classes est une innovation du langage Java et l'une des raisons importantes de la popularité du langage Java. Il permet aux classes Java d'être chargées dynamiquement dans la machine virtuelle Java et exécutées. Les chargeurs de classes existent depuis le JDK 1.0 et ont été initialement développés pour répondre aux besoins des applets Java. Java Applet doit télécharger les fichiers de classe Java de la télécommande vers le navigateur et les exécuter. Les chargeurs de classes sont désormais largement utilisés dans les conteneurs Web et OSGi. D'une manière générale, les développeurs d'applications Java n'ont pas besoin d'interagir directement avec les chargeurs de classes. Le comportement par défaut de la machine virtuelle Java est suffisant pour répondre aux besoins de la plupart des situations. Cependant, si vous rencontrez une situation dans laquelle vous devez interagir avec le chargeur de classe et que vous ne savez pas grand-chose sur le mécanisme du chargeur de classe, il est facile de passer beaucoup de temps au Débogage ClassNotFoundException et NoClassDefFoundError et autres exceptions. Cet article présentera en détail le chargeur de classes Java pour aider les lecteurs à comprendre en profondeur ce concept important du langage Java. Ci-dessous, nous introduisons d’abord quelques concepts de base connexes.
Concept de base du chargeur de classe
Comme son nom l'indique, le chargeur de classe est utilisé pour charger des classes Java dans la machine virtuelle Java. De manière générale, la machine virtuelle Java utilise les classes Java de la manière suivante : Les programmes sources Java (fichiers .java) sont convertis en byte codes Java (fichiers .class) après avoir été compilés par un compilateur Java. Le chargeur de classe est chargé de lire le byte code Java et de le convertir en une instance de la classe java.lang.Class. Chacune de ces instances représente une classe Java. Un objet de cette classe peut être créé via la méthode newInstance() de cette instance. La situation réelle peut être plus compliquée. Par exemple, le byte code Java peut être généré dynamiquement via des outils ou téléchargé via le réseau.
Fondamentalement, tous les chargeurs de classe sont une instance de la classe java.lang.ClassLoader. Cette classe Java est décrite en détail ci-dessous.
java.lang.ClassLoader**Introduction à la classe**
La responsabilité fondamentale de la classe java.lang.ClassLoader est de trouver ou de générer le code d'octet correspondant en fonction du nom d'un élément spécifié. class. , puis définissez une classe Java à partir de ces codes d'octet, c'est-à-dire une instance de la classe java.lang.Class. De plus, ClassLoader est également responsable du chargement des ressources requises par les applications Java, telles que les fichiers image et les fichiers de configuration, etc. Cependant, cet article ne traite que de sa fonction de chargement des classes. Afin de remplir la responsabilité du chargement des classes, ClassLoader fournit une série de méthodes, les méthodes les plus importantes sont présentées dans le tableau 1. Les détails de ces méthodes sont décrits ci-dessous.
Tableau 1. Méthodes liées au chargement des classes dans ClassLoader
Description de la méthode
getParent()
Renvoie le chargeur de classe parent de ce chargeur de classe.
loadClass(String name)
Charge la classe nommée name et le résultat renvoyé est une instance de la classe java.lang.Class.
findClass(String name)
Recherchez la classe nommée name et le résultat renvoyé est une instance de la classe java.lang.Class.
findLoadedClass(String name)
Recherchez la classe chargée nommée name, et le résultat renvoyé est une instance de la classe java.lang.Class.
defineClass(String name, byte[] b, int off, int len)
Convertir le contenu de l'octet array b en une classe Java et renvoyer le résultat Est une instance de la classe java.lang.Class. Cette méthode est déclarée final.
resolveClass(Class> c)
Liez la classe Java spécifiée.
Pour les méthodes données dans le tableau 1, la valeur du paramètre name représentant le nom de la classe est le nom binaire de la classe. Ce qu'il faut noter, c'est la représentation des classes internes, telles que com.example.Sample$1 et com.example.Sample$Inner. Ces méthodes seront expliquées plus en détail ci-dessous lorsque le mécanisme de fonctionnement du chargeur de classe sera introduit. Ce qui suit décrit la structure organisationnelle arborescente du chargeur de classe.
Structure organisationnelle arborescente des chargeurs de classes
Les chargeurs de classes en Java peuvent être grossièrement divisés en deux catégories, l'une est fournie par le système et l'autre est écrite par les développeurs d'applications Java. Il existe trois chargeurs de classes principaux fournis par le système :
bootstrap chargeur de classe) : il est utilisé pour charger la bibliothèque principale de Java et est implémenté dans le code natif Inherits de java.lang. .ClassLoader. Chargeur de classes d'extensions : il est utilisé pour charger les bibliothèques d'extensions Java. L'implémentation de la machine virtuelle Java fournit un répertoire de bibliothèques d'extensions. Le chargeur de classes recherche et charge les classes Java dans ce répertoire.
Chargeur de classes système : il charge les classes Java en fonction du chemin de classe (CLASSPATH) de l'application Java. D'une manière générale, les classes d'application Java sont chargées par celui-ci. Il peut être obtenu via ClassLoader.getSystemClassLoader().
À l'exception du chargeur de classe bootstrap, tous les chargeurs de classe ont un chargeur de classe parent. Il peut être obtenu via la méthode getParent() donnée dans le tableau 1. Pour le chargeur de classe fourni par le système, le chargeur de classe parent du chargeur de classe système est le chargeur de classe d'extension, et le chargeur de classe parent du chargeur de classe d'extension est le chargeur de classe de démarrage pour les chargeurs de classe écrits par les développeurs. Par exemple, son parent ; le chargeur de classe est le chargeur de classe qui charge les classes Java de ce chargeur de classe. Parce que la classe Java du chargeur de classe, comme
les autres classes Java, est également chargée par le chargeur de classe. De manière générale, le chargeur de classe parent d'un chargeur de classe écrit par un développeur est le chargeur de classe système. Les chargeurs de classes sont organisés de cette manière pour former une arborescence. Le nœud racine de l'arborescence est le chargeur de classe de démarrage. La figure 1 montre un diagramme de structure organisationnelle typique d'un chargeur de classe, dans lequel la flèche pointe vers le chargeur de classe parent.
Figure 1. Diagramme schématique de la structure organisationnelle de l'arborescence du chargeur de classe
La liste de codes 1 montre la structure organisationnelle arborescente du chargeur de classe.
Listing 1. Démonstration de l'organisation arborescente des chargeurs de classe
public class ClassLoaderTree { public static void main(String[] args) { ClassLoader loader = ClassLoaderTree.class.getClassLoader(); while (loader != null) { System.out.println(loader.toString()); loader = loader.getParent(); } } }
Référence, cette référence peut être obtenue via la méthode getClassLoader(). Dans la liste de codes 1, la méthode getParent() est appelée récursivement pour afficher tous les chargeurs de classe parent. Les résultats d'exécution de la liste de codes 1 sont affichés dans la liste de codes 2.
Listing 2. Résultats d'exécution démontrant la structure arborescente du chargeur de classe sun.misc.Launcher$AppClassLoader@9304b1
sun.misc.Launcher$ExtClassLoader@190d11
Comme le montre la liste de codes 2, la première sortie est le chargeur de classe de la classe ClassLoaderTree, c'est-à-dire le chargeur de classe système. Il s'agit d'une instance de la classe sun.misc.Launcher$AppClassLoader ; la deuxième sortie est le chargeur de classe d'extension, qui est une instance de la classe sun.misc.Launcher$ExtClassLoader. Il convient de noter que le chargeur de classe de démarrage n'est pas affiché ici car certaines implémentations du JDK renvoient null lorsque le chargeur de classe parent est le chargeur de classe de démarrage.
Après avoir compris la structure organisationnelle arborescente du chargeur de classe, ce qui suit présente le mode proxy du chargeur de classe.
Mode proxy du chargeur de classeLorsqu'un chargeur de classe essaie de trouver le code d'octet d'une classe par lui-même et le définit, il fera d'abord un proxy vers son chargeur de classe parent, et la classe parent Le chargeur de classe essaiera d'abord de charger cette classe, et ainsi de suite. Avant de présenter la motivation derrière le modèle de proxy, nous devons d'abord expliquer comment la machine virtuelle Java détermine que deux classes Java sont identiques. La machine virtuelle Java vérifie non seulement si les noms complets des classes sont identiques, mais également si les chargeurs de classes qui chargent cette classe sont les mêmes. Deux classes ne sont considérées comme identiques que si toutes deux sont identiques. Même si le même byte code est chargé par différents chargeurs de classes, les classes obtenues sont différentes. Par exemple, une classe Java com.example.Sample génère un fichier de code d'octet Sample.class après compilation. Deux chargeurs de classe différents, ClassLoaderA et ClassLoaderB, lisent respectivement ce fichier Sample.class et définissent deux instances de la classe java.lang.Class pour représenter cette classe. Ces deux instances ne sont pas identiques. Pour la machine virtuelle Java, ce sont des classes différentes. Tenter d'attribuer des objets de ces deux classes l'un à l'autre lèvera une exception d'exécution ClassCastException. Ce qui suit est une explication détaillée à travers des exemples. La classe Java com.example.Sample est donnée dans le listing de codes 3.
Listing 3. com.example.Sample class
package com.example; public class Sample { private Sample instance; public void setSample(Object instance) { this.instance = (Sample) instance; } }
如 代码清单 3所示,com.example.Sample类的方法 setSample接受一个 java.lang.Object类型的参数,并且会把该参数强制转换成com.example.Sample类型。测试 Java 类是否相同的代码如 代码清单 4所示。
清单 4. 测试 Java 类是否相同
public void testClassIdentity() { String classDataRootPath = "C:\workspace\Classloader\classData"; File SystemClassLoader fscl1 = new FileSystemClassLoader(classDataRootPath); FileSystemClassLoader fscl2 = new FileSystemClassLoader(classDataRootPath); String className = "com.example.Sample"; try { Class<?> class1 = fscl1.loadClass(className); Object obj1 = class1.newInstance(); Class<?> class2 = fscl2.loadClass(className); Object obj2 = class2.newInstance(); Method setSampleMethod = class1.getMethod("setSample", java.lang.Object.class); setSampleMethod.invoke(obj1, obj2); } catch (Exception e) { e.printStackTrace(); } }
代码清单 4中使用了类 FileSystemClassLoader的两个不同实例来分别加载类 com.example.Sample,得到了两个不同的java.lang.Class的实例,接着通过 newInstance()方法分别生成了两个类的对象 obj1和 obj2,最后通过 Java 的反射 API 在对象 obj1上调用方法 setSample,试图把对象 obj2赋值给 obj1内部的 instance对象。代码清单 4的运行结果如 代码清单 5所示。
清单 5. 测试 Java 类是否相同的运行结果
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at classloader.ClassIdentity.testClassIdentity(ClassIdentity.java:26)
at classloader.ClassIdentity.main(ClassIdentity.java:9)
Caused by: java.lang.ClassCastException: com.example.Sample
cannot be cast to com.example.Sample
at com.example.Sample.setSample(Sample.java:7)
... 6 more
从 代码清单 5给出的运行结果可以看到,运行时抛出了 java.lang.ClassCastException异常。虽然两个对象 obj1和 obj2的类的名字相同,但是这两个类是由不同的类加载器实例来加载的,因此不被 Java 虚拟机认为是相同的。
了解了这一点之后,就可以理解代理模式的设计动机了。代理模式是为了保证 Java 核心库的类型安全。所有 Java 应用都至少需要引用java.lang.Object类,也就是说在运行的时候,java.lang.Object这个类需要被加载到 Java 虚拟机中。如果这个加载过程由 Java 应用自己的类加载器来完成的话,很可能就存在多个版本的 java.lang.Object类,而且这些类之间是不兼容的。通过代理模式,对于 Java 核心库的类的加载工作由引导类加载器来统一完成,保证了 Java 应用所使用的都是同一个版本的 Java 核心库的类,是互相兼容的。
不同的类加载器为相同名称的类创建了额外的名称空间。相同名称的类可以并存在 Java 虚拟机中,只需要用不同的类加载器来加载它们即可。不同类加载器加载的类之间是不兼容的,这就相当于在 Java 虚拟机内部创建了一个个相互隔离的 Java 类空间。这种技术在许多框架中都被用到,后面会详细介绍。
下面具体介绍类加载器加载类的详细过程。
加载类的过程
在前面介绍类加载器的代理模式的时候,提到过类加载器会首先代理给其它类加载器来尝试加载某个类。这就意味着真正完成类的加载工作的类加载器和启动这个加载过程的类加载器,有可能不是同一个。真正完成类的加载工作是通过调用 defineClass来实现的;而启动类的加载过程是通过调用 loadClass来实现的。前者称为一个类的定义加载器(defining loader),后者称为初始加载器(initiating loader)。在 Java 虚拟机判断两个类是否相同的时候,使用的是类的定义加载器。也就是说,哪个类加载器启动类的加载过程并不重要,重要的是最终定义这个类的加载器。两种类加载器的关联之处在于:一个类的定义加载器是它引用的其它类的初始加载器。如类 com.example.Outer引用了类com.example.Inner,则由类 com.example.Outer的定义加载器负责启动类 com.example.Inner的加载过程。
方法 loadClass()抛出的是 java.lang.ClassNotFoundException异常;方法 defineClass()抛出的是java.lang.NoClassDefFoundError异常。
类加载器在成功加载某个类之后,会把得到的 java.lang.Class类的实例缓存起来。下次再请求加载该类的时候,类加载器会直接使用缓存的类的实例,而不会尝试再次加载。也就是说,对于一个类加载器实例来说,相同全名的类只加载一次,即 loadClass方法不会被重复调用。
下面讨论另外一种类加载器:线程上下文类加载器。
线程上下文类加载器
Le chargeur de classe de contexte de thread (chargeur de classe de contexte) a été introduit à partir du JDK 1.2. Les méthodes getContextClassLoader() et setContextClassLoader(ClassLoader cl) dans la classe java.lang.Thread sont utilisées pour obtenir et définir le chargeur de classe contextuelle du thread. S'il n'est pas défini via la méthode setContextClassLoader(ClassLoader cl), le thread héritera du chargeur de classe contextuelle de son thread parent. Le chargeur de classe contextuelle pour le thread initial sur lequel une application Java s'exécute est le chargeur de classe système. Le code exécuté dans un thread peut charger des classes et des ressources via ce type de chargeur.
Le mode proxy du chargeur de classe mentionné ci-dessus ne peut pas résoudre tous les problèmes des chargeurs de classe rencontrés dans le développement d'applications Java. Java fournit de nombreuses interfaces de fournisseurs de services (Service Provider Interface, SPI), permettant à des tiers de fournir des implémentations pour ces interfaces. Les SPI courants incluent JDBC, JCE, JNDI, JAXP et JBI. Ces interfaces SPI sont fournies par la bibliothèque principale Java. Par exemple, la définition de l'interface SPI de JAXP est incluse dans le package javax.xml.parsers. Ces codes d'implémentation SPI sont susceptibles d'être inclus sous forme de packages jar dont dépendent les applications Java et peuvent être trouvés via le chemin de classe (CLASSPATH), comme le package jar inclus dans Apache Xerces qui implémente JAXP SPI. Le code de l'interface SPI doit souvent charger des classes d'implémentation spécifiques. Par exemple, la méthode newInstance() de la classe javax.xml.parsers.DocumentBuilderFactory dans JAXP est utilisée pour générer une nouvelle instance de DocumentBuilderFactory. La vraie classe de l'instance ici est héritée de javax.xml.parsers.DocumentBuilderFactory, fournie par l'implémentation SPI. Par exemple, dans Apache Xerces, la classe implémentée est org.apache.xerces.jaxp.DocumentBuilderFactoryImpl. Le problème est que l'interface SPI fait partie de la bibliothèque principale Java et est chargée par le chargeur de classe de démarrage ; les classes Java implémentées par SPI sont généralement chargées par le chargeur de classe système. Le chargeur de classe de démarrage ne peut pas trouver la classe d'implémentation SPI car il charge uniquement la bibliothèque principale de Java. Il ne peut pas non plus se connecter au chargeur de classe système car il s'agit du chargeur de classe ancêtre du chargeur de classe système. En d’autres termes, le mode proxy du chargeur de classe ne peut pas résoudre ce problème. Le chargeur de classe de contexte de thread résout exactement ce problème. Si aucun paramètre n'est défini, le chargeur de classe de contexte du thread d'application Java utilise par défaut le chargeur de classe de contexte système. En utilisant le chargeur de classe de contexte de thread dans le code de l'interface SPI, vous pouvez charger avec succès la classe d'implémentation SPI. Le chargeur de classe de contexte de thread est utilisé dans de nombreuses implémentations SPI. Ce qui suit présente une autre méthode de chargement des classes : Class.forName.
Class.forName
Class.forName est une méthode statique
qui peut également être utilisée pour charger des classes. Cette méthode a deux formes : Class.forName (String name, boolean initialize, ClassLoader loader) et Class.forName (String className). La première forme du nom du paramètre représente le nom complet de la classe ; initialize indique s'il faut initialiser la classe ; le chargeur de classe représente le chargeur de classe utilisé lors du chargement. La deuxième forme équivaut à définir la valeur du paramètre initialize sur true et la valeur de loader sur le chargeur de classe de la classe actuelle. Une utilisation très courante de Class.forName est lors du chargement de la base de données pilote . Par exemple, Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance() est utilisé pour charger le pilote de la base de données Apache Derby. Après avoir présenté les concepts de base liés aux chargeurs de classes, voici comment développer votre propre chargeur de classes.
Bien que dans la plupart des cas, l'implémentation du chargeur de classe fournie par le système par défaut puisse répondre aux besoins. Mais dans certains cas, vous devez quand même développer votre propre chargeur de classes pour votre application. Par exemple, votre application transmet des codes d'octet de classe Java via le réseau Pour garantir la sécurité, ces codes d'octet sont cryptés. À ce stade, vous avez besoin de votre propre chargeur de classe pour lire le code d'octet crypté à partir d'une certaine adresse réseau, puis le déchiffrer et le vérifier, et enfin définir la classe à exécuter dans la machine virtuelle Java. Ce qui suit illustrera le développement de chargeurs de classes à travers deux exemples spécifiques.
Système de fichiersChargeur de classeLe premier chargeur de classe est utilisé pour charger le code d'octet Java stocké sur le système de fichiers. L'implémentation complète est présentée dans la liste de codes 6.
public class FileSystemClassLoader extends ClassLoader { private String rootDir; public FileSystemClassLoader(String rootDir) { this.rootDir = rootDir; } protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] classData = getClassData(name); if (classData == null) { throw new ClassNotFoundException(); } else { return defineClass(name, classData, 0, classData.length); } } private byte[] getClassData(String className) { String path = classNameToPath(className); try { InputStream ins = new FileInputStream(path); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int bufferSize = 4096; byte[] buffer = new byte[bufferSize]; int bytesNumRead = 0; while ((bytesNumRead = ins.read(buffer)) != -1) { baos.write(buffer, 0, bytesNumRead); } return baos.toByteArray(); } catch (IOException e) { e.printStackTrace(); } return null; } private String classNameToPath(String className) { return rootDir + File.separatorChar + className.replace('.', File.separatorChar) + ".class"; } }
如 代码清单 6所示,类 FileSystemClassLoader继承自类 java.lang.ClassLoader。在 表 1中列出的 java.lang.ClassLoader类的常用方法中,一般来说,自己开发的类加载器只需要覆写 findClass(String name)方法即可。java.lang.ClassLoader类的方法loadClass()封装了前面提到的代理模式的实现。该方法会首先调用 findLoadedClass()方法来检查该类是否已经被加载过;如果没有加载过的话,会调用父类加载器的 loadClass()方法来尝试加载该类;如果父类加载器无法加载该类的话,就调用 findClass()方法来查找该类。因此,为了保证类加载器都正确实现代理模式,在开发自己的类加载器时,最好不要覆写 loadClass()方法,而是覆写 findClass()方法。
类 FileSystemClassLoader的 findClass()方法首先根据类的全名在硬盘上查找类的字节代码文件(.class 文件),然后读取该文件内容,最后通过 defineClass()方法来把这些字节代码转换成 java.lang.Class类的实例。
网络类加载器
下面将通过一个网络类加载器来说明如何通过类加载器来实现组件的动态更新。即基本的场景是:Java 字节代码(.class)文件存放在服务器上,客户端通过网络的方式获取字节代码并执行。当有版本更新的时候,只需要替换掉服务器上保存的文件即可。通过类加载器可以比较简单的实现这种需求。
类 NetworkClassLoader负责通过网络下载 Java 类字节代码并定义出 Java 类。它的实现与 FileSystemClassLoader类似。在通过NetworkClassLoader加载了某个版本的类之后,一般有两种做法来使用它。第一种做法是使用 Java 反射 API。另外一种做法是使用接口。需要注意的是,并不能直接在客户端代码中引用从服务器上下载的类,因为客户端代码的类加载器找不到这些类。使用 Java 反射 API 可以直接调用 Java 类的方法。而使用接口的做法则是把接口的类放在客户端中,从服务器上加载实现此接口的不同版本的类。在客户端通过相同的接口来使用这些实现类。网络类加载器的具体代码见 下载。
在介绍完如何开发自己的类加载器之后,下面说明类加载器和 Web 容器的关系。
类加载器与 Web 容器
对于运行在 Java EE™容器中的 Web 应用来说,类加载器的实现方式与一般的 Java 应用有所不同。不同的 Web 容器的实现方式也会有所不同。以 Apache Tomcat 来说,每个 Web 应用都有一个对应的类加载器实例。该类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的。这是 Java Servlet 规范中的推荐做法,其目的是使得 Web 应用自己的类的优先级高于 Web 容器提供的类。这种代理模式的一个例外是:Java 核心库的类是不在查找范围之内的。这也是为了保证 Java 核心库的类型安全。
绝大多数情况下,Web 应用的开发人员不需要考虑与类加载器相关的细节。下面给出几条简单的原则:
每个 Web 应用自己的 Java 类文件和使用的库的 jar 包,分别放在 WEB-INF/classes和 WEB-INF/lib目录下面。
多个应用共享的 Java 类文件和 jar 包,分别放在 Web 容器指定的由所有 Web 应用共享的目录下面。
当出现找不到类的错误时,检查当前类的类加载器和当前线程的上下文类加载器是否正确。
在介绍完类加载器与 Web 容器的关系之后,下面介绍它与 OSGi 的关系。
Class Loaders et OSGi
OSGi™ est un système de modules dynamiques sur Java. Il fournit aux développeurs un environnement d'exécution orienté services et basé sur les composants, et fournit un moyen standard de gérer le cycle de vie du logiciel. OSGi a été implémenté et déployé sur de nombreux produits et a reçu un large soutien au sein de la communauté open source. Eclipse est construit sur la base de la technologie OSGi.
Chaque module (bundle) d'OSGi contient des packages et des classes Java. Un module peut déclarer les packages Java et les classes d'autres modules dont il dépend (via Import-Package), et il peut également déclarer et exporter (exporter) ses propres packages et classes pour une utilisation par d'autres modules (via Export-Package). . Cela signifie que vous devez pouvoir masquer et partager certains packages et classes Java au sein d'un module. Ceci est réalisé grâce au mécanisme de chargement de classe unique d'OSGi. Chaque module d'OSGi possède un chargeur de classe correspondant. Il est responsable du chargement des packages et classes Java contenus dans le module lui-même. Lorsqu'il doit charger des classes à partir de la bibliothèque principale Java (packages et classes commençant par Java), il se connectera au chargeur de classe parent (généralement le chargeur de classe de démarrage) pour le terminer. Lorsqu'il doit charger une classe Java importée, il délègue au module qui exporte la classe Java le soin de terminer le chargement. Les modules peuvent également déclarer explicitement certains packages et classes Java qui doivent être chargés par le chargeur de classe parent. Définissez simplement la valeur de l'attribut système org.osgi.framework.bootdelegation.
Supposons qu'il existe deux modules bundleA et bundleB, qui ont tous deux leurs propres chargeurs de classe correspondants classLoaderA et classLoaderB. BundleA contient la classe com.bundleA.Sample et la classe est déclarée exportée, ce qui signifie qu'elle peut être utilisée par d'autres modules. bundleB déclare qu'il importe la classe com.bundleA.Sample fournie par bundleA et contient une classe com.bundleB.NewSample qui hérite de com.bundleA.Sample. Lorsque bundleB démarre, son chargeur de classe classLoaderB doit charger la classe com.bundleB.NewSample, qui à son tour doit charger la classe com.bundleA.Sample. Puisque bundleB déclare la classe com.bundleA.Sample à importer, classLoaderB délègue le travail de chargement de la classe com.bundleA.Sample au chargeur de classe classLoaderA de bundleA qui exporte cette classe. classLoaderA recherche la classe com.bundleA.Sample dans son module et la définit. L'instance de classe résultante com.bundleA.Sample peut être utilisée par tous les modules qui déclarent cette classe. Pour les classes commençant par Java, elles sont chargées par le chargeur de classe parent. Si la propriété système org.osgi.framework.bootdelegation=com.example.core.* est déclarée, alors les classes du package com.example.core sont complétées par le chargeur de classe parent.
La structure du chargeur de classes du module OSGi permet à différentes versions d'une classe de coexister dans la machine virtuelle Java, apportant une grande flexibilité. Cependant, cette différence apportera également quelques problèmes aux développeurs, notamment lorsque le module doit utiliser des bibliothèques fournies par des tiers. Voici quelques bonnes suggestions :
Si une bibliothèque de classes est utilisée par un seul module, placez le package jar de la bibliothèque de classes dans le module et spécifiez-le dans le Bundle-ClassPath.
Si une bibliothèque de classes est partagée par plusieurs modules, vous pouvez créer un module distinct pour cette bibliothèque de classes et déclarer les packages Java que les autres modules doivent utiliser comme exportés. D'autres déclarations de module importent ces classes.
Si la bibliothèque de classes fournit une interface SPI et utilise le chargeur de classe de contexte de thread pour charger la classe Java implémentée par SPI, la classe Java risque de ne pas être trouvée. Si une exception NoClassDefFoundError se produit, vérifiez d'abord si le chargeur de classe contextuelle du thread actuel est correct. Le chargeur de classe peut être obtenu via Thread.currentThread().getContextClassLoader(). Ce chargeur de classe doit être le chargeur de classe correspondant pour ce module. Sinon, vous pouvez d'abord obtenir le chargeur de classe correspondant au module via class.getClassLoader(), puis définir le chargeur de classe contextuelle du thread actuel via Thread.currentThread().setContextClassLoader().
Résumé
Le chargeur de classe est une innovation dans le langage Java. Il permet d'installer et de mettre à jour dynamiquement des composants logiciels. Cet article présente en détail les sujets liés au chargeur de classe, notamment les concepts de base, le mode proxy, le chargeur de classe de contexte de thread, la relation avec les conteneurs Web et OSGi, etc. Lorsque les développeurs rencontrent des exceptions telles que ClassNotFoundException et NoClassDefFoundError, ils doivent vérifier le chargeur de classe de la classe qui a levé l'exception et le chargeur de classe contextuelle du thread actuel pour découvrir le problème. Lorsque vous développez votre propre chargeur de classes, vous devez faire attention à la coordination avec la structure organisationnelle existante du chargeur de classes.
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!