1 java에서 class path

자바는 클래스를 기반으로 동작하도록 만들어진 프로그램 언어이다. 그리고 자바 클래스를 실행하는 엔진을 자바 가상 머신(JVM)이라 한다. JVM은 물리적으로 java.exe라는 실행 파일과 부속 라이브러리로 되어있다.

JVM(java.exe)이 수행할  클래스의 위치가 classpath이다.

클래스패스 설정

Java.exe가 인식할 수 있는 클래스패스에는 여러 가지가 있다. 그중에서 크게 java.exe가 실행하면서 로딩하는 bootclass java.lang.ClassLoader에 의해 로딩되는 일반 클래스가 있다.

Bootclass java.exe에 의해 로딩되면 JVM이 초기화되면서 필요한 클래스들로 이루어져 있다.System.getProperty("sun.boot.class.path")에서 확인할 수 있다.

JVM초기화 과정이 끝나면 java java.lang.ClassLoader를 통해서 어플리케이션 클래스들을 로딩한다. System.getProperty("java.class.path")에서 값을 확인할 수 있다.

Hello 클래스를 만들어 실행하는 방식을 알아보자.

>Hello.java를 컴파일하여 hello.jar파일 생성

1.       Jar파일을 CLASSPATH환경변수에 설정

>set CLASSPATH=hello.jar

>java test.Hello

2. jar파일을 -cp 혹은 -classpath를 이용하여 직접 설정

>java  -cp hello.jar  test.Hello

>java  -classpath hello.jar test.Hello

3. Xbootclasspath hello.jar를 설정하여 실행하는 방법

>java  -Xbootclasspath/p:hello.jar  test.Hello

>java  -Xbootclasspath/a:hello.jar  test.Hello

4. JAVA_HOME/jre/lib/ext hello.jar를 복사하고 실행

>copy hello.jar %JAVA_HOME%/jre/lib/ext/.

>java  -Xbootclasspath/a:hello.jar  test.Hello

JAVA_HOME/jre/lib/ext의 파일들은 System.getProperty("java.class.path")에 표현되지 않는다.

5. -jar를 이용하여 바로 실행

Jar파일내에 /META-INF/MANIFEST.MF 파일을 만들어 넣는다. 만약 존재하면 다음과 같이Main-Class: 속성을 추가한다.

Manifest-Version: 1.0

Main-Class: test.Hello

그리고 다음과 같이 실행하면 test.Hello가 실행된다.

>java  -jar hello.jar

 -jar로 수행할 때 클래스 패스를 추가하려면

Manifest-Version: 1.0

Main-Class: test.Hello

Class-Path: a.jar b.jar

클래스 로딩 순서

만약 동일한 클래스가 여러곳에 설정되어있을 때 로딩되는 우선순위는 다음과 같다.

>Xbootclasspath에 설정된 파일  ç BOOT LOADER

> JAVA_HOME/jre/lib/ext에 있는 파일  ç EXT LOADER

>MENIFEST.MF Class-Path:에 설정된 클래스  ç APP LOADER

>-cp or -classpath에 설정된 파일                   ç APP LOADER

>환경변수 CLASSPATH에 설정된 파일               ç APP LOADER

ü        Java  -jar로 클래스를 실행한 경우 클래스패스를 추가하고자 하면JAVA_HOME/jre/lib/ext에 복사하거나 MENIFEST.MF Class-Path를 편집하는 방법이 있다.

ü        App Loader를 위한 클래스 패스 설정은 같이 사용할 수 없고 그 중에서 하나를 선택해야 한다.

클래스 로더 구조

자바에서 모든 클래스로더는 java.lang.ClassLoader child클래스 이다. 클래스 로더에서 말하는Parent/Child관계는 상속관계를 의미하는 것이 아니고 ClassLoader Chain에서 선행 Node를 의미한다.


여기서 ExtClassLoader AppClassLoader Parent ClassLoader이다. 마찮가지로AppClassLoader SubAppClassLoader parent이다.

ClassLoader의 주요 메소드의 소스를 파악하면 쉽게 이해 할 수 있다.

protected Class findClass(String name) throws ClassNotFoundException {

             throw new ClassNotFoundException(name);  ç 각 로더 마다 재정의된다.

 }

 

protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException

    {

             // First, check if the class has already been loaded

             Class c = findLoadedClass(name);

             if (c == null) {

                 try {

                           if (parent != null) {

                               c = parent.loadClass(name, false); ç ParentLoader 에게 먼저 요청

                           } else {

                               c = findBootstrapClass0(name);

                           }

                 } catch (ClassNotFoundException e) {

                     // If still not found, then call findClass in order to find the class.

                     c = findClass(name);

                 }

             }

             return c;

    }

각 클래스로더는 findClass를 재정의해야 한다.각 로더의 findClass  클래스 패스에서  요청된 클래스가 존재하는지 확인하고 로딩한다.

개별 로더는 자신의 클래스 패스를 검색하기 전에 parent클래스 로더에게 해당 클래스 로딩을 먼저 요청한다. 따라서 자바에서 동일한 파일을 여러 곳에 복사하더라도 항상 최상위 클래스패스의 클래스가 로딩된다.

2 Application Server ClassLoader

단순한 자바 Application의 클래스로더는 SystemClassLoader(BootClassLoader), ExtClassLoader, AppClassLoader 3계층으로 구성된다. 그러나 WAS Servlet컨테이너와 그 아래에 각 Servlet Context를 위한 클래스 로더를 별도로 두고 있다.

 



AppClassLoader ServletContainerClassLoader는 같이 사용되기도 하지만 보통 분리된다. Tomcat의 경우 ServletContainerClassLoader TOMCAT_HOME/common/lib의 클래스들을 로딩한다.

