2009. 2. 26. 09:37

클래스로더 관련

//----------------------------------------------------------------------------



//----------------------------------------------------------------------------

아키텍트와 클래스로더(2007/10/9)

JLab 허원진

 

자바 프로그램의 흐름은 클래스에 의해 좌우된다. 클래스의 실행 여부, 클래스의 억세스 가능 여부 등 시스템의 핵심이다. 아키텍트에 어떻게 클래스로더를 이용하는지 알아보도록하자.

 

Class Loader

넷에는 많은 클래스로더에 관한 글들이 있다. 그 글들에서 잘 다루진 않은 내용 중심으로 클래스로더에 대해 간략이 이야기 해보자. 존재 하지만 쓰지는 않는다. 99%이상의 자바 어플리케이션 개발자들은 클래스로더를 직접적으로 다루지 않는다. 그들이 사용하는 프레임워크에 이미 클래스로더가 정의되어있기 때문이다. 따라서 프레임워크 자체를 개발 하는 코어 개발자거나 그 프레임워크를 재개발하는 3rd Party 코어 개발자가 아니라면 클래스로더를 사용할 필요가 없는것이다. (단순히 기존의 파일 형태의 클래스가 아닌 클래스를 로딩하기 위해 클래스로더를 확장하는 것은 제외하자) 

위 그림에서 Root, A, B 모두 클래스라 생각하자 Root 종류의 클래스 하나와, A 종류의 클래스 하나, B 종류의 클래스 3개가 있다. 화살표는 억세스 가능을 나타낸다. 위와 같은 관계를 정의하기 위해서는 몇가지의 디자인패턴과 inner class가 생각날 것이다. (위의 문제를 해결하기 위해 스크립트언어를 이용하는것도 방법이긴 하지만 비효율적이다.)fixed 된 경우와 단순한 흐름의 경우는 방법일 수도 있지만, 억세스하고 실행할 수 있는 상황과 위와 같은 관계가 dynamic하게 변한다면 디자인패턴과 inner class만으로는 해결이 불가능하다. 또한 자신의 전부에 대한 억세스 권한이 아닌 일부만 주어야할 경우 상황은 더욱 복잡해진다.

어떻게 클래스로더로 해결이 가능할까. 아래의 테이블을 살펴보자.

Class Loader1 net.jlab.incubatorr.common.service
Class Loader2 net.jlab.incubatorr.common.service
Class Loader3 net.jlab.incubatorr.common.service

현재 JVM 위에는 3개의 클래스로더가 올려져있다. 하지만, 각각의 클래스로더는 같은 클래스(net.jlab.incubatorr.common.service)를 로딩하고 있다. 하지만 이들은 각각 분리된 상태이다. Class Loader가 name-space역할을 하기 때문이다. 이미 net.jlab.incubatorr.common.service 클래스가 Class Loader1에 의해서 로딩된 상태라도 Class Loader2에서는 net.jlab.incubatorr.common.service클래스가 로딩되지 않았고 찾지 못했다면 에러가 발생하게 된다. 자바 어플리케이션의 진입점인 main 클래스와 classpath에 잡혀있는 클래스는 system class loader가 일반 JDK 라이브러리는 primordial class loader가 맡아서 클래스를 로딩한다. System/primordial class loader를 사용하지 않고 Class Loader를 따라 구현하여 클래스를 로딩한다면 완전히 독립적으로 각 클래스(컴포넌트) 간의 관계를 정의할 수 있다.

Note 아래의 내용은 이 기사 전체를 이해하기 위한 핵심이다. 감이 안올 경우 첨부한 소스의 클래스 선언부와 참조부에 브레이크 포인트를 걸어서 디버그를 해보기 바란다.

Class Loader1이 로딩한 클래스를 Class Loader2의 클래스가 접근하려면 어떻게 해야할까. Class Loader1에 의해 로딩된 클래스들은 참조할 클래스를 찾기 위해서 Class Loader1을 이용한다. 따라서 자신을 로딩한 클래스로더가 못찾을 경우 다른 클래스 로더를 통해 찾도록 하면 된다. 다음과 같은 시퀀스가 된다.

1.primordial class loader 참조 요구

2.클래스 참조

3.자신을 로딩한 클래스 로더에게 클래스를 참조를 요구

4.이미 로딩된 클래스 여부 확인

4a.이미 로딩된 경우 참조 리턴

