- Sản phẩm & Dịch vụSản phẩm & Dịch vụ
- Giải phápGiải pháp
- Bảng giáBảng giá
- Công tyCông ty
- Tài liệuTài liệu
vi
vi
Chuyên mục với các bài viết hướng dẫn, nghiên cứu và phân tích chi tiết về kỹ thuật, các xu hướng công nghệ mới nhất dành cho lập trình viên.
Web Developer
I’m passionate about web development and sharing my insights through articles, with over 8 years of experience. I hope these sharings inspire you and help build a strong web development community. @#@ Tôi đam mê phát triển web và chia sẻ những hiểu biết của mình thông qua các bài viết, với hơn 8 năm kinh nghiệm. Tôi hy vọng những chia sẻ này sẽ truyền cảm hứng cho các bạn và giúp xây dựng một cộng đồng phát triển web mạnh mẽ.
Thảo luận (0)
Đăng nhập để thảo luận
Java ClassLoader là một trong những thành phần quan trọng nhưng hiếm khi được sử dụng trong quá trình phát triển dự án. Tôi chưa bao giờ mở rộng ClassLoader trong bất kỳ dự án nào của mình. Nhưng ý tưởng về việc có ClassLoader riêng có thể tùy chỉnh quá trình tải Java class thật thú vị. Bài viết này sẽ cung cấp cho bạn cái nhìn tổng quan về Java ClassLoader và sau đó sẽ tiến tới việc tạo một custom ClassLoader (ClassLoader tùy chình) trong Java.

Chúng ta biết rằng chương trình Java chạy trên JVM (Java Virtual Machine). Khi chúng ta biên dịch một Java Class, JVM tạo ra bytecode, là mã độc lập với nền tảng và máy. Loại mã này được lưu trữ trong một file .class. Khi chúng ta cố gắng sử dụng một class, ClassLoader sẽ tải nó vào bộ nhớ.
Có ba loại ClassLoader tích hợp sẵn trong Java.
rt.jar và các core class khác, ví dụ như các class trong package java.lang.*.ClassLoader có tính phân cấp trong việc tải một class vào bộ nhớ. Bất cứ khi nào có yêu cầu tải một class, nó sẽ ủy quyền cho parent ClassLoader (ClassLoader cha). Đây là cách mà tính duy nhất của các class được duy trì trong môi trường runtime. Nếu parent ClassLoader không tìm thấy class thì ClassLoader đó sẽ tự cố gắng tải class. Chúng ta hãy cùng tìm hiểu điều này bằng cách thực thi chương trình Java dưới đây.
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()); }}
Kết quả
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
Hãy cùng tìm hiểu cách hoạt động của các class loader từ output chương trình trên.
java.util.HashMap xuất hiện là null, điều này phản ánh Bootstrap ClassLoader. ClassLoader của DNSNameService class là ExtClassLoader. Vì bản thân class này nằm trong CLASSPATH nên System ClassLoader sẽ tải nó.Java default ClassLoader có thể tải các class từ local file system, điều này là đủ tốt cho hầu hết các trường hợp. Tuy nhiên, nếu bạn mong đợi một class tại runtime hoặc từ FTP server hoặc thông qua một web service của bên thứ ba tại thời điểm tải class, thì bạn phải mở rộng class loader hiện có. Ví dụ, AppletViewers tải các class từ một web server từ xa.
loadClass() của ClassLoader bằng cách truyền vào tên đủ điều kiện (fully classified name) của Class đó.loadClass() gọi phương thức findLoadedClass() để kiểm tra xem class đã được tải trước đó hay chưa. Điều này là cần thiết để tránh tải cùng một class nhiều lần.findClass() để tìm kiếm các class trong file system.Chúng ta sẽ tạo ClassLoader riêng bằng cách mở rộng lớp ClassLoader và ghi đè phương thức loadClass(String name). Nếu tên class bắt đầu bằng com.journaldev thì chúng ta sẽ tải nó bằng custom class loader của mình, hoặc nếu không, chúng ta sẽ gọi phương thức loadClass() của parent ClassLoader để tải class.

Đây là custom class loader của chúng ta với các method dưới đây.
private byte[] loadClassFileData(String name): Method này sẽ đọc file class từ file system thành byte array.private Class<?> getClass(String name): Method này sẽ gọi hàm loadClassFileData() và bằng cách gọi method defineClass() của parent, nó sẽ tạo ra Class và trả về.public Class<?> loadClass(String name): Method này chịu trách nhiệm tải class. Nếu tên class bắt đầu bằng com.journaldev (các sample class của chúng ta) thì nó sẽ tải bằng method getClass(), hoặc nếu không, nó sẽ gọi hàm loadClass() của parent để tải.public CCLoader(ClassLoader parent): Đây là constructor (hàm khởi tạo), chịu trách nhiệm thiết lập parent ClassLoaderimport java.io.DataInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream;/**
*/ 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;
}
}
Đây là test class của chúng ta với hàm main. Chúng ta đang tạo một instance của ClassLoader của mình và tải các class mẫu bằng cách sử dụng method loadClass() của nó. Sau khi tải class, chúng ta đang sử dụng Java Reflection API để gọi các method của nó.
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]); }}
Đây là các test class của chúng ta đang được tải bởi custom classloader của chúng ta. Chúng có một method printCL() đang được gọi để in thông tin về ClassLoader. Foo class sẽ được tải bởi custom class loader của chúng ta. Foo sử dụng Bar class, vì vậy Bar class cũng sẽ được tải bởi custom class loader của chúng ta.
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());
}
}
Trước hết, chúng ta sẽ biên dịch tất cả các class thông qua command line. Sau đó, chúng ta sẽ chạy CCRun class bằng cách truyền ba đối số (argument). Đối số đầu tiên là tên đầy đủ (fully classified name) cho Foo class sẽ được tải bởi class loader của chúng ta. Hai đối số còn lại được truyền cho hàm main của Foo class và constructor của Bar. Các bước thực thi và output sẽ như dưới đây.
$ 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ếu bạn nhìn vào output, nó đang cố gắng tải com.journaldev.cl.Foo class. Vì nó đang mở rộng java.lang.Object class, nên nó cố gắng tải Object class trước. Do đó, yêu cầu đến method loadClass của CCLoader, vốn đang ủy quyền nó cho parent class. Vì vậy, các parent class loader đang tải các Object, String, và các Java class khác. ClassLoader của chúng ta chỉ tải Foo và Bar class từ file system. Điều này rõ ràng từ output của hàm printCL(). Chúng ta có thể thay đổi chức năng của loadClassFileData() để đọc byte array từ FTP Server hoặc bằng cách gọi bất kỳ third party service nào để lấy class byte array một cách nhanh chóng. Tôi hy vọng rằng bài viết này sẽ hữu ích trong việc hiểu cách hoạt động của Java ClassLoader và cách chúng ta có thể mở rộng nó để làm được nhiều điều hơn là chỉ lấy từ file system.
Chúng ta có thể biến custom class loader của mình thành default class loader khi JVM khởi động bằng cách sử dụng Java Options. Ví dụ, tôi sẽ chạy chương trình ClassLoaderTest một lần nữa sau khi cung cấp tùy chọn 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 đang tải lớp ClassLoaderTest vì nó nằm trong package com.journaldev.
Bạn có thể tải mã ví dụ về ClassLoader từ kho lưu trữ GitHub của chúng tôi.