Java java지도 시간 Java 클래스 로더에 대한 심층적인 살펴보기

Java 클래스 로더에 대한 심층적인 살펴보기

Apr 17, 2017 pm 02:27 PM

클래스 로더는 Java™에서 매우 중요한 개념입니다. 클래스 로더는 Java 클래스의 바이트 코드를 Java 가상 머신에 로드하는 역할을 합니다. 이 글에서는 먼저 프록시 모드, 클래스 및 스레드 컨텍스트 클래스 로더를 로드하는 구체적인 프로세스 등을 포함하여 Java 클래스 로더의 기본 개념을 자세히 소개하고, 그 방법을 소개합니다. 자신만의 클래스 로더를 개발하고, 마지막으로 웹 컨테이너와 OSGi™에 클래스 로더를 적용하는 방법을 소개합니다.

IBM Bluemix 클라우드 플랫폼에서 다음 앱을 개발하고 배포하세요.
평가판 시작
클래스 로더는 Java 언어의 혁신이며 Java 언어 인기의 중요한 이유 중 하나입니다. 이를 통해 Java 클래스를 JVM(Java Virtual Machine)에 동적으로 로드하고 실행할 수 있습니다. 클래스 로더는 JDK 1.0부터 사용되었으며 원래 Java 애플릿의 요구 사항을 충족하기 위해 개발되었습니다. Java Applet은 Java 클래스 파일을 원격에서 브라우저로 다운로드하여 실행해야 합니다. 클래스 로더는 이제 웹 컨테이너와 OSGi에서 널리 사용됩니다. 일반적으로 Java 애플리케이션 개발자는 클래스 로더와 직접 상호 작용할 필요가 없습니다. Java Virtual Machine의 기본 동작은 대부분의 상황 요구 사항을 충족하기에 충분합니다. 하지만 클래스 로더와 상호 작용해야 하는 상황에 직면하고 클래스 로더의 메커니즘에 대해 잘 알지 못하는 경우 디버깅 ClassNotFound예외 및 NoClassDefFoundError 및 기타 예외. 이 기사에서는 독자가 Java 언어에서 이 중요한 개념을 깊이 이해할 수 있도록 Java의 클래스 로더를 자세히 소개합니다. 아래에서는 먼저 몇 가지 관련 기본 개념을 소개합니다.
클래스 로더의 기본 개념이름에서 알 수 있듯이 클래스 로더는 Java 클래스를 Java 가상 머신에 로드하는 데 사용됩니다. 일반적으로 Java 가상 머신은 다음과 같이 Java 클래스를 사용합니다. Java 소스 프로그램(.java 파일)은 Java 컴파일러에 의해 컴파일된 후 Java 바이트 코드(.class 파일)로 변환됩니다. 클래스 로더는 Java 바이트 코드를 읽고 이를 java.lang.Class 클래스의 인스턴스로 변환하는 일을 담당합니다. 이러한 각 인스턴스는 Java 클래스를 나타냅니다. 이 클래스의
객체는 이 인스턴스의 newInstance() 메서드를 통해 생성할 수 있습니다. 실제 상황은 더 복잡할 수 있습니다. 예를 들어 Java 바이트 코드는 도구를 통해 동적으로 생성되거나 네트워크를 통해 다운로드될 수 있습니다. 기본적으로 모든 클래스 로더는 java.lang.ClassLoader 클래스의 인스턴스입니다. 이 Java 클래스는 아래에 자세히 설명되어 있습니다.

java.lang.ClassLoader**클래스 소개**java.lang.ClassLoader 클래스의 기본 역할은 지정된 클래스의 이름을 기반으로 해당 바이트 코드를 찾거나 생성하는 것입니다. class. 그런 다음 이러한 바이트 코드에서 Java 클래스, 즉 java.lang.Class 클래스의 인스턴스를 정의합니다. 또한 ClassLoader는 이미지 파일,
구성 파일 등 Java 애플리케이션에 필요한 리소스를 로드하는 역할도 합니다. 그러나 이 문서에서는 클래스 로드 기능에 대해서만 설명합니다. 클래스 로딩 책임을 완수하기 위해 ClassLoader는 일련의 메소드를 제공하며, 더 중요한 메소드는 표 1에 나와 있습니다. 이러한 방법에 대한 자세한 내용은 아래에 설명되어 있습니다.

