자바 클래스 로더의 기초

Java 가상 머신의 초석 중 하나 인 클래스 로더 개념은 명명 된 클래스를 해당 클래스 구현을 담당하는 비트로 변환하는 동작을 설명합니다. 클래스 로더가 존재하기 때문에 Java 프로그램을 실행할 때 Java 런타임은 파일 및 파일 시스템에 대해 알 필요가 없습니다.

클래스 로더의 기능

클래스는 이미 실행중인 클래스에서 이름으로 참조 될 때 Java 환경에 도입됩니다. 첫 번째 클래스를 실행하기 위해 계속되는 약간의 마술이 있지만 (이것이 main () 메서드를 정적 으로 선언하고 문자열 배열을 인수로 취해야하는 이유입니다 ) 일단 해당 클래스가 실행되면 향후 시도는 로드 클래스는 클래스 로더에 의해 수행됩니다.

가장 간단하게 클래스 로더는 문자열 이름으로 참조되는 클래스 본문의 플랫 네임 스페이스를 만듭니다. 메서드 정의는 다음과 같습니다.

클래스 r = loadClass (문자열 className, 부울 resolveIt); 

className 변수 는 클래스 로더가 이해하고 클래스 구현을 고유하게 식별하는 데 사용되는 문자열을 포함합니다. resolveIt 변수 는이 클래스 이름으로 참조되는 클래스가 해결되어야 함 (즉, 참조 된 모든 클래스도로드되어야 함)을 클래스 로더에 알리는 플래그입니다.

모든 Java 가상 머신에는 가상 머신에 임베드 된 하나의 클래스 로더가 포함됩니다. 이 임베디드 로더를 원시 클래스 로더라고합니다. 가상 머신은 검증없이 VM에서 실행할 수 있는 신뢰할 수있는 클래스 의 저장소에 액세스 할 수 있다고 가정하기 때문에 다소 특별 합니다.

원시 클래스 로더는 loadClass () 의 기본 구현을 구현합니다 . 따라서이 코드는 클래스 이름 java.lang.Object 가 클래스 경로 어딘가에 접두어 java / lang / Object.class가있는 파일에 저장되어 있음을 이해합니다 . 이 코드는 클래스 경로 검색과 클래스에 대한 zip 파일 조사를 모두 구현합니다. 이것이 디자인 된 방식에 대한 정말 멋진 점은 Java가 클래스 로더를 구현하는 함수 세트를 변경하여 클래스 스토리지 모델을 변경할 수 있다는 것입니다.

Java 가상 머신의 내부를 파헤쳐 보면 기본 클래스 로더가 주로 FindClassFromClassResolveClass 함수에서 구현된다는 것을 알게 될 것 입니다.

그렇다면 클래스는 언제로드됩니까? 정확히 두 가지 경우가 있습니다. 새 바이트 코드가 실행되는 경우 (예 : FooClass f = new FooClass () ;)와 바이트 코드가 클래스에 대한 정적 참조를 만드는 경우 (예 : System. out ).

기본이 아닌 클래스 로더

"그래서?" 물어볼 수 있습니다.

JVM (Java Virtual Machine)에는 기본 클래스 대신 사용자 정의 클래스 로더를 사용할 수 있도록 후크가 있습니다. 또한 사용자 클래스 로더가 클래스 이름에서 첫 번째 크랙을 받기 때문에 사용자는 HTTP 서버가 아닌 흥미로운 클래스 리포지토리를 원하는만큼 구현할 수 있습니다.

그러나 클래스 로더가 매우 강력하기 때문에 (예 : java.lang.Object 를 자체 버전으로 대체 할 수 있음 ) 비용이 발생합니다. 애플릿과 같은 Java 클래스는 자체 로더를 인스턴스화 할 수 없습니다. (이것은 클래스 로더에 의해 강제됩니다.)이 열은 애플릿으로이 작업을 수행하려는 경우 유용하지 않으며 신뢰할 수있는 클래스 저장소 (예 : 로컬 파일)에서 실행중인 응용 프로그램에서만 유용합니다.

사용자 클래스 로더는 기본 클래스 로더가 수행하기 전에 클래스를로드 할 기회를 얻습니다. 이 때문에 AppletClassLoader 가 HTTP 프로토콜을 사용하여 클래스를로드 할 수있는 방법 인 일부 대체 소스에서 클래스 구현 데이터를 로드 할 수 있습니다.

SimpleClassLoader 빌드