ServletContainerClassLoader ServletContextClassLoader WAS벤더에 의해 구현되기 때문에 세부적인 상황이 버전에 따라 다를 수 있다. 따라서 구체적으로 클래스를 어떻게 배치할 것인가는 해당 WAS의 구현을 확인하고 결정해야 한다.

3 Servlet 2.3 spec for ClassLoader

서블릿 스팩을 보면 WAR를 위한 클래스 로딩 방식이 일반 자바 클래스 로더와 규칙이 다름을 알 수있다.

Servlet Spec

SRV.9.5 Directory Structure

The web application classloader must load classes from the WEB-INF/ classes directory first, and then from library JARs in the WEB-INF/lib directory.

..

SRV.9.7.2 Web Application Classloader

The classloader that a container uses to load a servlet in a WAR must allow the developer to load any resources contained in library JARs within the WAR following normal J2SE semantics using getResource().

It must not allow the WAR to override J2SE or Java servlet API classes.

It is further recommended that the loader not allow servlets in the WAR access to the web container’s implementation classes.

It is recommended also that the application class loader be implemented so that classes and resources packaged within the WAR are loaded in preference to classes and resources residing in container-wide library JARs.

스팩과 클래스 로더

-         WEB-INF/classes WEB-INF/lib는 동일 로더에 의해 로딩되지만 로더는 WEB-INF/clasess를 먼저 검색/로딩한다(must)

-         Servlet-API를 구현한 서블릿 컨테이너 클래스를 웹 어플리케이션이 Access할 수 없도록 하라(recommend)

-         WAR내부의 클래스를 컨테이너 클래스 패스의 클래스보다 먼저 로딩하라(recommend)

서블릿 스팩의 일부 모호한 점으로 인해 (recommend)형태의 항목은 서브릿 엔진에 따라서 다르게 구현하고 있거나 옵션 처리되어 있을 것이다.

4 ServletContextClassLoader구현

서블릿 스팩을 근거로 하여 ServletContextClassLoader(이하 SCCL)의 구현하기 위해서는 다음과 같은 형태로 로직이 구현되어야 한다. 클래스 load 요청이 들어 오면 먼저 자기 자신의 클래스 패스를 검색하고 이후에 parent 로더에게 요청한다.

 protected synchronized Class loadClass(String name, boolean resolve) {

             // First, check if the class has already been loaded

             Class c = findLoadedClass(name);

           If(c == null){

try{

c = findClass(name); ç Own Classpath 먼저 검색

} catch (ClassNotFoundException e) {

if (parent != null) {

                     c = parent.loadClass(name, false);

                  } else {

                     c = findBootstrapClass0(name);

                  }

}

}

             return c;

    }

 

위 로직은 서블릿 스팩을 완전하게 구현한 것이 아니다. 실제 서블릿 스팩을 보면 servlet API 관련한 클래스는 WAR가 아닌 Servlet Container에서 로딩하도록 되어있다. 따라서c=findClass(name)을 수행하기 전에 로딩해야 할 클래스가 서블릿 API관련한 클래스인지를 검사하는 로직이 필요하다. 만약 로딩해야할 클래스가 서블릿API관련 클래스이면 parent.loadClass()를 먼저 호출하도록 구현되어야 한다.

5 WAR 개념과 클래스 로딩의 우선순위

근원적으로 컴포넌트 컴포넌트 내부의 정보를 감추고 인터페이스 만으로 외부와 통신한다. WAR는 이러한 컴포넌트 개념을 지원하고 있으며 따라서 동일한 클래스가 WAR 외부와 내부에 존재할 때WAR는 내부의 클래스를 우선적으로 사용해야 한다


 

ContainerClassLoader WARClassLoader Parent 클래스이다. 따라서 Java클래스 로더 정책에 의해서는 Container ClassA가 로딩되어야 한다. 하지만 Servlet Spec2.3에 의해서 WAR Class A 가 로딩된다. 컴포넌트 개념에서는 외부 클래스가 아닌 내부 클래스 (Class A)가 사용되어야만 내부 로직이 바뀌어도 외부에 영향을 미치지 않으며, 컴포넌트(WAR)가 어느 컨테이너에 설치되더라도 내부 로직이 일관되게 동작할 것이다.

그러나 이것은 공통 클래스의 관리를 어렵게 할 가능성이 높다. 다음 예를 보자.

 



OracleConnection은 컴포넌트에 의존적이라기 보다는 외부자원(DBMS)에 의존적인 클래스 이다. JDBC드라이버가 WAR파일 안에 존재 한다면, JDBC 드라이버의 버전을 관리하는데 있어 모든WAR파일을 재배포해야 하는 상황이 발생하게 된다. 또한 위와 같이 Container의 공통클래스가DB를 사용하기 위해서 OracleConnection을 사용해야 하는 상황이 발생하고 이 클래스의 내용이WAR파일의 클래스와 참조관계가 형성되는 경우에는 VerifyError혹는 ClassCastException등이 발생할 수 있다.

 

따라서 WAR파일에 JDBC드라이버와 같이 외부 의존적인 클래스 혹은 Framework과 같은 공통 클래스를 같이 두는 것은 좋은 정책이 아니다. 내부 로직만을 위한 클래스이면서 외부요인에 의해서 변경되지 않아도 되는 라이브러리 클래스(ex. Regexp, Parser) WAR파일 내에 두는 것이 좋다.


'코딩과 개발' 카테고리의 다른 글

Java Launcher만들기  (0) 2012.09.19
The GNU C Library Reference Manual  (0) 2009.10.30
멀티 쓰레드 패턴  (0) 2009.10.26
Eclipse RCP 따라하기  (1) 2009.10.26
Posted by sjokim
,