표 1. ClassLoader의 클래스 로딩 관련 메소드

메소드 설명

getParent()

이 클래스 로더의 상위 클래스 로더를 반환합니다.

loadClass(

String name)

name이라는 클래스를 로드하고 반환된 결과는 java.lang.Class 클래스의 인스턴스입니다.

findClass(String name)

name이라는 클래스를 찾으면 반환된 결과는 java.lang.Class 클래스의 인스턴스입니다.

findLoadedClass(String name)

name이라는 이름의 로드된 클래스를 찾으면 반환된 결과는 java.lang.Class 클래스의 인스턴스입니다.

defineClass(String name, byte[] b, int off, int len)

바이트

array b의 내용을 Java 클래스로 변환하고 결과를 반환합니다. java.lang.Class 클래스의 인스턴스입니다. 이 메소드는 최종으로 선언됩니다.

resolveClass(Class c)

지정된 Java 클래스를 연결합니다.

표 1에 제시된 메소드의 경우 클래스 이름을 나타내는 name 매개변수의 값은 클래스의 바이너리 이름입니다. 주목해야 할 것은 com.example.Sample$1 및 com.example.Sample$Inner와 같은 내부 클래스의 표현입니다. 이러한 메소드는 클래스 로더의 작동 메커니즘이 소개될 때 아래에서 더 자세히 설명됩니다. 다음은 클래스 로더의 트리형 조직 구조를 설명합니다.

클래스 로더의 나무형 조직 구조

Java의 클래스 로더는 크게 두 가지 범주로 나눌 수 있습니다. 하나는 시스템에서 제공하고 다른 하나는 Java 애플리케이션 개발자가 작성합니다. 시스템에서 제공하는 세 가지 주요 클래스 로더가 있습니다:
부트스트랩 클래스 로더): Java의 핵심 라이브러리를 로드하는 데 사용되며 java.lang의 상속 네이티브 코드로 구현됩니다. .클래스로더. 확장 클래스 로더: Java 확장 라이브러리를 로드하는 데 사용됩니다. JVM(Java Virtual Machine)의 구현은 확장 라이브러리 디렉토리를 제공합니다. 클래스 로더는 이 디렉토리에서 Java 클래스를 찾아 로드합니다.
시스템 클래스 로더: Java 애플리케이션의 클래스 경로(CLASSPATH)에 따라 Java 클래스를 로드합니다. 일반적으로 Java 애플리케이션 클래스는 이를 통해 로드됩니다. ClassLoader.getSystemClassLoader()를 통해 얻을 수 있습니다.

개발자는 시스템에서 제공하는 클래스 로더 외에도 몇 가지 특별한 요구 사항을 충족하기 위해 java.lang.ClassLoader 클래스를 상속하여 자신만의 클래스 로더를 구현할 수 있습니다.

부트스트랩 클래스 로더를 제외한 모든 클래스 로더에는 상위 클래스 로더가 있습니다. 표 1에 주어진 getParent() 메소드를 통해 얻을 수 있다. 시스템에서 제공하는 클래스 로더의 경우, 시스템 클래스 로더의 상위 클래스 로더는 확장 클래스 로더이고, 확장 클래스 로더의 상위 클래스 로더는 개발자가 작성한 클래스 로더의 부트 클래스 로더입니다. 클래스 로더는 이 클래스 로더의 Java 클래스를 로드하는 클래스 로더입니다.
다른 Java 클래스와 마찬가지로 클래스 로더 Java 클래스도 클래스 로더에 의해 로드되기 때문입니다. 일반적으로 개발자가 작성한 클래스 로더의 상위 클래스 로더는 시스템 클래스 로더입니다. 클래스 로더는 이러한 방식으로 구성되어 트리 구조를 형성합니다. 트리의 루트 노드는 부트 클래스 로더입니다. 그림 1은 화살표가 상위 클래스 로더를 가리키는 일반적인 클래스 로더 트리 조직 구조 다이어그램을 보여줍니다.
그림 1. 클래스 로더 트리 구성 구조 도식

Java 클래스 로더에 대한 심층적인 살펴보기

classload_tree.png

코드 목록 1은 클래스 로더의 트리형 조직 구조를 보여줍니다.