4b.이미 로딩되지 않은 경우 에러처리 - 다른 클래스 로더로 찾기

package net.jlab.classloader;

import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class MyClassLoader1 extends ClassLoader {
  String basePath;

  public MyClassLoader1(ClassLoader parent, String basePath)
      throws FileNotFoundException {
    super(parent);
    this.basePath = basePath;
  }

  public Class findClass(String namethrows ClassNotFoundException {
    System.out.println("[MyClassLoader1]Finding " + name);
    Class clazz = null;
    String classFileName;
    try {
      classFileName = name.substring(name.lastIndexOf("."1, name
          .length());

      String fullPath = basePath + "/" + classFileName + ".class";
      byte[] classBytes = loadClassData(fullPath);
      return defineClass(name, classBytes, 0, classBytes.length);

    catch (Exception ex) {
      clazz = super.findSystemClass(name);
      
    finally {
    }
    return clazz;

  }

  private byte[] loadClassData(String classNamethrows IOException {

    File f = new File(className);
    int size = (intf.length();
    byte buff[] new byte[size];
    FileInputStream fis = new FileInputStream(f);
    DataInputStream dis = new DataInputStream(fis);

    dis.readFully(buff);
    dis.close();
    return buff;
  }

}

클래스를 못찾았을 경우 발생하는 예외이다. 이 예외 안에서 다른 클래스 로더가 처리하도록 할 수 있다. 예외 처리를 안하면 결국 ClassNotFound 예외를 보게 된다.

[MyClassLoader1]Finding root.component1.Component1
[MyClassLoader1]Finding net.jlab.classloader.IComponent
Exception in thread "main" java.lang.NoClassDefFoundError: net/jlab/classloader/IComponent
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:620)
at java.lang.ClassLoader.defineClass(ClassLoader.java:465)
at net.jlab.classloader.MyClassLoader1.findClass(MyClassLoader1.java:28)
at net.jlab.classloader.FrameWorkRoot.init(FrameWorkRoot.java:16)
at net.jlab.classloader.ClassLoaderTest.test(ClassLoaderTest.java:15)
at net.jlab.classloader.ClassLoaderTest.main(ClassLoaderTest.java:10)

클래스 로더를 클래스(컴포넌트)간에 나눈후 클래스 로더들 간의 참조는 가능하게 하면 각 클래스 간의 독립성은 유지하면서 클래스(컴포넌트)간의 관계를 설정 할 수 있다.

 

Secure Framework

지금 까지 알아본 내용을 바탕으로 억세스를 제안하는 제한 기능이 있는 프레임워크를 만들어 보자. 이 프레임워크는 SecuritySerivce를 통하여 컴포넌트간 억세스를 제한한다. 프레임워크는 억세스 정책을 읽어서 SecurityService를 통하여 적용한다. Component는 로컬 저장장치에 일반 클래스 형태로 저장된다.

package net.jlab.classloader;

import java.io.FileNotFoundException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import common.IComponent;
import common.SecurityService;


public class SecureFrameWorkRoot {
  SecurityService security= SecurityService.getService();

  public void init() {
    String path = "C:/Documents and Settings/jini.JLAB/workspace/Component/root";
    try {
      MyClassLoader1 componentRoot = new MyClassLoader1(null, path);
      security.addClassLoader("root", componentRoot);
      
      MyClassLoader1 component1 = new MyClassLoader1(null, path
          "/component1");
      security.addClassLoader("component1", component1);
      security.addGrantedComponent("root","component1");
      
      IComponent component = (IComponentcomponent1.findClass(
          "root.component1.Component1").newInstance();
      component.start();

    catch (FileNotFoundException e) {
      e.printStackTrace();
    catch (ClassNotFoundException e) {
      e.printStackTrace();
    catch (InstantiationException e) {
      e.printStackTrace();
    catch (IllegalAccessException e) {
      e.printStackTrace();
    }

  }
}

각각의 컴포넌트는 서로 다른 클래스로더에 의해 로딩된다. 클래스로더 설정과 동시에 억세스 정책이 적용된다.

클래스로더 설정과 정책 설정이 끝난 후 컴포넌트들을 기동하기 시작한다.

이 라인을 주석 처리하게 되면 Component1에서 root로 접근이 불가능해진다. 따라서 에러가 발생하게 된다.

[component1] Finding root.component1.Component1
[component1] Finding common.IComponent
[component1] Finding root.CommonService
Exception in thread "main" java.lang.NoClassDefFoundError: root/CommonService
at root.component1.Component1.start(Component1.java:13)
at net.jlab.classloader.SecureFrameWorkRoot.init(SecureFrameWorkRoot.java:27)
at net.jlab.classloader.ClassLoaderTest.test(ClassLoaderTest.java:15)
at net.jlab.classloader.ClassLoaderTest.main(ClassLoaderTest.java:10)

이 경로는 필자의 이클립스 웍스페이스 경로이다. 자신에게 맞게 수정해야지 예제 실행이 가능하다.

package common;

import java.util.ArrayList;

import net.jlab.classloader.MyClassLoader1;

public class SecurityService {
  private SecurityService() {
  }

  static SecurityService security;

  static public SecurityService getService() {
    if (security == null)
      security = new SecurityService();
    return security;
  }

  ArrayList<SecureMapItem> secureMap = new ArrayList();

  public Class findClass(String compId, String className)
      throws ClassNotFoundException {
    SecureMapItem item = getSecureItem(compId);
    if (item != null)
      return item.findClass(compId, className);

    return null;
  }

  public Class findClass(MyClassLoader1 classLoader, String className)
      throws ClassNotFoundException {
    SecureMapItem item = getSecureItem(classLoader);
    String compId;
    Class clazz;
    if (item != null) {
      compId = item.getId();
      for (int i = 0; i < secureMap.size(); i++) {
        item = secureMap.get(i);
        clazz = item.findClass(compId, className);
        if (clazz != null)
          return clazz;
      }
    }

    return null;
  }

  private SecureMapItem getSecureItem(MyClassLoader1 classLoader) {
    SecureMapItem item;
    for (int i = 0; i < secureMap.size(); i++) {
      item = secureMap.get(i);
      if (item.getClassLoader() == classLoader) {
        return item;
      }
    }
    return null;
  }

  public void addClassLoader(String id, MyClassLoader1 classLoader) {
    classLoader.setId(id);
    secureMap.add(new SecureMapItem(id, classLoader));
  }

  protected SecureMapItem getSecureItem(String id) {
    SecureMapItem item;
    for (int i = 0; i < secureMap.size(); i++) {
      item = secureMap.get(i);
      if (item.getId().equals(id)) {
        return item;
      }
    }
    return null;
  }

  public void addGrantedComponent(String id, String grantedId) {
    getSecureItem(id).addGrantedId(grantedId);
  }
}

class SecureMapItem {
  String id;

  MyClassLoader1 classLoader;

  ArrayList grantedList = new ArrayList();

  public SecureMapItem(String id, MyClassLoader1 classLoader) {
    super();
    this.id = id;
    this.classLoader = classLoader;
  }

  public Class findClass(String id, String className)
      throws ClassNotFoundException {
    if (isGranted(id))
      return classLoader.findClass(className);
    return null;
  }

  public void addGrantedId(String grantedId) {
    grantedList.add(grantedId);
  }

  public boolean isGranted(String id) {
    if (grantedList.indexOf(id!= -1)
      return true;
    return false;
  }

  public MyClassLoader1 getClassLoader() {
    return classLoader;
  }

  public void setClassLoader(MyClassLoader1 classLoader) {
    this.classLoader = classLoader;
  }

  public String getId() {
    return id;
  }

  public void setId(String id) {
    this.id = id;
  }
}

SecurityService.java

억세스 정책을 적용하고 클래스를 찾아주는 핵심 클래스이다. 기사 앞부분에 열거한 내용들을 그대로 구현했을 뿐이다.

 

각 컴포넌트는 jar 형태의 단일 archive file로 묶고 각 억세스 정책은 xml파일로 정의하여 로딩한다면 여러가지 응용이 가능한 기초 프레임워크로 쓸 수 있을것이다.

[소스 코드 다운로드]

프레임워크와 컴포넌트를 분리하기 위해서 2개의 이클립스 프로젝트로 구성되어 있다. JDK1.5이상 환경에서 실행해야한다.

 


About Author

허원진

제이랩 대표 운영자, Total Eclipse 대표 저자, Eclipse Consultant, Juliet Scandal Project Leader. 이클립스 프로젝트들을 적극 이용해서 저비용으로 쾌속개발을 하는데 관심이 많다.Contact


www.jlab.net

이 컨텐츠는 JPL에 의해서 보호 받습니다. 오탈 자나 건의는 이곳에 해주십시오