본 장은 JEUS JNDI의 기본적인 개념과 용어 그리고 환경 설정 방법 및 애플리케이션을 개발 방법에 대해서 설명한다.
Java Naming and Directory Interface™ (JNDI)는 Java 애플리케이션이 네트워크에서 객체를 찾고 가져올 수 있도록 하는 표준 API이다. 애플리케이션은 객체의 논리적인 이름을 통해 해당하는 객체를 찾아서 사용할 수 있다. 사용자의 관점에서 볼 때는 이전의 엔터프라이즈 환경보다 쉽게 객체를 찾아서 사용할 수 있는 환경을 제공해준다.
JEUS JNDI는 JNDI 1.2 API와 호환되며, Sun Microsystems에서 제안한 표준 JNDI API를 지원한다. 그리고 엔터프라이즈 환경에 적합하도록 JNDI Service Provider Interface(SPI)도 제공한다. 즉, JNDI SPI를 구현한 제품은 JEUS JNDI 트리의 객체를 사용할 수 있다.
JEUS JNDI 서비스는 JEUS 시스템 전반적으로 사용되므로, EJB, 서블릿/JSP, JMS, JDBC 등을 사용할 때마다 보게 될 것이다.
JEUS JNDI는 객체를 bind하고 lookup하는 고유의 아키텍처를 가지고 있다. 우선 JEUS JNDI의 구조와 기본 개념에 대해서 설명한다.
JEUS Manager를 시작하면 JEUS는 자동적으로 JNDI 서비스를 준비한다. JNDI 서비스는 JNDI Naming Server가 제공하는데, 이것이 실행될 때 내부적으로 JNS Naming Server(이하 JNSServer)가 실행된다. 이 JNSServer와 통신하기 위한 클라이언트 역할을 하는 것이 JNSLocal Naming Client(이하 JNSLocal)이다.
JNSLocal은 JNSServer와 접속되어 있어, 객체의 bind와 lookup은 JNSLocal에서 먼저하고, 다음으로 JNSServer에서 진행된다. 하나의 JNSServer는 하나 이상의 JNSLocal과 접속되어 있으며, 또한 JNSServer들도 다른 JNSServer와 접속하고 있어(특히 클러스터링 환경일 때) 트리와 같은 구조를 이루고 있다. 이 구조를 JNDI 트리라고 한다.
JNDI 트리는 객체를 bind하거나 lookup할 때 사용되는데, 모든 객체는 서버들을 통해서 JNDI 트리로 bind되고 lookup된다. 애플리케이션에서 JNSLocal로 객체의 bind을 요청하면 JNDI 트리로 전달되어 bind되며, 이렇게 bind된 객체는 각 JNSLocal을 통해 애플리케이션에서 lookup할 수 있다.
JNDI 트리의 객체를 액세스할 때는 InitialContext를 통해서만 가능하다. 그러므로 애플리케이션에서 JNDI를 사용하려면 반드시 InitialContext 객체를 생성해야만 한다. 이 객체는 JNDI 트리에 접근해서 객체를 핸들링할 수 있도록 해준다. 그리고 객체를 bind하거나 lookup할 뿐만 아니라 객체의 목록을 가져올 수 있으며 제거할 수 있는 기능을 한다.
본 절에서는 JNDI 트리 구조가 실제로 어떻게 작동되는지 자세히 설명한다.
JNDI 트리는 JNSServer와 JNSLocal로 구성되어 있다. JNSServer는 JEUS Manager의 JVM에 존재하며, JNSLocal은 각 엔진 컨테이너 또는 클라이언트의 JVM에 존재한다. JNSServer는 JEUS JNDI 아키텍처의 메인으로 JNDI 트리를 생성하고 관리한다. JNSServer는 그 하위에 JNSLocal을 둬서 관리한다.
다음 그림은 JNSServer와 JNSLocal의 관계를 나타낸다.
전체 JNDI 트리를 액세스하기 위해서는 JNSLocal이 JNSServer로 요청을 보내게 된다. 그리고 JNSServer는 다른 노드의 JNSServer와 연결되어서 클러스터링을 구성한다. JNSLocal은 JNSServer와 노드 내에서 상호 작용을 하며, 클라이언트의 액세스 요청을 처리한다. 클라이언트는 JNSServer로 바로 접근하는 것이 아니라 JNSLocal을 통해서 객체를 bind하고 lookup한다.
JNSServer는 JNDI 트리를 관리하며, JNSLocal이 JNDI 트리를 액세스할 수 있도록 하는 독립적인 Naming Server이다. JNDI 트리를 확장하기 위해서 여러 개의 JNSServer를 연결할 수 있다. JNSServer는 다른 노드의 JNSServer와 직접적으로 연결할 수 있기 때문이다. JEUS에서는 JEUS Manager가 시작되면 JNSServer는 자동적으로 JNSLocal의 접속을 기다린다.
JNSLocal의 기본적인 기능은 JNSServer로 접속하여 애플리케이션의 요청을 전송하고 JNSServer의 결과를 다시 돌려주는 것이다. 각 JVM에서는 하나의 JNSLocal 싱글턴 인스턴스가 존재한다. 그래서 lookup을 처리할 때 하나의 서버만 사용하면 되므로, 엔터프라이즈 환경에서 EJB와 서블릿을 사용할 때 효과적이다.
다음은 JNSLocal의 중요 기능이다.
JNDI 트리 접속
JNSLocal은 JNSServer에 접속해서, JEUS Manager가 관리하는 JNDI 트리로 접속하는 방법을 제공한다. bind되고 lookup되는 객체는 전체 JNDI 트리에서 공유되거나 클라이언트의 설정에 따라서 특정 클라이언트만 액세스할 수도 있다.
lookup된 객체의 캐싱
JNSLocal은 자주 사용되는 객체를 캐싱해서 클라이언트가 더 빠르게 사용할 수 있도록 한다. JNSLocal은 JNSServer와 통신을 하면서 객체를 캐싱(caching)한다.
JNSServer와의 연결 관리
JNSLocal은 클라이언트의 요청을 받아서 JNSServer로 전달하고, 그 결과를 받아서 리턴한다. 클라이언트가 있는 JVM에 JNSLocal이 존재하므로 별다른 I/O 없이 효율적으로 통신할 수 있다.
JNSLocal에는 JNSServer의 위치(같은 노드인지 아니면 원격지에 있는지)에 따라서 2가지 종류로 나뉜다.
JNDI 트리는 JNSServer와 JNSLocal 간의 연결로 구성되어 있다. 이 구조는 하나의 컴포넌트(JNSLocal)에서 객체에 대한 정보의 변화가 있을 때 다른 컴포넌트(JNSLocal)로 전달될 수 있도록 하며, 또한 여러 개의 노드를 지원(클러스터링)할 수 있도록 한다.
즉, JNDI Naming 클러스터링은 각각의 독립적인 JNDI를 하나의 확장된 JNDI 트리로 구성하는 것이라 할 수 있으며, 이때도 마찬가지로 각 JNDI 트리에서 발생하는 객체에 대한 정보의 변화가 있을 때에는 나머지 다른 JNDI로 그 내용이 전달된다. 따라서 JNDI Naming 클러스터링 환경에서는 하나의 Naming Server에서 바인딩한 객체를 다른 여러 Naming Server에서 이 객체를 lookup할 수 있게 된다.
예를 들어 다음 그림처럼 4개의 노드 A, B, C, D가 클러스터링을 구성하고 있는 환경에서 Node A에 obj1이라는 객체를 'objName1'이라는 이름으로 바인딩하면, 이는 나머지 3개의 노드 B, C, D에도 이 객체가 전달된다. 따라서 처음에 클라이언트가 직접 바인딩을 시도한 Node A가 아닌 다른 3개의 노드에서도 'objName1'으로 lookup하면 obj1이라는 객체를 얻어올 수 있게 된다.
각 노드의 JEUS Manager는 각각의 JNSServer를 관리할 책임을 가지고 있다. 각 JNSServer는 JEUS 시스템이 기동될 때 시작되어서 다른 노드의 JNSServer와 연결된다. 각 JEUS 엔진은 InitialContext를 가져옴과 동시에 JNSLocal이 JNSServer로 연결된다. 클라이언트가 JNDI 트리의 객체를 lookup할 때 JNSLocal로 요청하고, 이어서 해당 노드의 JNSServer로 요청이 가게 된다. 그리고 이에 대한 객체를 클라이언트가 받게 된다.
별도의 설정을 하지 않은 경우 JNDI lookup은 자신이 포함된 JEUS/JNDI 클러스터링 영역에 대해서 수행된다. 그러나 애플리케이션이 클러스터링에 있지 않은 다른 JEUS Manager의 JNDI 서버에 있는 내용을 lookup할 때에는 PROVIDER URL(“JEUS Reference Book”의 “1.5. JNDI 시스템 프로퍼티” 참고)을 지정해서 Context를 생성하거나 lookup할 때 JEUS에서 생성한 jh(jeus host) 프로토콜을 사용한 이름으로 lookup해야 한다. 이에 대한 내용은 “6.5. JNDI 프로그래밍”을 참고한다.
JEUS의 각 JNSServer는 다른 서버와 연결되어서 상호 작용하기를 기다리고 있다. 기존의 클러스터로 새로운 노드가 들어오면, 새로 들어온 노드의 JNSServer는 시작할 때 이미 존재하는 다른 JNSServer로 통보를 보내게 된다. 이때 각 JNSServer는 자신의 데이터를 새로 들어온 JNSServer로 전송하게 되며, 이렇게 해서 새로 들어온 JNSServer에서도 기존에 bind되어 있는 객체를 lookup할 수 있게 된다.
이런 확장성으로 인해 이상 작동하여 재기동된 JNSServer는 다른 JNSServer로부터 JNDI 트리 정보를 받아 정상 상태로 동작할 수 있다.
JNDI Naming Server는 JNSServer와 JNSLocal로 구성되어 있다. 이 둘은 서로 다른 설정을 가진다.
JNSServer에서는 JNSLocal의 커넥션을 받아들이는 설정과 다른 JNSServer와 접속하기 위한 설정이 필요하고, JNSLocal은 JNSServer와 접속하기 위한 것과 JNDI 트리의 반영을 위한 설정이 필요하다.
본 절에서는 JNSServer와 JNSLocal을 설정하는 것에 대해서 설명한다.
JEUS Manager에서 JNSServer가 실행되기 때문에 JNSServer의 설정은 JEUSMain.xml에 한다. 이 설정은 WebAdmin을 사용하거나 직접 XML 파일을 수정한다.
다음은 JNSServer 설정을 하기 위해 JEUSMain.xml 파일을 설정한 예로 <naming-server>의 <server> 태그는 다음과 같아야 한다.
[예 6.1] Naming server 설정 : <<JEUSMain.xml>>
<jeus-system> . . . <naming-server> <server> <use-nio>true</use-nio> <export-cos-naming>true</export-cos-naming> <backlog-size>100</backlog-size> <pooling> <min>1</min> <max>10</max> <period>30000</period> </pooling> </server> </naming-server> </jeus-system>
다음은 설정 태그에 대한 설명이다.
이 파일을 수정한 다음 JEUS를 재시작해야만 변경된 내용이 적용된다.
JNSLocal은 놓이는 위치에 따라서 설정하는 법이 다르다.
Server-side JNSLocal은 JNSLocal이 JEUS Manager와 같이 실행되는 것을 의미한다.
JEUSMain.xml을 직접 수정할 때는 <naming-server>의 <local> 태그를 사용해서 다음과 같이 한다.
[예 6.2] Server-side JNSLocal 설정 : <<JEUSMain.xml>>
<jeus-system> . . . <naming-server> . . . <local> <pooling> <min>1</min> <max>10</max> <period>30000</period> </pooling> </local> </naming-server> . . . </jeus-system>
다음은 설정 태그에 대한 설명이다.
설정 파일을 수정한 후에는 JEUS를 재시작해야 변경 내용이 적용된다.
Client-side JNSLocal은 JVM이 서로 다른 JNSServer를 액세스한다. 그래서 JNSServer와 연결되어서 JNDI 트리의 내용을 반영하는 Thread가 존재한다. JEUS JNDI는 Thread Pool로 Thread를 관리한다. 이 Thread Pool은 JEUS 프로퍼티를 사용해서 설정하며, 기본값을 사용해도 무방하다.
Client-side JNSLocal의 프로퍼티를 설정하려면 JVM의 시스템 프로퍼티를 사용하거나 InitialContext의 Hash Table을 사용해야 한다.
다음은 Client-side JNSLocal의 프로퍼티의 종류이다.
만약 EJB와 서블릿/JSP 같은 서버 측 객체에서 JNDI를 사용한다면 JEUS Manager에 의해서 Server-side JNSLocal을 사용해서 bind나 lookup되기 때문에 이런 프로퍼티를 설정할 필요가 없다. 프로퍼티에 대한 자세한 내용은 “JEUS Reference Book”의 “1.5. JNDI 시스템 프로퍼티”를 참조한다.
JEUS 노드 클러스터링 환경이 구성되어 있다면 JNDI를 클러스터링하기 위해서 별도의 작업은 필요없다. JEUS 노드를 클러스터링하는 것에 대해서는 “제4장 JEUS 클러스터링”을 참고한다.
만약 JEUS 노드가 클러스터링된다면 각 JNSServer도 자동적으로 클러스터링 환경을 구성한다. JEUSMain.xml에 다른 노드의 정보를 설정하면 JEUS Manager가 클러스터 내의 다른 JNSServer로 접속한다. JNSLocal은 각각의 JNSServer로 접속하고, 마치 클러스터링 환경이 아닌 것처럼 작동한다. 그러나 JNSLocal이 Client-side일 경우에는 Context.PROVIDER_URL에 접속하려는 JNSServer를 설정해야 한다. 기본적으로 JNSLocal은 로컬 JNSServer로 접속한다.
클러스터에 있는 클라이언트는 JNDI 트리에 bind되어 있는 어떤 객체든 lookup할 수 있다. 만약 클라이언트가 JNSLocal에 객체를 bind한다면 곧 JNSServer를 통해서 모든 노드에서 그것을 공유하게 된다. 또한 데이터 삭제나 변경이 발생하면 곧 각 노드의 JNSLocal에도 반영된다.
JEUS JNDI에서는 객체의 성격에 따라서 bind하는 3가지 옵션을 제공한다. 어떤 객체는 클러스터 전체에 공유되고, 또 어떤 객체는 자신의 노드에서만 존재한다거나, 또 어떤 것은 자신의 JVM 내에서만 보여야 할 경우가 있다.
JVM 옵션 [-D] 사용
Context 클래스의 addToEnvironment() 메소드 사용
기능을 사용하려면 다음의 3가지 프로퍼티를 설정해야 한다.
REPLICATE_BINDINGS
CACHE_BINDINGS
LOCAL_BINDINGS
다음은 REPLICATE_BINDINGS와 CACHE_BINDINGS 옵션을 사용하고, LOCAL_BINDING을 사용하지 않는 예제이다.
ctx.addToEnvironment(JNSContext.REPLICATE_BINDINGS, “true”); ctx.addToEnvironment(JNSContext.CACHE_BINDINGS, “true”); ctx.addToEnvironment(JNSContext.LOCAL_BINDINGS, “false”); ctx.bind(“name”, object);
다음은 addToEnvironment() 메소드의 설정 프로퍼티에 대한 설명이다.
JNDI 트리 전체로 객체를 사용하려면 JNSLocal은 객체를 JNSServer로 전송해야 한다. 그러면 JNSServer는 연결되어 있는 모든 JNSServer로 그 객체를 넘기게 된다.
설정값 | 설명 |
---|---|
true | 객체는 모든 JNSServer로 퍼진다. (기본값) |
false | 로컬 노드의 JNSServer 내에서만 유지한다. |
클라이언트가 객체를 lookup하면 JNSLocal은 항상 JNSServer로부터 객체를 lookup해온다. 같은 객체를 여러 번 lookup하여 사용할 경우에는 객체를 캐싱하여 사용하면 매번 JNSServer에서 객체를 가져오는 오버헤드를 줄일 수 있다.
설정값 | 설명 |
---|---|
true | 클라이언트에서 이 객체를 lookup했을 때 lookup을 한 후 바로 JNSLocal의 저장소에 캐싱(Caching)한다. 이후부터는 JNSLocal은 JNSServer로부터 객체를 lookup하지 않고 Cache된 객체를 사용한다. (기본값) |
false | 클라이언트에서 이 객체를 lookup했을 때 lookup을 한 후 바로 JNSLocal의 저장소에 캐싱(Caching)하지 않는다. |
본 절에서는 JEUS JNDI를 사용하는 프로그래밍 방법에 대해서 설명한다.
Java 클라이언트는 InitialContext를 사용해서 JNDI 트리를 접근한다. InitialContext에 사용되는 프로퍼티는 JNDI 표준 프로퍼티와 JEUS용 프로퍼티가 있다.
먼저 JNDI 환경을 설정하고, 그 다음으로 InitialContext를 사용해서 객체를 lookup한 다음, 객체의 레퍼런스를 가져온다. 마지막으로 InitialContext를 사용한 다음에는 반드시 close시켜준다.
다음의 과정으로 Java 클라이언트는 JEUS JNDI 서비스를 사용한다.
JEUS 환경설정
InitialContext를 위한 프로퍼티 설정
Context를 사용한 Named Object의 lookup
Named Object를 사용해서 객체의 레퍼런스 취득
Context 닫기
다음은 각 단계별 자세한 설명이다.
JNDI 서비스에서 필요한 클래스를 사용할 수 있도록 JEUS 클라이언트 모듈의 경로(JEUS_CLIENT)를 클래스 패스에 설정한다.
-classpath %JEUS_CLIENT%;
Java 클라이언트가 JEUS JNDI 서비스를 사용하기 전에 다음의 2가지 방법으로 InitialContext의 환경 프로퍼티를 설정한다.
JVM의 ‘-D’ 옵션을 사용한다.
Hash Table을 생성하여 InitialContext의 생성자에게 넘긴다.
위의 2가지 방법 중 Hash Table을 생성하여 InitialContext의 생성자에게 넘기는 방법보다 JVM의 '-D' 옵션을 사용하는 방법이 우선한다.
JEUS JNDI의 InitialContext를 생성하기 위해서 설정해야 하는 프로퍼티는 다음과 같다.
Context.INITIAL_CONTEXT_FACTORY (required)
Context.URL_PKG_PREFIXES
Context.PROVIDER_URL
Context.SECURITY_PRINCIPAL
Context.SECURITY_CREDENTIALS
프로퍼티에 대한 자세한 사항은 “JEUS Reference Book”의 “1.5. JNDI 시스템 프로퍼티”를 참조한다.
이 프로퍼티들을 Hash Table에 넣어서 InitialContext를 생성할 때 사용한다. 만약 서버 측 객체 내(EJB나 서블릿/JSP)에서만 InitialContext를 사용한다면 별도의 설정 없이 기본으로 설정된 InitialContext를 사용한다.
클라이언트 프로그램에서는 다음과 같이 설정한다.
Context ctx = null; Hashtable ht = new Hashtable(); ht.put(Context.INITIAL_CONTEXT_FACTORY, "jeus.jndi.JNSContextFactory”); ht.put(Context.URL_PKG_PREFIXES, “jeus.jndi.jns.url”); ht.put(Context.PROVIDER_URL, “<hostname>
”); ht.put(Context.SECURITY_PRINCIPAL, “<username>
”); ht.put(Context.SECURITY_CREDENTIALS, “<password>
”); try { ctx = new InitialContext(ht); // use the context } catch (NamingException ne) { // fail to get an InitialContext } finally { try{ ctx.close(); catch (Exception e) { // an error occurred } }
클러스터링 환경에서 JNDI를 사용할 때는 JNDI 트리를 구성하고 JNSLocal의 내부적인 작동을 효과적으로 관리하기 위해서 jeus.jndi.JNSContext의 추가적인 환경 프로퍼티를 사용할 수 있다.
Hash Table을 사용해서 Context 생성하기
InitialContext의 환경 프로퍼티를 설정할 때 Hash Table(java.util.Hashtable)의 키로 프로퍼티 이름을 넣고, 값으로 프로퍼티의 데이터를 넣은 후에 InitialContext 생성자의 파라미터로 넣어준다.
Security Context 생성하기
JEUS Security 도메인의 사용자를 실행 스레드에 적용시키기 위해서 몇 가지 보안 관련 환경 프로퍼티를 사용할 수 있다. InitialContext가 생성될 때 Security Context는 다음의 프로퍼티로 구성한다.
사용자명 프로퍼티 : java.naming.security.principal
패스워드 프로퍼티 : java.naming.security.credentials
인증이 성공하면 인증된 사용자의 정보가 실행 Thread에 설정되며, JEUS Security Manager가 관리하는 리소스를 액세스할 수 있다. 만약 인증에 실패하면 Thread는 guest 사용자로 인식된다.
만약 Security Context가 설정되어 있다고 하더라도 InitialContext가 생성되기 전에 JEUS Security API를 사용해서 로그인한 경우 Security Context는 무시된다. 즉, 이미 로그인된 subject를 사용해서 JNDI 통신을 수행한다.
1. 사용자 정보 세팅에 관한 더 자세한 내용과 JEUS Security Service에 대한 내용은 "JEUS Security 안내서"를 참조한다.
2. 환경 프로퍼티에 대한 자세한 것은 “JEUS Reference Book”의 “1.5. JNDI 시스템 프로퍼티”를 참조한다.
JNDI 트리에 bind된 객체는 JNDI Context의 lookup() 메소드를 사용해서 찾는다.
다음은 lookup하려는 객체의 이름이 ejbAppHome1인 경우의 예제이다.
try { Context ctx = new InitialContext(); AppHome appHome1 = (AppHome)ctx.lookup(“ejbAppHome1”); // successfully got the object } catch (NameNotFoundException ex) { // no such binding exists } catch (NamingException e) { // an error occurred }
lookup한 객체를 사용한다.
다음은 EJB 클라이언트에서는 lookup한 EJB 홈 객체의 create() 메소드를 사용해서 EJB 리모트 객체의 레퍼런스를 가져오는 예제이다. 예와 같이 메소드를 실행해서 사용하려는 객체의 레퍼런스를 가져오고, 그 객체 레퍼런스의 메소드를 실행한다.
AppBean bean = appHome1.create(); bean.service();
다음과 같이 Context를 사용한 다음에는 close()메소드를 실행해서 Context를 닫아준다.
try { cx.close(); } catch(Exception e) { // an error occurred }
JEUS 클러스터링 환경에서 사용할 수 있는 Context를 생성하기 위해서는 다음과 같이 Context.PROVIDER_URL에 여러 개의 호스트를 설정한다.
Hashtable ht = new Hashtable(); ht.put(Context.PROVIDER_URL, "host1:9736,host2:9736");
애플리케이션이 클러스터링에 있지 않은 다른 JEUS Manager의 JNDI 서버에 있는 내용을 lookup할 때에는 PROVIDER URL을 지정해서 Context를 생성하거나 lookup할 때 JEUS에서 생성한 jh(jeus host) 프로토콜을 사용한 이름으로 lookup해야 한다.
다음은 JEUS의 클러스터링 환경에서 원격으로 lookup을 실행할 수 있도록 하는 특별한 lookup 구문이다. 이 구문은 자신이 속한 JEUS/JNDI 클러스터링의 영역을 벗어나 원격지의 JEUS/JNDI 클러스터링에 있는 객체를 lookup할 때 사용한다.
jh:<remote JEUS host name>:<remote JEUS base port>/<export name> ("jh" = JEUS host)
다음은 lookup 구분의 사용법으로 JNDI Context 문자열 내에 다음과 같은 구문을 입력한다.
try { Context ctx = new InitialContext(); AppHome appHome1 = (AppHome)ctx.lookup("jh:dev:9736/ejbAppHome1"); // successfully got the object } catch (NameNotFoundException ex) { // no such binding exists } catch (NamingException e) { // an error occurred }