목록 1. 클래스 로더의 트리 구성 시연

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(); 
    } 
    }
}
로그인 후 복사

각 Java 클래스는 이를 정의하는 클래스 로더에 대한 포인터를 유지합니다

참조 , getClassLoader() 메소드를 통해 얻을 수 있습니다. 코드 목록 1에서는 getParent() 메서드가 재귀적으로 호출되어 모든 상위 클래스 로더를 출력합니다. 코드 목록 1의 실행 결과는 코드 목록 2에 표시됩니다.
목록 2. 클래스 로더의 트리 조직 구조를 보여주는 실행 결과 sun.misc.Launcher$AppClassLoader@9304b1
sun.misc.Launcher$ExtClassLoader@190d11
코드 목록 2에 표시된 것처럼 첫 번째 출력은 ClassLoaderTree 클래스의 클래스 로더, 즉 시스템 클래스 로더입니다. 이는 sun.misc.Launcher$AppClassLoader 클래스의 인스턴스입니다. 두 번째 출력은 sun.misc.Launcher$ExtClassLoader 클래스의 인스턴스인 확장 클래스 로더입니다. 부트 클래스 로더는 여기서 출력되지 않습니다. 이는 상위 클래스 로더가 부트 클래스 로더일 때 일부 JDK 구현이 null을 반환하기 때문입니다.
클래스 로더의 트리형 조직 구조를 이해한 후 클래스 로더의 프록시 모드를 소개합니다.

클래스 로더의 프록시 모드클래스 로더가 클래스의 바이트 코드를 스스로 찾아 정의하려고 하면 먼저 상위 클래스 로더로 프록시를 수행하고 상위 클래스는 로더는 클래스 로더가 먼저 이 클래스를 로드하려고 시도하며 계속해서 시도합니다. 프록시 패턴의 동기를 소개하기 전에 먼저 Java 가상 머신이 두 Java 클래스가 동일하다고 판단하는 방법을 설명해야 합니다. JVM(Java Virtual Machine)은 클래스의 전체 이름이 동일한지 여부뿐만 아니라 이 클래스를 로드하는 클래스 로더가 동일한지도 확인합니다. 두 클래스는 둘 다 동일한 경우에만 동일한 것으로 간주됩니다. 동일한 바이트 코드가 다른 클래스 로더에 의해 로드되더라도 획득되는 클래스는 다릅니다. 예를 들어, Java 클래스 com.example.Sample은 컴파일 후에 바이트 코드 파일 Sample.class를 생성합니다. 두 개의 서로 다른 클래스 로더인 ClassLoaderA와 ClassLoaderB는 각각 이 Sample.class 파일을 읽고 이 클래스를 나타내기 위해 java.lang.Class 클래스의 두 인스턴스를 정의합니다. 이 두 인스턴스는 동일하지 않습니다. Java 가상 머신에서는 서로 다른 클래스입니다. 이 두 클래스의 개체를 서로 할당하려고 하면 런타임 예외 ClassCastException이 발생합니다. 다음은 예시를 통해 자세히 설명합니다. Java 클래스 com.example.Sample은 코드 목록 3에 나와 있습니다.

