Java Classloader
Java ClassLoader është një nga komponentët thelbësorë, por që përdoret rrallë në zhvillimin e projektit. Unë kurrë nuk e kam zgjeruar ClassLoader në asnjë nga projektet e mia. Por, ideja për të pasur ClassLoader timin që mund të personalizojë ngarkimin e klasës Java është emocionuese. Ky artikull do të sigurojë një përmbledhje të Java ClassLoader dhe më pas do të ecë përpara për të krijuar një ngarkues të personalizuar të klasës në Java.
Çfarë është Java ClassLoader?
Ne e dimë se Programi Java funksionon në Java Virtual Machine (JVM). Kur përpilojmë një klasë Java, JVM krijon bytekodin, i cili është i pavarur nga platforma dhe nga makina. Bajtkodi ruhet në një skedar .class. Kur përpiqemi të përdorim një klasë, ClassLoader e ngarkon atë në memorie.
Llojet e integruara të Classloader-it
Ekzistojnë tre lloje të ClassLoader-it të integruar në Java.
- Bootstrap Class Loader – Ai ngarkon klasat e brendshme të JDK. Ai ngarkon rt.jar dhe klasa të tjera bazë, për shembull java.lang.* klasat e paketave.
- Ngarkuesi i klasës së zgjerimeve – Ai ngarkon klasa nga drejtoria e shtesave JDK, zakonisht direktoria $JAVA_HOME/lib/ext.
- Ngarkuesi i klasës së sistemit – Ky ngarkues i klasës ngarkon klasa nga shtegu aktual i klasës. Ne mund të caktojmë rrugën e klasës gjatë thirrjes së një programi duke përdorur opsionin e linjës së komandës -cp ose -classpath.
Hierarkia e ClassLoader
ClassLoader është hierarkik në ngarkimin e një klase në memorie. Sa herë që ngrihet një kërkesë për të ngarkuar një klasë, ajo ia delegon atë ngarkuesit prind të klasës. Kështu ruhet unike në mjedisin e ekzekutimit. Nëse ngarkuesi i klasës mëmë nuk e gjen klasën, atëherë vetë ngarkuesi i klasës përpiqet të ngarkojë klasën. Le ta kuptojmë këtë duke ekzekutuar programin java më poshtë.
package com.journaldev.classloader;
public class ClassLoaderTest {
public static void main(String[] args) {
System.out.println("class loader for HashMap: "
+ java.util.HashMap.class.getClassLoader());
System.out.println("class loader for DNSNameService: "
+ sun.net.spi.nameservice.dns.DNSNameService.class
.getClassLoader());
System.out.println("class loader for this class: "
+ ClassLoaderTest.class.getClassLoader());
System.out.println(com.mysql.jdbc.Blob.class.getClassLoader());
}
}
Prodhimi:
class loader for HashMap: null
class loader for DNSNameService: sun.misc.Launcher$ExtClassLoader@7c354093
class loader for this class: sun.misc.Launcher$AppClassLoader@64cbbe37
sun.misc.Launcher$AppClassLoader@64cbbe37
Si funksionon Java ClassLoader?
Le të kuptojmë funksionimin e ngarkuesve të klasës nga dalja e programit të mësipërm.
- java.util.HashMap ClassLoader po vjen si null, gjë që pasqyron Bootstrap ClassLoader. Klasa ClassLoader e klasës DNSNameService është ExtClassLoader. Meqenëse vetë klasa është në CLASSPATH, System ClassLoader e ngarkon atë.
- Kur përpiqemi të ngarkojmë HashMap, Sistemi ynë ClassLoader e delegon atë te Extension ClassLoader. Ngarkuesi i klasës së zgjerimit e delegon atë te Bootstrap ClassLoader. Ngarkuesi i klasës bootstrap gjen klasën HashMap dhe e ngarkon atë në memorien JVM.
- I njëjti proces ndiqet për klasën DNSNameService. Por, Bootstrap ClassLoader nuk është në gjendje ta lokalizojë atë pasi është në
$JAVA_HOME/lib/ext/dnsns.jar
. Prandaj, ngarkohet nga Extensions Classloader. - Klasa Blob përfshihet në kavanozin MySql JDBC Connector (mysql-connector-java-5.0.7-bin.jar), i cili është i pranishëm në rrugën e ndërtimit të projektit. Po ngarkohet gjithashtu nga Klasifikimi i Sistemit.
- Klasat e ngarkuara nga një ngarkues i klasës fëmijë kanë dukshmëri në klasat e ngarkuara nga ngarkuesit e klasës mëmë. Pra, klasat e ngarkuara nga System Classloader kanë dukshmëri në klasat e ngarkuara nga Extensions dhe Bootstrap Classloader.
- Nëse ka ngarkues të klasës vëllezër, atëherë ata nuk mund të kenë qasje në klasat e ngarkuara nga njëri-tjetri.
Pse të shkruani një Classloader të personalizuar në Java?
Java e parazgjedhur ClassLoader mund të ngarkojë klasa nga sistemi lokal i skedarëve, gjë që është mjaft e mirë për shumicën e rasteve. Por, nëse prisni një klasë në kohën e ekzekutimit ose nga serveri FTP ose nëpërmjet shërbimit të uebit të palës së tretë në kohën e ngarkimit të klasës, atëherë duhet të zgjeroni ngarkuesin ekzistues të klasës. Për shembull, AppletViewers ngarkojnë klasat nga një server në distancë.
Metodat Java Classloader
- Kur JVM kërkon për një klasë, ai thërret funksionin
loadClass()
të ClassLoader duke kaluar emrin plotësisht të klasifikuar të Klasës. - Funksioni loadClass() thërret metodën
findLoadedClass()
për të kontrolluar nëse klasa është ngarkuar tashmë apo jo. Kërkohet të shmanget ngarkimi i shumëfishtë i së njëjtës klasë. - Nëse Klasa nuk është e ngarkuar tashmë, atëherë ajo do t'ia delegojë kërkesën prindit ClassLoader për të ngarkuar klasën.
- Nëse ClassLoader prind nuk e gjen klasën, atëherë ai do të thërrasë metodën findClass() për të kërkuar klasat në sistemin e skedarëve.
Shembull Java Custom ClassLoader
1. CCLoader.java
Ky është ngarkuesi ynë i klasës me porosi me metodat e mëposhtme.
bajti privat[] loadClassFileData(Emri i vargut)
: Kjo metodë do të lexojë skedarin e klasës nga sistemi i skedarëve në grupin bajt.Klasa private> getClass(emri i vargut)
: Kjo metodë do të thërrasë funksionin loadClassFileData() dhe duke thirrur metodën prind defineClass(), do të gjenerojë klasën dhe do ta kthejë atë.< /li>Klasa publike> loadClass(Emri i vargut)
: Kjo metodë është përgjegjëse për ngarkimin e klasës. Nëse emri i klasës fillon me com.journaldev (Klasat tona të mostrës), atëherë ajo do ta ngarkojë atë duke përdorur metodën getClass() ose përndryshe do të thërrasë funksionin prind loadClass() për ta ngarkuar atë.CCLoader publik (prindi ClassLoader)
: Ky është konstruktori, i cili është përgjegjës për vendosjen e ClassLoader-it prind.
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
/**
* Our Custom ClassLoader to load the classes. Any class in the com.journaldev
* package will be loaded using this ClassLoader. For other classes, it will delegate the request to its Parent ClassLoader.
*
*/
public class CCLoader extends ClassLoader {
/**
* This constructor is used to set the parent ClassLoader
*/
public CCLoader(ClassLoader parent) {
super(parent);
}
/**
* Loads the class from the file system. The class file should be located in
* the file system. The name should be relative to get the file location
*
* @param name
* Fully Classified name of the class, for example, com.journaldev.Foo
*/
private Class getClass(String name) throws ClassNotFoundException {
String file = name.replace('.', File.separatorChar) + ".class";
byte[] b = null;
try {
// This loads the byte code data from the file
b = loadClassFileData(file);
// defineClass is inherited from the ClassLoader class
// that converts byte array into a Class. defineClass is Final
// so we cannot override it
Class c = defineClass(name, b, 0, b.length);
resolveClass(c);
return c;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
/**
* Every request for a class passes through this method. If the class is in
* com.journaldev package, we will use this classloader or else delegate the
* request to parent classloader.
*
*
* @param name
* Full class name
*/
@Override
public Class loadClass(String name) throws ClassNotFoundException {
System.out.println("Loading Class '" + name + "'");
if (name.startsWith("com.journaldev")) {
System.out.println("Loading Class using CCLoader");
return getClass(name);
}
return super.loadClass(name);
}
/**
* Reads the file (.class) into a byte array. The file should be
* accessible as a resource and make sure that it's not in Classpath to avoid
* any confusion.
*
* @param name
* Filename
* @return Byte array read from the file
* @throws IOException
* if an exception comes in reading the file
*/
private byte[] loadClassFileData(String name) throws IOException {
InputStream stream = getClass().getClassLoader().getResourceAsStream(
name);
int size = stream.available();
byte buff[] = new byte[size];
DataInputStream in = new DataInputStream(stream);
in.readFully(buff);
in.close();
return buff;
}
}
2. CCRun.java
Kjo është klasa jonë e provës me Java Reflection API për të thirrur metodat e saj.
import java.lang.reflect.Method;
public class CCRun {
public static void main(String args[]) throws Exception {
String progClass = args[0];
String progArgs[] = new String[args.length - 1];
System.arraycopy(args, 1, progArgs, 0, progArgs.length);
CCLoader ccl = new CCLoader(CCRun.class.getClassLoader());
Class clas = ccl.loadClass(progClass);
Class mainArgType[] = { (new String[0]).getClass() };
Method main = clas.getMethod("main", mainArgType);
Object argsArray[] = { progArgs };
main.invoke(null, argsArray);
// Below method is used to check that the Foo is getting loaded
// by our custom class loader i.e CCLoader
Method printCL = clas.getMethod("printCL", null);
printCL.invoke(null, new Object[0]);
}
}
3. Foo.java dhe Bar.java
Këto janë klasat tona të testimit që po ngarkohen nga ngarkuesi ynë i personalizuar i klasës. Ata kanë një metodë printCL()
, e cila thirret për të printuar informacionin e ClassLoader. Klasa Foo do të ngarkohet nga ngarkuesi ynë i personalizuar i klasës. Foo përdor klasën Bar, kështu që klasa Bar do të ngarkohet gjithashtu nga ngarkuesi ynë i personalizuar i klasës.
package com.journaldev.cl;
public class Foo {
static public void main(String args[]) throws Exception {
System.out.println("Foo Constructor >>> " + args[0] + " " + args[1]);
Bar bar = new Bar(args[0], args[1]);
bar.printCL();
}
public static void printCL() {
System.out.println("Foo ClassLoader: "+Foo.class.getClassLoader());
}
}
package com.journaldev.cl;
public class Bar {
public Bar(String a, String b) {
System.out.println("Bar Constructor >>> " + a + " " + b);
}
public void printCL() {
System.out.println("Bar ClassLoader: "+Bar.class.getClassLoader());
}
}
4. Hapat e ekzekutimit të Java Custom ClassLoader
Para së gjithash, ne do të përpilojmë të gjitha klasat përmes vijës së komandës. Pas kësaj, ne do të ekzekutojmë klasën CCRun duke kaluar tre argumente. Argumenti i parë është emri plotësisht i klasifikuar për klasën Foo që do të ngarkohet nga ngarkuesi ynë i klasës. Dy argumente të tjera i kalohen funksionit kryesor të klasës Foo dhe konstruktorit të Bar. Hapat e ekzekutimit dhe dalja do të jenë si më poshtë.
$ javac -cp . com/journaldev/cl/Foo.java
$ javac -cp . com/journaldev/cl/Bar.java
$ javac CCLoader.java
$ javac CCRun.java
CCRun.java:18: warning: non-varargs call of varargs method with inexact argument type for last parameter;
cast to java.lang.Class<?> for a varargs call
cast to java.lang.Class<?>[] for a non-varargs call and to suppress this warning
Method printCL = clas.getMethod("printCL", null);
^
1 warning
$ java CCRun com.journaldev.cl.Foo 1212 1313
Loading Class 'com.journaldev.cl.Foo'
Loading Class using CCLoader
Loading Class 'java.lang.Object'
Loading Class 'java.lang.String'
Loading Class 'java.lang.Exception'
Loading Class 'java.lang.System'
Loading Class 'java.lang.StringBuilder'
Loading Class 'java.io.PrintStream'
Foo Constructor >>> 1212 1313
Loading Class 'com.journaldev.cl.Bar'
Loading Class using CCLoader
Bar Constructor >>> 1212 1313
Loading Class 'java.lang.Class'
Bar ClassLoader: CCLoader@71f6f0bf
Foo ClassLoader: CCLoader@71f6f0bf
$
Nëse shikoni daljen, ai po përpiqet të ngarkojë klasën com.journaldev.cl.Foo
. Meqenëse po zgjeron klasën java.lang.Object, po përpiqet të ngarkojë klasën e objektit në fillim. Pra, kërkesa po vjen në metodën CCLoader loadClass, e cila po e delegon atë te klasa mëmë. Pra, ngarkuesit e klasës prind po ngarkojnë klasat Object, String dhe të tjera java. ClassLoader ynë po ngarkon vetëm klasën Foo and Bar nga sistemi i skedarëve. Është e qartë nga dalja e funksionit printCL(). Ne mund të ndryshojmë funksionalitetin loadClassFileData() për të lexuar grupin e bajteve nga FTP Server ose duke thirrur ndonjë shërbim të palës së tretë për të marrë grupin e bajtit të klasës në fluturim. Shpresoj se artikulli do të jetë i dobishëm për të kuptuar punën e Java ClassLoader dhe se si mund ta zgjerojmë atë për të bërë shumë më tepër sesa thjesht ta marrim atë nga sistemi i skedarëve.
Krijimi i ClassLoader-it të personalizuar si Classloader i parazgjedhur
Ne mund ta bëjmë ngarkuesin tonë të personalizuar të klasës si të paracaktuar kur JVM fillon duke përdorur opsionet Java. Për shembull, unë do të ekzekutoj programin ClassLoaderTest edhe një herë pasi të jap opsionin java classloader.
$ javac -cp .:../lib/mysql-connector-java-5.0.7-bin.jar com/journaldev/classloader/ClassLoaderTest.java
$ java -cp .:../lib/mysql-connector-java-5.0.7-bin.jar -Djava.system.class.loader=CCLoader com.journaldev.classloader.ClassLoaderTest
Loading Class 'com.journaldev.classloader.ClassLoaderTest'
Loading Class using CCLoader
Loading Class 'java.lang.Object'
Loading Class 'java.lang.String'
Loading Class 'java.lang.System'
Loading Class 'java.lang.StringBuilder'
Loading Class 'java.util.HashMap'
Loading Class 'java.lang.Class'
Loading Class 'java.io.PrintStream'
class loader for HashMap: null
Loading Class 'sun.net.spi.nameservice.dns.DNSNameService'
class loader for DNSNameService: sun.misc.Launcher$ExtClassLoader@24480457
class loader for this class: CCLoader@38503429
Loading Class 'com.mysql.jdbc.Blob'
sun.misc.Launcher$AppClassLoader@2f94ca6c
$
CCLoader po ngarkon klasën ClassLoaderTest sepse është në paketën com.journaldev
.
Ju mund të shkarkoni kodin shembull ClassLoader nga depoja jonë e GitHub.