클래스 로더는 java.lang.ClassLoader 의 서브 클래스로 시작됩니다 . 구현해야하는 유일한 추상 메서드는 loadClass () 입니다. loadClass () 의 흐름은 다음과 같습니다.

  • 클래스 이름을 확인하십시오.
  • 요청 된 클래스가 이미로드되었는지 확인하십시오.
  • 클래스가 "시스템"클래스인지 확인하십시오.
  • 이 클래스 로더의 저장소에서 클래스 가져 오기를 시도합니다.
  • VM의 클래스를 정의하십시오.
  • 수업을 해결하십시오.
  • 클래스를 호출자에게 반환합니다.

SimpleClassLoader는 코드에 흩어져있는 작업에 대한 설명과 함께 다음과 같이 나타납니다.

공용 동기화 된 클래스 loadClass (String className, boolean resolveIt) throws ClassNotFoundException {Class result; 바이트 classData []; System.out.println ( ">>>>>>로드 클래스 :"+ className); / * 클래스의 로컬 캐시 확인 * / result = (Class) classes.get (className); if (result! = null) {System.out.println ( ">>>>>> 캐시 된 결과를 반환합니다."); 반환 결과; }

위의 코드는 loadClass 메서드 의 첫 번째 섹션입니다 . 보시다시피, 그것은 클래스 이름을 취하고 우리의 클래스 로더가 이미 반환 한 클래스를 유지하고있는 로컬 해시 테이블을 검색합니다. 요청할 때마다 동일한 클래스 이름에 대해 동일한 클래스 객체 참조를 반환 해야 하므로이 해시 테이블을 유지하는 것이 중요 합니다. 그렇지 않으면 시스템은 동일한 이름을 가진 두 개의 다른 클래스가 있다고 믿고 그들 사이에 객체 참조를 할당 할 때마다 ClassCastException을 발생시킵니다. loadClass () 가 캐시를 유지하는 것도 중요합니다. 메서드는 클래스가 확인 될 때 ​​재귀 적으로 호출되며 다른 복사본을 위해 추적하는 대신 캐시 된 결과를 반환해야합니다.

/ * 원시 클래스 로더로 확인 * / try {result = super.findSystemClass (className); System.out.println ( ">>>>>> 반환 시스템 클래스 (CLASSPATH)."); 반환 결과; } catch (ClassNotFoundException e) {System.out.println ( ">>>>>> 시스템 클래스가 아닙니다."); }

위의 코드에서 볼 수 있듯이 다음 단계는 기본 클래스 로더가이 클래스 이름을 확인할 수 있는지 확인하는 것입니다. 이 검사는 시스템의 온전 성과 보안 모두에 필수적입니다. 예를 들어, 자신의 java.lang.Object 인스턴스를 호출자 에게 반환하면 이 객체는 다른 객체와 공통 수퍼 클래스를 공유하지 않습니다! 클래스 로더 가 실제 값과 동일한 검사가없는 java.lang.SecurityManager 자체 값을 반환하면 시스템의 보안이 손상 될 수 있습니다 .

/ * 저장소에서로드 해보십시오. * / classData = getClassImplFromDataBase (className); if (classData == null) {throw new ClassNotFoundException (); }

초기 검사 후, 간단한 클래스 로더가이 클래스의 구현을로드 할 기회를 얻는 위의 코드에 도달합니다. SimpleClassLoader는 방법이있다 getClassImplFromDataBase () 우리의 간단한 예에 불과 클래스 이름에 디렉토리 "가게를 \"접두사 및 확장 ".impl"를 추가합니다. 예제에서이 기술을 선택하여 기본 클래스 로더가 우리 클래스를 찾는 데 문제가 없도록했습니다. 참고는 것을 sun.applet.AppletClassLoader가 다음 HTML 페이지에서 코드베이스 URL 접두사 곳 이름으로 애플릿의 삶과 바이트 코드를 가져 오기 위해 HTTP GET 요청을한다.

 / * 정의 (클래스 파일 구문 분석) * / result = defineClass (classData, 0, classData.length); 

클래스 구현이로드 된 경우 두 번째 단계는 java.lang.ClassLoader 에서 defineClass () 메소드 를 호출하는 것입니다 . 이는 클래스 검증의 첫 번째 단계로 간주 될 수 있습니다. 이 메소드는 JVM (Java Virtual Machine)에서 구현되며 클래스 바이트가 올바른 Java 클래스 파일인지 확인합니다. 내부적으로 defineClass 메소드는 JVM이 클래스를 보유하는 데 사용하는 데이터 구조를 채 웁니다. 클래스 데이터가 잘못된 경우이 호출로 인해 ClassFormatError 가 발생합니다.

if (resolveIt) {resolveClass (result); }

The last class loader-specific requirement is to call resolveClass() if the boolean parameter resolveIt was true. This method does two things: First, it causes any classes that are referenced by this class explicitly to be loaded and a prototype object for this class to be created; then, it invokes the verifier to do dynamic verification of the legitimacy of the bytecodes in this class. If verification fails, this method call will throw a LinkageError, the most common of which is a VerifyError.

Note that for any class you will load, the resolveIt variable will always be true. It is only when the system is recursively calling loadClass() that it may set this variable false because it knows the class it is asking for is already resolved.

 classes.put(className, result); System.out.println(" >>>>>> Returning newly loaded class."); return result; } 