목록 3. com.example.Sample 클래스

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方法不会被重复调用。
下面讨论另外一种类加载器:线程上下文类加载器。
线程上下文类加载器
스레드 컨텍스트 클래스 로더(컨텍스트 클래스 로더)는 JDK 1.2부터 도입되었습니다. 클래스 java.lang.Thread의 getContextClassLoader() 및 setContextClassLoader(ClassLoader cl) 메소드는 스레드의 컨텍스트 클래스 로더를 획득하고 설정하는 데 사용됩니다. setContextClassLoader(ClassLoader cl) 메소드를 통해 설정되지 않은 경우 스레드는 상위 스레드의 컨텍스트 클래스 로더를 상속합니다. Java 애플리케이션이 실행되는 초기 스레드에 대한 컨텍스트 클래스 로더는 시스템 클래스 로더입니다. 스레드에서 실행되는 코드는 이러한 유형의 로더를 통해 클래스와 리소스를 로드할 수 있습니다.
위에서 언급한 클래스 로더의 프록시 모드는 Java 애플리케이션 개발에서 직면하게 되는 클래스 로더의 모든 문제를 해결할 수는 없습니다. Java는 다양한 서비스 공급자 인터페이스(SPI)를 제공하므로 제3자가 이러한 인터페이스에 대한 구현을 제공할 수 있습니다. 일반적인 SPI에는 JDBC, JCE, JNDI, JAXP 및 JBI가 포함됩니다. 이러한 SPI 인터페이스는 Java 코어 라이브러리에서 제공됩니다. 예를 들어 JAXP의 SPI 인터페이스 정의는 javax.xml.parsers 패키지에 포함되어 있습니다. 이러한 SPI 구현 코드는 Java 애플리케이션이 의존하는 jar 패키지로 포함될 가능성이 높으며 JAXP SPI를 구현하는 Apache Xerces에 포함된 jar 패키지와 같은 클래스 경로(CLASSPATH)를 통해 찾을 수 있습니다. SPI 인터페이스의 코드는 특정 구현 클래스를 로드해야 하는 경우가 많습니다. 예를 들어 JAXP의 javax.xml.parsers.DocumentBuilderFactory 클래스에 있는 newInstance() 메서드는 DocumentBuilderFactory의 새 인스턴스를 생성하는 데 사용됩니다. 여기서 인스턴스의 실제 클래스는 SPI 구현에서 제공하는 javax.xml.parsers.DocumentBuilderFactory에서 상속됩니다. 예를 들어 Apache Xerces에서 구현된 클래스는 org.apache.xerces.jaxp.DocumentBuilderFactoryImpl입니다. 문제는 SPI 인터페이스가 Java 코어 라이브러리의 일부이고 부트 클래스 로더에 의해 로드된다는 것입니다. SPI에 의해 구현된 Java 클래스는 일반적으로 시스템 클래스 로더에 의해 로드됩니다. 부트 클래스 로더는 Java 코어 라이브러리만 로드하기 때문에 SPI 구현 클래스를 찾을 수 없습니다. 또한 시스템 클래스 로더의 조상 클래스 로더이기 때문에 시스템 클래스 로더로 프록시할 수 없습니다. 즉, 클래스 로더의 프록시 모드는 이 문제를 해결할 수 없습니다. 스레드 컨텍스트 클래스 로더는 이 문제를 정확하게 해결합니다. 설정이 이루어지지 않은 경우 Java 애플리케이션 스레드의 컨텍스트 클래스 로더는 기본적으로 시스템 컨텍스트 클래스 로더로 설정됩니다. SPI 인터페이스 코드에서 스레드 컨텍스트 클래스 로더를 사용하면 SPI 구현 클래스를 성공적으로 로드할 수 있습니다. 스레드 컨텍스트 클래스 로더는 많은 SPI 구현에서 사용됩니다. 다음은 클래스를 로드하는 또 다른 방법인 Class.forName을 소개합니다.

Class.forName
Class.forName은 클래스를 로드하는 데에도 사용할 수 있는 정적
메서드입니다. 이 메소드에는 Class.forName(문자열 이름, 부울 초기화, ClassLoader 로더) 및 Class.forName(String className)의 두 가지 형식이 있습니다. 매개변수 이름의 첫 번째 형식은 클래스의 전체 이름을 나타내며, 초기화는 클래스를 초기화할지 여부를 나타냅니다. 두 번째 형식은 초기화 매개변수의 값을 true로 설정하고, loader의 값을 현재 클래스의 클래스 로더로 설정하는 것과 동일합니다. Class.forName의 매우 일반적인 용도는 데이터베이스 드라이버 를 로드할 때입니다. 예를 들어, Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance()는 Apache Derby 데이터베이스의 드라이버를 로드하는 데 사용됩니다. 클래스 로더와 관련된 기본 개념을 소개한 후, 자신만의 클래스 로더를 개발하는 방법을 소개합니다.

자신만의 클래스 로더 개발

대부분의 경우 시스템에서 기본적으로 제공하는 클래스 로더 구현이 요구 사항을 충족할 수 있습니다. 그러나 어떤 경우에는 여전히 애플리케이션을 위한 자체 클래스 로더를 개발해야 합니다. 예를 들어, 애플리케이션은 보안을 보장하기 위해 Java 클래스 바이트 코드를 암호화합니다. 이때, 특정 네트워크 주소에서 암호화된 바이트코드를 읽어와 이를 복호화하고 검증한 뒤, 최종적으로 자바 가상 머신에서 실행될 클래스를 정의하기 위해서는 자신만의 클래스 로더가 필요하다. 다음은 두 가지 구체적인 예를 통해 클래스 로더의 개발을 보여줍니다.

