클래스로더란 무엇인가요? 모든 프로그래밍 언어 중에서 Java는 Java Virtual Machine에서 실행된다는 점에서 독특합니다. 이는 컴파일된 프로그램이 대상 시스템의 형식이 아닌 고유하고 플랫폼 독립적인 형식으로 대상 시스템에서 실행된다는 것을 의미합니다. 이 형식은 여러 측면에서 기존 실행 프로그램과 매우 다릅니다.
Java ClassLoader는 Java 운영 체제에서 중요하지만 종종 무시되는 구성 요소입니다. 런타임 시 클래스 파일을 찾고 로드하는 일을 담당합니다. 사용자 정의 ClassLoader를 생성하면 클래스 파일이 시스템에 로드되는 방식을 완전히 재정의할 수 있습니다.
이 튜토리얼은 Java ClassLoader의 일반적인 개요를 제공하고 사용자 정의 ClassLoader의 예를 제공합니다. 이 ClassLoader는 코드를 로드하기 전에 자동으로 컴파일됩니다. ClassLoader가 수행하는 작업과 사용자 정의 ClassLoader를 생성하는 방법을 배우게 됩니다.
이 튜토리얼을 사용하려면 독자가 간단한 명령줄 Java 프로그램 생성, 컴파일, 실행을 포함하여 Java 프로그래밍에 대한 기본적인 이해가 필요합니다.
이 튜토리얼을 읽고 나면 다음 방법을 알게 됩니다.
JVM 기능 확장
Java 프로그램과 C 또는 C++ 프로그램의 가장 큰 차이점은 단일 실행 파일이 아니라 여러 개의 개별 클래스 파일로 구성되며 각 클래스 파일은 Java 클래스에 해당한다는 것입니다.
그뿐만 아니라 이러한 클래스 파일은 한꺼번에 메모리에 로드되는 것이 아니라 요청 시 로드됩니다. ClassLoader는 클래스를 메모리에 로드하는 JVM의 일부입니다.
또한 Java ClassLoader는 Java로 작성되었습니다. 이는 JVM에 대한 자세한 내용을 알지 못해도 자신만의 ClassLoader를 쉽게 만들 수 있음을 의미합니다.
ClassLoader를 작성하는 이유
그러나 JAVA 언어의 가장 새로운 기능 중 하나는 로컬 하드 드라이브나 인터넷 외부에서 수업을 얻을 수 있다는 것입니다. 예를 들어, 브라우저는 사용자 정의 ClassLoader를 사용하여 웹 사이트에서 실행 가능한 컨텐츠를 얻습니다.
클래스 파일을 얻는 다른 방법도 많이 있습니다. 클래스 파일을 로컬 또는 온라인으로 로드하는 것 외에도 클래스 로더를 사용하여 다음을 수행할 수도 있습니다.
CompilingClassLoader(CCL)
라는 사용자 정의 클래스 로더를 생성하여 Java 코드를 컴파일하는 데 도움을 줍니다. 이는 기본적으로 실행 중인 시스템에 직접 간단한 make 프로그램을 구축하는 것과 같습니다. ClassLoader 구조 ClassLoader의 기본 목적은 클래스 요청을 처리하는 것입니다. JVM에는 클래스가 필요하므로 ClassLoader에게 해당 이름으로 클래스를 로드하도록 요청합니다. ClassLoader는 클래스를 나타내는 객체를 반환하려고 시도합니다. CompilingClassLoader(CCL)
、CCL会帮我们编译Java代码。它基本上就像是在运行系统中直接构建一个简单的make程序。
ClassLoader的基本目的是为类的请求提供服务。JVM需要一个类,于是它通过类的名字询问ClassLoader来加载这个类。ClassLoader试着返回一个代表该类的对象。
通过覆盖此过程不同阶段对应的方法,可以创建自定义的ClassLoader。
在本文的剩余部分,你会了解到ClassLoader中的一些关键方法。你会了解到每个方法的用途以及它在类加载过程中是如何调用的。你还会了解当你在自定义ClassLoader时需要完成的工作。
loadClass
方法##、ClassLoader.loadClass()
方法是ClassLoader的入口。它的方法标签如下:
Class loadClass(String name, boolean resolve)
name
参数代表JVM需要的类的名称,比如Foo
或是java.lang.Object
이 프로세스의 여러 단계에 해당하는 메서드를 재정의하여 사용자 정의 ClassLoader를 만들 수 있습니다.
loadClass
method##, #🎜🎜##🎜🎜#ClassLoader.loadClass()
메소드는 ClassLoader의 입구입니다. 메소드 태그는 다음과 같습니다: #🎜🎜#% java Foo arg1 arg2#🎜🎜#
name
매개변수는 Foo
또는 java.lang.객체
. #🎜🎜#resolve
매개변수는 클래스를 해결해야 하는지 여부를 나타냅니다. 클래스 구문 분석은 클래스 실행을 완전히 준비하는 것으로 이해될 수 있습니다. 구문 분석은 필요하지 않습니다. JVM이 클래스의 존재 여부를 확인하거나 해당 상위 클래스만 찾으면 구문 분석할 필요가 없습니다. resolve
参数说明类是否需要被解析。可以把类的解析理解为完全的准备好执行类。解析并不是必要的。如果JVM只需要确定该类存在或是找出其父类,则无需解析。
在java1.1版本以前,自定义ClassLoader只需要重写loadClass
方法。
defineClass
方法是整个ClassLoader的核心。此方法将原始字节数组转化为一个Class
对象。原始字节数组包含从本地或是远程得到的数据。
defineClass
负责处理JVM的许多复杂,神秘而且依赖于具体实现的部分。它将字节码解析为运行时的数据结构,检查其有效性等。不用担心,这些你不用自己实现。事实上,你根本没法重写它,因为该方法为final方法。
findSystemClass方法
findSysetmClass
方法从本地文件系统中加载文件。它在本地文件系统中查找类文件,如果存在,使用defineClass
将其从原始字节转化为类对象。这是JVM在运行Java应用程序时加载类的默认机制。
对于自定义的ClassLoader,我们只会在尝试了别的方法来加载类内容之后,才调用findSystemClass
方法。道理很简单:自定义的ClassLoader包含加载特殊类的一些步骤,但是并非所有的类都是特殊类。比如,即便ClassLoader需要从远程网站上获取一些类,还是有许多类需要从本地的Java库中加载。这些类并不是我们关注的重点,因此我们需要JVM用默认的方式来获取。
整个流程如下:
请求自定义ClassLoader加载一个类
查看远程服务器是否有该类
如果有,则获取并返回
如果没有,我们假设该类是位于本地的一个基础类,并调用findSystemClass
从文件系统中加载出来。
在大多数自定义的ClassLoader中,你需要先滴啊用findSystemClass
来减少对远程网站的访问,因为大多数Java类都位于本地的类库中。但是,在下一节中你会看到,在自动将应用代码编译之前,我们不希望JVM从本地文件系统加载类。
resolveClass
方法如前文所说,类的加载是可以部分进行(不进行解析)或是彻底进行的(进行解析)。当我们实现自己的loadClass
方法时,我们或许需要调用resolveClass
方法,这取决于loadClass
中的resolve
参数的值。
findLoadedClass
方法findLoadedClass
方法充当一个缓存调用机制:当loadClass
方法被调用时,他会调用这个方法来查看类是否已经被加载过了,省去了重复加载。这个方法应当最先被调用。
我们的例子中loadClass
执行以下几步(这里我们不会特别关注到底采用了什么神奇的方法来获取类文件。它可以是从本地,网络或者是压缩文件中获得的,总之我们获得了原始类文件的字节码):
调用findLoadedClass
查看是否已经加载过该类
如果没有,则使用神奇的魔法来获得原始字节码
如果获得字节码,调用defineClass
将其转化为Class
对象
如果没有获得字节码,则调用findSystemClass
,看是否能从本地文件系统获得类
如果resolve
值为true,则调用resolveClass
来解析Class
对象
如果还是没有找到类,则抛出ClassNotFoundException
否则,将类返回给调用者
CompilingClassLoader
CCL
的作用是确保代码已经被编译,并且是最新版本的。
以下是该类的描述:
当需要一个类时,查看该类是否在磁盘上,在当前的目录或是相应的子目录下
如果该类不存在,但是其源码存在,在调用Java编译器来生成类文件
如果类文件存在,查看他是否比源码的版本旧,如果低于源码的版本,则重新生成类文件
如果编译失败,或者其他的原因导致无法从源码中生成类文件,抛出ClassNotFoundException
如果还是没有类文件,那么它或许在其他的一些库中,调用findSystemClass
看是否有用
如果还是找不到类,抛出ClassNotFoundException
loadClass
메서드를 재정의하기만 하면 되었습니다. defineClass
메소드는 전체 ClassLoader의 핵심입니다. 이 메서드는 원본 바이트 배열을 Class
객체로 변환합니다. 원시 바이트 배열에는 로컬 또는 원격 소스에서 얻은 데이터가 포함됩니다.
defineClass
는 JVM의 복잡하고 신비하며 구현에 의존적인 여러 부분을 처리하는 역할을 담당합니다. 바이트코드를 런타임 데이터 구조로 구문 분석하고 유효성을 검사합니다. 걱정하지 마세요. 직접 구현할 필요는 없습니다. 실제로 메서드가 최종 메서드이기 때문에 전혀 재정의할 수 없습니다. 🎜findSystemClass 메서드
findSysetmClass
메서드는 로컬 파일 시스템에서 파일을 로드합니다. 로컬 파일 시스템에서 클래스 파일을 찾고, 있는 경우 defineClass
를 사용하여 원시 바이트에서 클래스 객체로 변환합니다. 이는 Java 애플리케이션을 실행할 때 클래스를 로드하기 위한 JVM의 기본 메커니즘입니다. 🎜🎜사용자 정의 ClassLoader의 경우 클래스 콘텐츠를 로드하기 위해 다른 메서드를 시도한 후에만 findSystemClass
메서드를 호출합니다. 이유는 간단합니다. 사용자 정의 ClassLoader에는 특수 클래스를 로드하기 위한 몇 가지 단계가 포함되어 있지만 모든 클래스가 특수 클래스는 아닙니다. 예를 들어 ClassLoader가 원격 웹사이트에서 일부 클래스를 가져와야 하는 경우에도 로컬 Java 라이브러리에서 로드해야 하는 클래스가 여전히 많이 있습니다. 이러한 클래스는 우리의 초점이 아니므로 기본 방식으로 클래스를 얻으려면 JVM이 필요합니다. 🎜🎜전체 프로세스는 다음과 같습니다. 🎜findSystemClass
를 호출하여 파일 시스템에서 로드합니다. 🎜🎜findSystemClass
를 사용해야 합니다. 그러나 다음 섹션에서 볼 수 있듯이 애플리케이션 코드를 자동으로 컴파일하기 전에 JVM이 로컬 파일 시스템에서 클래스를 로드하는 것을 원하지 않습니다. 🎜resolveClass
메서드loadClass
메서드를 구현할 때 loadClass
의 resolve
에 따라 resolveClass
메서드를 호출해야 할 수도 있습니다. > >매개변수의 값입니다. 🎜findLoadedClass
메서드findLoadedClass
메서드는 캐시 호출 메커니즘으로 작동합니다. loadClass
메서드가 호출되면 call 이 메소드는 클래스가 로드되었는지 확인하는 데 사용되므로 반복적인 로드가 필요하지 않습니다. 이 메서드를 먼저 호출해야 합니다. 🎜loadClass
는 다음 단계를 수행합니다(여기서는 클래스 파일을 얻기 위해 어떤 마법의 방법이 사용되는지 특별히 주의하지 않습니다. 로컬에서, 네트워크 또는 압축 파일에서 얻었습니다. 즉, 원래 클래스 파일의 바이트코드를 얻었습니다.): 🎜findLoadedClass
클래스가 이미 로드되었는지 확인하세요🎜🎜🎜🎜그렇지 않다면 마법을 사용하여 원래 바이트코드를 얻으세요🎜🎜🎜🎜바이트코드를 얻으면 defineClass
를 호출하여 Class로 변환하세요
객체 🎜🎜🎜🎜바이트코드를 얻지 못한 경우 findSystemClass
를 호출하여 로컬 파일 시스템에서 클래스를 얻을 수 있는지 확인하세요. 🎜🎜🎜🎜해결
되는 경우 code> 값이 true인 경우 resolveClass
를 호출하여 Class
객체🎜🎜🎜🎜클래스를 여전히 찾을 수 없으면 ClassNotFoundException
🎜을 발생시킵니다. 🎜🎜🎜그렇지 않으면 클래스를 호출자에게 반환합니다🎜🎜CompilingClassLoader
CCL
는 코드가 제대로 작성되었는지 확인하는 데 사용됩니다. 컴파일되어 최신 버전입니다. ClassNotFoundException
🎜🎜🎜🎜을 발생시킵니다. 여전히 클래스 파일이 없으면 다른 라이브러리에 있을 수 있습니다. findSystemClass
를 호출하여 유용한지 확인하세요.🎜🎜🎜🎜클래스를 여전히 찾을 수 없으면 ClassNotFoundException을 발생시킵니다.
🎜🎜🎜🎜그렇지 않으면 수업을 반환하세요🎜在深入研究之前,我们应该回过头来看一下Java的编译机制。总的来说,当你请求一个类的时候,Java不只是编译各种类信息,它还编译了别的相关联的类。
CCL会按需一个接一个的编译相关的类。但是,当CCL编译完一个类之后试着去编译其它相关类的时候会发现,其它的类已经编译完成了。为什么呢?Java编译器遵循一个规则:如果一个类不存在,或者它相对于源码已经过时了,就需要编译它。从本质上讲,Java编译器先CCL一步完成了大部分的工作。
CCL在编译类的时候会打印其编译的应用程序。在大多数场景里面,你会看到它在程序的主类上调用编译器。
但是,有一种情况是不会在第一次调用时编译所有类的的。如果你通过类名Class.forNasme
加载一个类,Java编译器不知道该类需要哪些信息。在这种场景下,你会看到CCL会再次运行Java编译器。
CompilingClassLoader
为了使用CCL,我们需要用一种独特的方式启动程序。正常的启动程序如下:
% java Foo arg1 arg2
而我们启动方式如下:
% java CCLRun Foo arg1 arg2
CCLRun是一个特殊的桩程序,它会创建一个CompilingClassLoader并使用它来加载程序的main方法,确保整个程序的类会通过CompilingClassLoader加载。CCLRun使用Java反射API来调用main方法并传参
Java1.2以后ClassLoader有一些变动。原有版本的ClassLoader还是兼容的,而且在新版本下开发ClassLoader更容易了
新的版本下采用了delegate模型。ClassLoader可以将类的请求委托给父类。默认的实现会先调用父类的实现,在自己加载。但是这种模式是可以改变的。所有的ClassLoader的根节点是系统ClassLoader。它默认会从文件系统中加载类。
loadClass
默认实现一个自定义的loadClass
方法通常会尝试用各种方法来获得一个类的信息。如果你写了大量的ClassLoader,你会发现基本上是在重复写复杂而变化不大的代码。
java1.2的loadClass
的默认实现中允许你直接重写findClass
方法,loadClass
将会在合适的时候调用该方法。
这种方式的好处在于你无须重写loadClass
方法。
findClass
该方法会被loadClass
的默认实现调用。findClass
是为了包含ClassLoader所有特定的代码,而无需写大量重负的其他代码
getSystenClassLoader
无论你是否重写了findClass
或是loadClass
方法,getSystemClassLoader
允许你直接获得系统的ClassLoader(而不是隐式的用findSystemClass
获得)
getParent
该方法允许类加载器获取其父类加载器,从而将请求委托给它。当你自定义的加载器无法找到类时,可以使用该方法。父类加载器是指包含创建该类加载代码的加载器。
// $Id$ import java.io.*; /* A CompilingClassLoader compiles your Java source on-the-fly. It checks for nonexistent .class files, or .class files that are older than their corresponding source code. */ public class CompilingClassLoader extends ClassLoader { // Given a filename, read the entirety of that file from disk // and return it as a byte array. private byte[] getBytes( String filename ) throws IOException { // Find out the length of the file File file = new File( filename ); long len = file.length(); // Create an array that's just the right size for the file's // contents byte raw[] = new byte[(int)len]; // Open the file FileInputStream fin = new FileInputStream( file ); // Read all of it into the array; if we don't get all, // then it's an error. int r = fin.read( raw ); if (r != len) throw new IOException( "Can't read all, "+r+" != "+len ); // Don't forget to close the file! fin.close(); // And finally return the file contents as an array return raw; } // Spawn a process to compile the java source code file // specified in the 'javaFile' parameter. Return a true if // the compilation worked, false otherwise. private boolean compile( String javaFile ) throws IOException { // Let the user know what's going on System.out.println( "CCL: Compiling "+javaFile+"..." ); // Start up the compiler Process p = Runtime.getRuntime().exec( "javac "+javaFile ); // Wait for it to finish running try { p.waitFor(); } catch( InterruptedException ie ) { System.out.println( ie ); } // Check the return code, in case of a compilation error int ret = p.exitValue(); // Tell whether the compilation worked return ret==0; } // The heart of the ClassLoader -- automatically compile // source as necessary when looking for class files public Class loadClass( String name, boolean resolve ) throws ClassNotFoundException { // Our goal is to get a Class object Class clas = null; // First, see if we've already dealt with this one clas = findLoadedClass( name ); //System.out.println( "findLoadedClass: "+clas ); // Create a pathname from the class name // E.g. java.lang.Object => java/lang/Object String fileStub = name.replace( '.', '/' ); // Build objects pointing to the source code (.java) and object // code (.class) String javaFilename = fileStub+".java"; String classFilename = fileStub+".class"; File javaFile = new File( javaFilename ); File classFile = new File( classFilename ); //System.out.println( "j "+javaFile.lastModified()+" c "+ // classFile.lastModified() ); // First, see if we want to try compiling. We do if (a) there // is source code, and either (b0) there is no object code, // or (b1) there is object code, but it's older than the source if (javaFile.exists() && (!classFile.exists() || javaFile.lastModified() > classFile.lastModified())) { try { // Try to compile it. If this doesn't work, then // we must declare failure. (It's not good enough to use // and already-existing, but out-of-date, classfile) if (!compile( javaFilename ) || !classFile.exists()) { throw new ClassNotFoundException( "Compile failed: "+javaFilename ); } } catch( IOException ie ) { // Another place where we might come to if we fail // to compile throw new ClassNotFoundException( ie.toString() ); } } // Let's try to load up the raw bytes, assuming they were // properly compiled, or didn't need to be compiled try { // read the bytes byte raw[] = getBytes( classFilename ); // try to turn them into a class clas = defineClass( name, raw, 0, raw.length ); } catch( IOException ie ) { // This is not a failure! If we reach here, it might // mean that we are dealing with a class in a library, // such as java.lang.Object } //System.out.println( "defineClass: "+clas ); // Maybe the class is in a library -- try loading // the normal way if (clas==null) { clas = findSystemClass( name ); } //System.out.println( "findSystemClass: "+clas ); // Resolve the class, if any, but only if the "resolve" // flag is set to true if (resolve && clas != null) resolveClass( clas ); // If we still don't have a class, it's an error if (clas == null) throw new ClassNotFoundException( name ); // Otherwise, return the class return clas; } }
import java.lang.reflect.*; /* CCLRun executes a Java program by loading it through a CompilingClassLoader. */ public class CCLRun { static public void main( String args[] ) throws Exception { // The first argument is the Java program (class) the user // wants to run String progClass = args[0]; // And the arguments to that program are just // arguments 1..n, so separate those out into // their own array String progArgs[] = new String[args.length-1]; System.arraycopy( args, 1, progArgs, 0, progArgs.length ); // Create a CompilingClassLoader CompilingClassLoader ccl = new CompilingClassLoader(); // Load the main class through our CCL Class clas = ccl.loadClass( progClass ); // Use reflection to call its main() method, and to // pass the arguments in. // Get a class representing the type of the main method's argument Class mainArgType[] = { (new String[0]).getClass() }; // Find the standard main method in the class Method main = clas.getMethod( "main", mainArgType ); // Create a list containing the arguments -- in this case, // an array of strings Object argsArray[] = { progArgs }; // Call the method main.invoke( null, argsArray ); } }
public class Foo { static public void main( String args[] ) throws Exception { System.out.println( "foo! "+args[0]+" "+args[1] ); new Bar( args[0], args[1] ); } }
import baz.*; public class Bar { public Bar( String a, String b ) { System.out.println( "bar! "+a+" "+b ); new Baz( a, b ); try { Class booClass = Class.forName( "Boo" ); Object boo = booClass.newInstance(); } catch( Exception e ) { e.printStackTrace(); } } }
package baz; public class Baz { public Baz( String a, String b ) { System.out.println( "baz! "+a+" "+b ); } }
public class Boo { public Boo() { System.out.println( "Boo!" ); } }
相关文章:
基于Java类的加载方式之classloader类加载器详解
相关视频:
위 내용은 ClassLoader를 작성하는 이유는 무엇입니까? Java의 클래스로더에 대한 깊은 이해의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!