The final step in the process is to store the class we've loaded and resolved into our hash table so that we can return it again if need be, and then to return the Class reference to the caller.

Of course if it were this simple there wouldn't be much more to talk about. In fact, there are two issues that class loader builders will have to deal with, security and talking to classes loaded by the custom class loader.

Security considerations

Whenever you have an application loading arbitrary classes into the system through your class loader, your application's integrity is at risk. This is due to the power of the class loader. Let's take a moment to look at one of the ways a potential villain could break into your application if you aren't careful.

In our simple class loader, if the primordial class loader couldn't find the class, we loaded it from our private repository. What happens when that repository contains the class java.lang.FooBar ? There is no class named java.lang.FooBar, but we could install one by loading it from the class repository. This class, by virtue of the fact that it would have access to any package-protected variable in the java.lang package, can manipulate some sensitive variables so that later classes could subvert security measures. Therefore, one of the jobs of any class loader is to protect the system name space.

In our simple class loader we can add the code:

 if (className.startsWith("java.")) throw newClassNotFoundException(); 

just after the call to findSystemClass above. This technique can be used to protect any package where you are sure that the loaded code will never have a reason to load a new class into some package.

Another area of risk is that the name passed must be a verified valid name. Consider a hostile application that used a class name of "..\..\..\..\netscape\temp\xxx.class" as its class name that it wanted loaded. Clearly, if the class loader simply presented this name to our simplistic file system loader this might load a class that actually wasn't expected by our application. Thus, before searching our own repository of classes, it is a good idea to write a method that verifies the integrity of your class names. Then call that method just before you go to search your repository.

Using an interface to bridge the gap

The second non-intuitive issue with working with class loaders is the inability to cast an object that was created from a loaded class into its original class. You need to cast the object returned because the typical use of a custom class loader is something like:

 CustomClassLoader ccl = new CustomClassLoader(); Object o; Class c; c = ccl.loadClass("someNewClass"); o = c.newInstance(); ((SomeNewClass)o).someClassMethod(); 

However, you cannot cast o to SomeNewClass because only the custom class loader "knows" about the new class it has just loaded.

There are two reasons for this. First, the classes in the Java virtual machine are considered castable if they have at least one common class pointer. However, classes loaded by two different class loaders will have two different class pointers and no classes in common (except java.lang.Object usually). Second, the idea behind having a custom class loader is to load classes after the application is deployed so the application does not know a priory about the classes it will load. This dilemma is solved by giving both the application and the loaded class a class in common.

There are two ways of creating this common class, either the loaded class must be a subclass of a class that the application has loaded from its trusted repository, or the loaded class must implement an interface that was loaded from the trusted repository. This way the loaded class and the class that does not share the complete name space of the custom class loader have a class in common. In the example I use an interface named LocalModule, although you could just as easily make this a class and subclass it.

첫 번째 기술의 가장 좋은 예는 웹 브라우저입니다. 모든 애플릿에서 구현되는 Java에 의해 정의 된 클래스는 java.applet.Applet 입니다. 클래스가 AppletClassLoader 에 의해로드 될 때 생성 된 객체 인스턴스는 Applet 의 인스턴스로 캐스팅됩니다 . 이 캐스트가 성공하면 init () 메서드가 호출됩니다. 제 예에서는 두 번째 기술인 인터페이스를 사용합니다.

예를 가지고 놀기

예제를 마무리하기 위해 몇 개 더 만들었습니다.

.자바

파일. 이것들은:

public interface LocalModule {/ * 모듈 시작 * / void start (String option); }