파일 시스템클래스 로더첫 번째 클래스 로더는 파일 시스템에 저장된 Java 바이트 코드를 로드하는 데 사용됩니다. 전체 구현은 코드 목록 6에 나와 있습니다.

목록 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(&#39;.&#39;, 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 的关系。

클래스 로더 및 OSGi
OSGi™는 Java의 동적 모듈 시스템입니다. 이는 개발자에게 서비스 지향 및 구성 요소 기반 런타임 환경을 제공하고 소프트웨어의 라이프 사이클을 관리하는 표준 방법을 제공합니다. OSGi는 많은 제품에 구현 및 배포되었으며 오픈 소스 커뮤니티에서 광범위한 지원을 받았습니다. Eclipse는 OSGi 기술을 기반으로 제작되었습니다.
OSGi의 모든 모듈(번들)에는 Java 패키지와 클래스가 포함되어 있습니다. 모듈은 가져와야 하는 다른 모듈의 Java 패키지 및 클래스를 선언하거나(Import-Package를 통해) 다른 모듈에서 사용할 수 있도록 자체 패키지와 클래스를 선언하고 내보낼 수 있습니다(Export를 통해). -패키지) . 이는 모듈 내에서 특정 Java 패키지와 클래스를 숨기고 공유할 수 있어야 함을 의미합니다. 이는 OSGi의 고유한 클래스 로더 메커니즘을 통해 달성됩니다. OSGi의 각 모듈에는 해당 클래스 로더가 있습니다. 모듈 자체에 포함된 Java 패키지와 클래스를 로드하는 일을 담당합니다. Java 코어 라이브러리(java로 시작하는 패키지 및 클래스)에서 클래스를 로드해야 하는 경우 이를 완료하기 위해 상위 클래스 로더(일반적으로 시작 클래스 로더)로 프록시됩니다. 가져온 Java 클래스를 로드해야 하는 경우 Java 클래스를 내보내는 모듈에 위임하여 로드를 완료합니다. 모듈은 상위 클래스 로더가 로드해야 하는 특정 Java 패키지 및 클래스를 명시적으로 선언할 수도 있습니다. 시스템 속성 org.osgi.framework.bootdelegation의 값을 설정하기만 하면 됩니다.
두 개의 모듈 BundleA와 BundleB가 있고 두 모듈 모두 해당 클래스 로더인 classLoaderA와 classLoaderB를 갖고 있다고 가정해 보겠습니다. BundleA에는 com.bundleA.Sample 클래스가 포함되어 있으며 클래스는 내보내기로 선언되었습니다. 즉, 다른 모듈에서 사용할 수 있습니다. BundleB는 BundleA에서 제공하는 com.bundleA.Sample 클래스를 가져오고 com.bundleA.Sample에서 상속되는 com.bundleB.NewSample 클래스를 포함한다고 선언합니다. BundleB가 시작되면 해당 클래스 로더 classLoaderB는 com.bundleB.NewSample 클래스를 로드해야 하며, 이 클래스는 com.bundleA.Sample 클래스를 로드해야 합니다. BundleB는 com.bundleA.Sample 클래스를 가져올 것으로 선언하므로 classLoaderB는 com.bundleA.Sample 클래스를 로드하는 작업을 이 클래스를 내보내는 BundleA의 클래스 로더 classLoaderA에 위임합니다. classLoaderA는 모듈 내에서 com.bundleA.Sample 클래스를 찾아 정의합니다. 결과 클래스 com.bundleA.Sample 인스턴스는 이 클래스를 선언하는 모든 모듈에서 사용할 수 있습니다. Java로 시작하는 클래스의 경우 상위 클래스 로더에 의해 로드됩니다. 시스템 속성 org.osgi.framework.bootdelegation=com.example.core.*가 선언되면 상위 클래스 로더는 com.example.core 패키지에서 클래스 로드를 완료합니다.
OSGi 모듈의 클래스 로더 구조를 사용하면 다양한 버전의 클래스가 Java 가상 머신에 공존할 수 있어 유연성이 뛰어납니다. 그러나 이러한 차이는 특히 모듈이 제3자가 제공하는 라이브러리를 사용해야 하는 경우 개발자에게 몇 가지 문제를 가져올 수도 있습니다. 다음은 좋은 제안입니다.
클래스 라이브러리가 하나의 모듈에서만 사용되는 경우 클래스 라이브러리의 jar 패키지를 모듈에 넣고 Bundle-ClassPath에 지정합니다.
클래스 라이브러리가 여러 모듈에서 공유되는 경우 이 클래스 라이브러리에 대해 별도의 모듈을 생성하고 다른 모듈이 사용해야 하는 Java 패키지를 내보낸 것으로 선언할 수 있습니다. 다른 모듈 선언은 이러한 클래스를 가져옵니다.
클래스 라이브러리가 SPI 인터페이스를 제공하고 스레드 컨텍스트 클래스 로더를 사용하여 SPI로 구현된 Java 클래스를 로드하는 경우 Java 클래스를 찾지 못할 수 있습니다. NoClassDefFoundError 예외가 발생하면 먼저 현재 스레드의 컨텍스트 클래스 로더가 올바른지 확인하십시오. 클래스 로더는 Thread.currentThread().getContextClassLoader()를 통해 얻을 수 있습니다. 이 클래스 로더는 이 모듈에 해당하는 클래스 로더여야 합니다. 그렇지 않은 경우에는 class.getClassLoader()를 통해 모듈에 해당하는 클래스 로더를 먼저 가져온 후 Thread.currentThread().setContextClassLoader()를 통해 현재 스레드의 컨텍스트 클래스 로더를 설정할 수 있습니다.

요약
클래스 로더는 Java 언어의 혁신입니다. 이를 통해 소프트웨어 구성 요소를 동적으로 설치하고 업데이트할 수 있습니다. 이 기사에서는 기본 개념, 프록시 모드, 스레드 컨텍스트 클래스 로더, 웹 컨테이너 및 OSGi와의 관계 등을 포함하여 클래스 로더 관련 주제를 자세히 소개합니다. 개발자가 ClassNotFoundException 및 NoClassDefFoundError와 같은 예외를 발견하면 예외를 발생시킨 클래스의 클래스 로더와 현재 스레드의 컨텍스트 클래스 로더를 확인하여 문제를 발견해야 합니다. 자신만의 클래스 로더를 개발할 때 기존 클래스 로더 조직 구조와의 조화에 주의해야 합니다.

위 내용은 Java 클래스 로더에 대한 심층적인 살펴보기의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

Video Face Swap

Video Face Swap

완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

뜨거운 도구

메모장++7.3.1

메모장++7.3.1

사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전

SublimeText3 중국어 버전

중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

SublimeText3 Mac 버전

SublimeText3 Mac 버전

신 수준의 코드 편집 소프트웨어(SublimeText3)

Java의 스미스 번호 Java의 스미스 번호 Aug 30, 2024 pm 04:28 PM

Java의 Smith Number 가이드. 여기서는 정의, Java에서 스미스 번호를 확인하는 방법에 대해 논의합니다. 코드 구현의 예.

Java Spring 인터뷰 질문 Java Spring 인터뷰 질문 Aug 30, 2024 pm 04:29 PM

이 기사에서는 가장 많이 묻는 Java Spring 면접 질문과 자세한 답변을 보관했습니다. 그래야 면접에 합격할 수 있습니다.

Java 8 Stream foreach에서 나누거나 돌아 오시겠습니까? Java 8 Stream foreach에서 나누거나 돌아 오시겠습니까? Feb 07, 2025 pm 12:09 PM

Java 8은 스트림 API를 소개하여 데이터 컬렉션을 처리하는 강력하고 표현적인 방법을 제공합니다. 그러나 스트림을 사용할 때 일반적인 질문은 다음과 같은 것입니다. 기존 루프는 조기 중단 또는 반환을 허용하지만 스트림의 Foreach 메소드는이 방법을 직접 지원하지 않습니다. 이 기사는 이유를 설명하고 스트림 처리 시스템에서 조기 종료를 구현하기위한 대체 방법을 탐색합니다. 추가 읽기 : Java Stream API 개선 스트림 foreach를 이해하십시오 Foreach 메소드는 스트림의 각 요소에서 하나의 작업을 수행하는 터미널 작동입니다. 디자인 의도입니다

Java의 날짜까지의 타임스탬프 Java의 날짜까지의 타임스탬프 Aug 30, 2024 pm 04:28 PM

Java의 TimeStamp to Date 안내. 여기서는 소개와 예제와 함께 Java에서 타임스탬프를 날짜로 변환하는 방법에 대해서도 설명합니다.

캡슐의 양을 찾기위한 Java 프로그램 캡슐의 양을 찾기위한 Java 프로그램 Feb 07, 2025 am 11:37 AM

캡슐은 3 차원 기하학적 그림이며, 양쪽 끝에 실린더와 반구로 구성됩니다. 캡슐의 부피는 실린더의 부피와 양쪽 끝에 반구의 부피를 첨가하여 계산할 수 있습니다. 이 튜토리얼은 다른 방법을 사용하여 Java에서 주어진 캡슐의 부피를 계산하는 방법에 대해 논의합니다. 캡슐 볼륨 공식 캡슐 볼륨에 대한 공식은 다음과 같습니다. 캡슐 부피 = 원통형 볼륨 2 반구 볼륨 안에, R : 반구의 반경. H : 실린더의 높이 (반구 제외). 예 1 입력하다 반경 = 5 단위 높이 = 10 단위 산출 볼륨 = 1570.8 입방 단위 설명하다 공식을 사용하여 볼륨 계산 : 부피 = π × r2 × h (4

PHP vs. Python : 차이점 이해 PHP vs. Python : 차이점 이해 Apr 11, 2025 am 12:15 AM

PHP와 Python은 각각 고유 한 장점이 있으며 선택은 프로젝트 요구 사항을 기반으로해야합니다. 1.PHP는 간단한 구문과 높은 실행 효율로 웹 개발에 적합합니다. 2. Python은 간결한 구문 및 풍부한 라이브러리를 갖춘 데이터 과학 및 기계 학습에 적합합니다.

PHP : 웹 개발의 핵심 언어 PHP : 웹 개발의 핵심 언어 Apr 13, 2025 am 12:08 AM

PHP는 서버 측에서 널리 사용되는 스크립팅 언어이며 특히 웹 개발에 적합합니다. 1.PHP는 HTML을 포함하고 HTTP 요청 및 응답을 처리 할 수 ​​있으며 다양한 데이터베이스를 지원할 수 있습니다. 2.PHP는 강력한 커뮤니티 지원 및 오픈 소스 리소스를 통해 동적 웹 컨텐츠, 프로세스 양식 데이터, 액세스 데이터베이스 등을 생성하는 데 사용됩니다. 3. PHP는 해석 된 언어이며, 실행 프로세스에는 어휘 분석, 문법 분석, 편집 및 실행이 포함됩니다. 4. PHP는 사용자 등록 시스템과 같은 고급 응용 프로그램을 위해 MySQL과 결합 할 수 있습니다. 5. PHP를 디버깅 할 때 error_reporting () 및 var_dump ()와 같은 함수를 사용할 수 있습니다. 6. 캐싱 메커니즘을 사용하여 PHP 코드를 최적화하고 데이터베이스 쿼리를 최적화하며 내장 기능을 사용하십시오. 7

미래를 창조하세요: 완전 초보자를 위한 Java 프로그래밍 미래를 창조하세요: 완전 초보자를 위한 Java 프로그래밍 Oct 13, 2024 pm 01:32 PM

Java는 초보자와 숙련된 개발자 모두가 배울 수 있는 인기 있는 프로그래밍 언어입니다. 이 튜토리얼은 기본 개념부터 시작하여 고급 주제를 통해 진행됩니다. Java Development Kit를 설치한 후 간단한 "Hello, World!" 프로그램을 작성하여 프로그래밍을 연습할 수 있습니다. 코드를 이해한 후 명령 프롬프트를 사용하여 프로그램을 컴파일하고 실행하면 "Hello, World!"가 콘솔에 출력됩니다. Java를 배우면 프로그래밍 여정이 시작되고, 숙달이 깊어짐에 따라 더 복잡한 애플리케이션을 만들 수 있습니다.

See all articles