2장. JBoss JMX Microkernel

JBoss 서버와 컨테이너는 그 기본부터 모듈화로 개발되어졌기 때문에 콤퍼넌트기반의 플러그인들을 사용하여 동작됩니다. 모둘화와 관련된 작업들은 JMX(자바 관리 확장 API)를 사용하여 이루어집니다. JMX을 사용하여 업계 표준의 인터페이스를 갖춤으로써 JBoss/서버 콤포넌트와 어플리케이션 배치 모두를 관리할 수 있습니다. JMX를 사용한 모듈화의 가장 큰 장점은 사용이 쉽다는 것이며 JBoss 서버 3.x 버전부터는 서버와 어플리케이션 관리의 용이성이외에 새로운 모듈 표준화와 플러그인 설계가 도입되었습니다.

이러한 고수준의 모듈화로 어플리케이션 개발자는 여러가지 장점을 얻을 수 있습니다. 작은 부분으로 구성되어야하는 어플리케이션을 지원하기위해 이미 타이트하게 코딩된 것도 보다 작게 자를 수 있습니다. 즉, 여러분의 어플리케이션에서 EJB passivation이 필요없게 된다면 서버에서 그 기능만 제거해주면 됩니다. 똑같은 어플리케이션을 ASP(어플리케이션 서비스 제공) 모델로 제공할 경우가 발생하더라도, 서버의 passivation 기능을 웹 기반의 배치관리도구를 이용하여 활성화시켜 주는 것만으로 다시 EJB passivation 기능이 구현되게 됩니다. 또 다른 예로는 TOPLink와 같은 O-R(객체-관계형 데이터베이스) 맵핑 도구를 컨테이너에 직접 사용해줄 수 있는 자유도까지 가능하게 됩니다.

이번 장에서는 JMX와 JBoss 서버 콤퍼넌트 버스에서의 JMX 역할에 대해 소개하고자 합니다. 또한 기본 JMX 관리 콤퍼넌트로 라이프 사이클 오퍼레이션을 추가시켜주는 JBoss MBean 서비스 사용법에 대해서도 소개합니다.

2.1. JMX

완전한 오픈 소스 J2EE 스택의 성공에는 JMX(Java Management Extension)의 사용이 중요한 역할을 하고 있습니다. JMX는 소프트웨어 통합에 최상의 도구입니다. JMX에서는 사용자가 모듈, 컨테이너 및 플러그-인들을 통합시킬 수 있는 범용 토대를 지원합니다. 그림 2.1, “JBoss JMX 통합 버스와 표준 JBoss 콤포넌트”에서는 콤퍼넌트들이 플러그-인되는 통합의 토대 혹은 버스로써의 JMX 역할을 보여주고 있습니다. 컴포넌트들은 MBean 서비스들로 선언된 후 JBoss에 적재되어집니다. 적재된 후 컴포넌트들은 JMX를 사용하여 관리될 수 있게 됩니다.

JBoss JMX 통합 버스와 표준 JBoss 콤포넌트

그림 2.1. JBoss JMX 통합 버스와 표준 JBoss 콤포넌트

2.1.1. JMX 소개

JMX가 JBoss의 컴포넌트 버스로 어떻게 사용되는지를 살펴보기 전에 JMX의 핵심 사항들을 개략적으로 살펴보는 것이 많은 도움이 될 것입니다.

JMX 컴포넌트는 Java Management Extension의 사용과 JSR003 웹 페이지인 http://jcp.org/aboutJava/communityprocess/final/jsr003/ 에 기술되어 있는 에이전트 사양 v1.0에 정의되어 있습니다. 이번 JMX 소개에서 다루어지는 것들은 JMX 사용 스펙중 JBoss에서 주로 사용되는 것들을 위주로 되어있습니다. JMX와 이를 이용한 어플리케이션의 심도있는 논의는 uha Lindfors씨가 쓴 책(Sams, 0672322889, 2002), JMX: Managing J2EE with Java Management Extensions 을 참고하십시오.

JMX는 자바에서 모든 가용한 소프트웨어와 하드웨어 컴포넌트들을 관리하고 모니터링할 때 필요한 표준을 제공하고 있습니다. 또한 기존에 나와있는 수 많은 표준 관리 콤포넌트들과의 통합을 제공하는 것을 목표로 하고 있습니다. 그림 2.2, “JMX 아키텍쳐의 콤포넌트들사이의 관계”에서는 JMX 환경에서 찾을 수 있는 콤포넌트들의 예를 보여주고 있으며, 그들간의 관계는 물론 JMX의 3개의 레벨 모델에 어떻게 연관되어 있는지도 보여주고 있습니다. 여기서 3개의 레벨은 다음과 같습니다:

JMX 아키텍쳐의 콤포넌트들사이의 관계

그림 2.2. JMX 아키텍쳐의 콤포넌트들사이의 관계

2.1.1.1. Instrumentation 레벨

instrumentation 레벨은 JMX 리소스 관리가 가능하는데 필요한 것들을 정의합니다. 관리가 가능한 JMX 리소스에는 어플리케이션, 서비스 콤퍼넌트, 디바이스등을 포함한 가상의 어떠한 것들도 될 수 있습니다. 관리가능한 리소스는 자바 객체나 그 리소스의 관리가능한 기능을 기술한 랩퍼를 노출시켜 해당 리소스의 관리 수행이 가능하도록 함으로써 JMX-호환 어플리케이션에서 관리되어질 수 있는 것입니다.

사용자는 하나 이상의 관리되는 빈즈 혹은 MBeans을 사용하여 주어진 리소스의 관리가 가능하게 됩니다. MBean 수행에는 4개의 종류가 있습니다: standard, dynamic, model, 그리고 open. 다양한 MBean 타입의 차이점에 대해서는 관리되는 빈즈 또는 MBeans 섹션에서 다루어지고 있습니다.

또한 instrumentation 레벨에서는 통지 메커니즘까지도 지정합니다. 통지 메커니즘의 목적은 MBeans로 하여금 각자의 환경의 변경에 대한 통신이 가능하도록 하는 것입니다. 이는 JavaBean 속성 변경 통지 메커니즘과 유사하며 속성 변경 통지, 상태 변경 통지등과 같은 곳에 사용될 수 있습니다.

2.1.1.2. Agent 레벨

에이전트 레벨에서는 에이전트 실행에 필요한 것들을 정의하고 있습니다. 에이전트들은 에이전트가 등록한 관리되는 리소스들의 제어와 노출에 대한 책임이 있습니다. 기본적으로 관리 에이전트들은 리소스들이 위치한 곳과 동일한 호스트에 함께 위치합니다. 이 배치는 필요사항은 아닙니다.

에이전트의 필요조건들은 instrumentation 레벨에서 표준 MBeanServer 관리 에이전트, 서비스 지원 및 커뮤니케이션 커넥터의 정의를 할 수 있도록 합니다. JBoss에서는 html 아답터뿐아니라 RMI 아답터까지도 지원합니다.

JMX 에이전트는 Java Virtual Machine(JVM)이 사용가능한 관리가능한 JMX 리소스들이 호스팅된 하드웨어에 위치할 수 있습니다. 이것은 JBoss 서버가 MBeanServer를 어떻게 사용하는지를 알려주는 부분입니다. JMX 에이전트는 어디서 리소스를 서비스해주는지 알필요가 없습니다. 관리가능한 JMX 리소스들은 필요한 서비스를 제공해주는 JMX 에이전트라면 어떤것이든 사용할 수 있습니다.

관리자는 다음 절에서 논의되는 2.1.1.3 절, “분산된 서비스 레벨”에 기술되어있는 프로토콜 아답터나 커넥터를 통해 에이전트의 MBeans와 상호작용합니다. 에이전트는 자신과 자신의 MBeans사이에서 서로 상호작용되는 커넥터나 관리 어플리케이션에 대해 알지못하더라도 동작할 수 있습니다.

2.1.1.3. 분산된 서비스 레벨

JMX 스펙에서 분산된 서비스 레벨의 완전한 정의는 JMX 스펙의 초기 버전에서 다룰 수 있는 범위를 벗어난다는 것에 주의하십시오. 이는 그림 2.2, “JMX 아키텍쳐의 콤포넌트들사이의 관계”에서 보여지는 컴포넌트 상자들의 수평선으로 표시되어 있습니다. 이 레벨의 주된 목적은 JMX 관리 어플리케이션이나 관리자의 실행에 필요한 인터페이스들을 정의하는 것입니다. 아래에 제시되는 항목들은 현 JMX 스펙에서 논의되는 분산된 서비스 레벨의 기능들입니다.

위에서 제시되는 기능들은 분산된 서비스 레벨의 컴포넌트들이 에이전트와 자신들의 리소스들사이의 네트워크 관리의 상호협력이 가능하도록 하는 것이 목적입니다. 이 컴포넌트들은 완전한 관리 어플리케이션을 제공하는데까지 확장되어질 수 있습니다.

2.1.1.4. JMX 콤포넌트 개요

이번 절에서는 instrumentation과 에이전트 레벨의 컴포넌트들에 대한 소개를 하도록 하겠습니다. instrumentation 레벨의 컴포넌트들는 다음과 같습니다:

에이전트 레벨의 컴포넌트들는 다음과 같습니다:

2.1.1.4.1. 관리되는 빈즈(Beans) 또는 MBeans

MBean은 표준 MBean 인터페이스들중 하나를 수행하며 관련된 디자인 패턴을 따르는 자바 객체입니다. 리소스를 위한 MBean에서는 필요한 모든 정보와 관리 어플리케이션이 리소스를 제어하는데 필요로 하는 기능들을 노출합니다.

MBean의 관리 인터페이스의 범위내에는 다음과 같은 것들이 포함됩니다:

JMX에서는 4개의 MBeans 타입을 통해 서로 다른 instrumentation 레벨의 요구사항을 지원합니다:

여러분이 직접 만든 서비스를 JBoss에서 확장하는 방법을 논의하는 섹션에서 Standard 와 Model MBean의 예를 보여줄 것입니다.

2.1.1.4.2. Notification 모델

JMX 통지는 자바 이벤트 모델의 확장입니다. MBean 서버와 MBeans 모두 정보를 제공하기위해 통지(notifications)를 할 수 있습니다. JMX 스펙에는 javax.management 패키지에 Notification 이벤트 객체, NotificationBroadcaster 이벤트 전송자 및 NotificationListener 이벤트 수신자 인터페이스가 정의되어 있습니다. 스펙에서는 통지 수신자의 등록이 가능하도록 하는 MBean 서버의 오퍼레이션들도 정의되어 있습니다.

2.1.1.4.3. MBean 메터데이타 클래스

MBean의 관리 인터페이스를 설명해주는 메터데이터 클래스의 컬렉션이 존재합니다. 사용자는 MBeans가 등록된 MBean 서버에 질의를 날려 4개의 MBean 타입중 어느것에 대해서라도 일반 메터데이타 뷰를 가져올 수 있습니다. 메터데이타 클래스는 MBean의 속성, 오퍼레이션, 통지 및 생성자 모두를 다루고 있습니다. 이들 각각에 대해서는 메터데이터에 이름과 설명 그리고 특정 기능들이 포함되게 됩니다. 가령 속성의 어느 한 특성이 읽기가능/쓰기가능 또는 이 두개 모두 가능하다고 할 때, 해당 오퍼레이션의 메터데이터에는 그 매개변수의 사용방법과 리턴 타입을 갖게 됩니다.

MBeans의 다양한 타입은 메터데이터 클래스에 필요한 추가 정보를 제공해줄 수 있는 수단으로 확장됩니다. 이러한 일반적인 상속을 통해 MBean의 타입에 구애받지 않으면서 표준 정보를 구현할 수 있습니다. 이러한 방식을 통해 관리 어플리케이션은 특정 MBean 타입의 확장 정보에 어떻게 액세스할지를 알수있게 됩니다.

2.1.1.4.4. MBean 서버

에이전트 레벨의 핵심 컴포넌트는 관리되는 빈 서버입니다. 그 기능성은 javax.management.MBeanServer의 인스턴스를 통해 노출됩니다. MBean 서버는 관리 어플리케이션에서 사용되는 가용한 MBean 관리 인터페이스를 만들어주는 MBeans 레지스트리입니다. MBean은 결코 자신이 직접 MBrean 객체를 노출하지 않고, MBean 서버 인터페이스내의 가용한 메터데이터와 오퍼레이션을 통해 노출시킵니다. 이를 통해 관리 어플리케이션과 여기서 관리되는 MBeans 사이에는 느슨한 커플링이 가능하게 됩니다.

MBeans는 다음과 같은 것들로 MBeanServer에 최기화되고 등록되어질 수 있습니다:

여러분이 MBean을 등록하게 될때, 반드시 고유한 객체 이름을 지정해야만 합니다. 이렇게 등록한 객체의 이름은 관리 오퍼레이션을 수행하는 객체를 관리 어플리케이션에서 고유하게 식별할 수 있도록 합니다. MBean 서버를 통해 MBeans에서 사용가능한 오퍼레이션에는 다음과 같은 것들이 있습니다:

프로토콜 아답터와 커넥터는 에이전트의 JVM 외부로부터 MBeanServer에 액세스하는데 필요합니다. 각각의 아답터들은 자신과 연결되어 있는 MBean 서버에 등록된 모든 MBeans들을 자신의 프로토콜을 통해 뷰로 제공합니다. HTML 아답터는 그중 대표적인 아답터로 이를 통해 웹 브라우저에서 MBeans를 점검하고 수정할 수 있도록 합니다. 그림 2.2, “JMX 아키텍쳐의 콤포넌트들사이의 관계”에서 보여주는 것처럼, 현재의 JMX 스펙에는 어떠한 프로토콜 아답터도 없습니다. 차기 스펙에서는 표준 방법내에서 원격 액세스를 위한 프로토콜의 필요성이 제기되어 있습니다.

커넥터는 커뮤니케이션 프로토콜에 비종속적인 방법으로 MBean 서버에 액세스할 수 있는 범용 API를 제공함으로써 관리 어플리케이션에서 사용되어지는 인터페이스를 말합니다. 각 커넥터의 타입은 서로 다른 프로토콜에 대해서 동일한 원격 인터페이스를 제공합니다. 이를 통해 원격 관리 어플리케이션은 어떤 프로토콜을 사용하는지에 상관없이 네트워크를 통해 투명하게 에이전트와 연결됩니다. 원격 관리 인터페이스의 스펙은 JMX 스펙의 차기버전에서 다루어지게 될 것입니다.

아답터와 커넥터는 모든 가용한 MBean 서버 오퍼레이션을 원격 관리 어플리케이션을 통해 다룰 수 있도록 합니다. 자신의 JVM 외부에서 관리가 가능한 에이전트를 위해 최소한 하나 이상의 프로토콜 아답터나 커넥터를 가져야만 합니다. 현재 JBoss에는 커스텀 HTML 아답터가 적용되어 있으며 커스텀 JBoss RMI 아답터가 포함되어 있습니다.

2.1.1.4.5. 에이전트 서비스

JMX 에이전트 서비스는 MBean 서버에 등록된 MBeans의 표준 오퍼레이션을 지원할 수 있는 객체들입니다. 관리 서비스를 지원하도록 함으로써 여러분이 보다 강력한 관리 솔류션을 만드는 데 따르는 도움을 줄 수 있게 되어있습니다. 에이전트 서비스는 종종 에이전트와 그 기능들로 하여금 MBean 서버를 통해 제어될 수 있는 MBeans 그 자체일 때도 있습니다. JMX 스펙에서는 다음과 같은 에이전트 서비스들이 정의되어 있습니다:

JMX와 호환되는 것들에 대해서는 모든 에이전트 서비스가 제공되게 됩니다. 그러치만 JBoss는 이 표준 에이전트 서비스들에 의존하지는 않습니다.

2.2. JBoss JMX 수행 아키텍쳐

2.2.1. JBoss ClassLoader 아키텍쳐

JBoss 3.x 에서는 배치된 유닛들과 서비스와 어플리케이션의 핫 디플로이먼트 전반에 걸쳐 클래스의 공유 기능을 구현하는 클래스 로딩 아키텍쳐가 도입되었습니다. JBoss에 특화된 클래스 로딩 모델을 논의하기전, 우리는 자바의 타입 시스템의 성격에 대한 이해와 여기에 클래스를 어떻게 로딩시키는지를 이해할 필요가 있습니다.

2.2.2. 자바에서의 클래스 로딩과 타입

클래스 로딩은 모든 서버 아키텍쳐의 중요한 부분입니다. 임의의 서비스와 이를 지원하는 클래스들은 서버 프레임워크에 로딩되어져야만 합니다. 이는 자바가 형식에 엄격하기 때문에 그렇습니다. 대부분의 개발자들이 알고 있는것 처럼 자바에서의 클래스 타입은 클래스의 완전한 형태의 이름을 갖는 함수입니다. 자바 1.2에 이르러서 타입에는 클래스를 정의하는데 사용되는 java.lang.ClassLoader 함수까지 포함되었습니다. 이렇게 추가된 타입의 능력은 임의의 위치에서 로딩되어진 클래스의 환경이 안전한 타입이 될 수 있도록 보장합니다. 1997년 Vijay Saraswat씨가 쓴 Java is not type-safe 이라는 논문에서는 자바가 고안했던 바대로 안전한 형식을 보장하지 않는다는 것을 보여주었습니다. 이전에 로딩되어진 클래스에 대체수행을 이용하여 자바 VM을 풀링함으로써 액세스되어서는 않되는 클래스의 메쏘드와 멤버에 액세스될 수 있다는 것을 의미합니다. 이러한 타입 시스템의 문제점때문에 일반적인 위임(delegation) 모델을 바이패스하는 클래스 로더의 개념이 탄생되었습니다. 클래스 로더는 클래스와 리소스들을 검색하는데 위임 모델을 사용합니다. 각각의 ClassLoader 인스턴스는 그것이 생성될때 명시적으로 설정되어졌거나 명시하지는 않았으나 VM에 의해 할당되어진 부모 클래스 로더와 관련되어 있습니다. 일반적으로 클래스 로더가 클래스를 찾기위해 호출되어진 경우, 클래스 로더는 자체적으로 클래스나 리소스를 검색하려는 시도를 하기 이전에 클래스 검색을 자신의 부모 클래스 로더쪽으로 위임하게 됩니다. bootstrap 클래스 로더를 호출하는 VM은 루트 클래스 로더로써 부모 클래스 로더는 갖지 않지만 ClassLoader 인스턴스의 부모로써 서비스하게 됩니다.

타입-안전성 문제를 다루기 위해서 타입 시스템은 클래스의 이름에 타입의 완전한 정의가 추가된 ClassLoader 정의를 갖는 클래스를 포함시켜 강화되었습니다. 이러한 문제의 해결을 다룬 첫 번째 논문은 Sheng Lian씨와 Gilad Bracha씨가 쓴 Dynamic Class Loading in the Java Virtual Machine이며, 이 논문은 http://java.sun.com/people/sl/papers/oopsla98.ps.gz에서 얻을 수 있습니다. 어플리케이션 서버와 같이 동적인 환경내에서의 이러한 변화에 따른 결과로써, 특히나 핫 디플로이먼트를 지원하고 있는 JBoss의 경우에서는 클래스의 강제적 캐스트, 연결 오류 및 올바르지 않은 액세스에 따른 오류가 정적인 클래스 로딩을 통한 컨텍스트의 경우보다 훨씬 빈번하게 보여지게 됩니다. 이제 각각의 예외상황과 이에 대한 상황설명에 대해 살펴보도록 하겠습니다.

2.2.2.1. ClassCastExceptions - 타입이 맞지 않을 때

호환되지 않는 타입의 인스턴스를 캐스트하려고 할때마다 java.lang.ClassCastException 결과가 나타납니다. 간단한 예제로 List로부터 String을 얻어 URL에 넣어주는 것을 들 수 있습니다:

ArrayList array = new ArrayList();
array.add(new URL("file:/tmp"));
String url = (String) array.get(0);

java.lang.ClassCastException: java.net.URL
at org.jboss.chap2.ex0.ExCCEa.main(Ex1CCE.java:16)

ClassCastException은 배열의 요소를 String으로 캐스트하는 과정에서 실제 타입이 URL이기 때문에 실패했다는 것을 여러분에게 알려주고 있습니다. 이처럼 매우 간단한 경우가 우리의 관심 대상이 될 수는 없으나 각기 다른 클래스 로더에 의해 로드되어진 JAR의 경우는 관심의 대상이 되어질 것입니다. 비록 각각의 클래스 로더를 통해 로딩된 클래스는 바이트코드로는 서로 동일하더라도 자바 타입 시스템에서의 뷰로는 서로 다른 타입이 됩니다. 이러한 상황의 예는 예제 2.1, “클래스 로더의 중복으로 발생되는 ClassCastException 구현예로 사용되는 ExCCEc”에 보여지고 있습니다.

예제 2.1. 클래스 로더의 중복으로 발생되는 ClassCastException 구현예로 사용되는 ExCCEc

package org.jboss.chap2.ex0;

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.lang.reflect.Method;

import org.apache.log4j.Logger;

import org.jboss.util.ChapterExRepository;
import org.jboss.util.Debug;

/**
 * 서로 다른 클래스 로더를 통해 로드된
 * 클래스로부터 ClassCastException이 
 * 발생되는 예제.
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.11 $
 */
public class ExCCEc
{
    public static void main(String[] args) throws Exception
    {
        ChapterExRepository.init(ExCCEc.class);

        String chapDir = System.getProperty("chapter.dir");
        Logger ucl0Log = Logger.getLogger("UCL0");
        File jar0 = new File(chapDir+"/j0.jar");
        ucl0Log.info("jar0 path: "+jar0.toString());
        URL[] cp0 = {jar0.toURL()};
        URLClassLoader ucl0 = new URLClassLoader(cp0);
        Thread.currentThread().setContextClassLoader(ucl0);
        Class objClass = ucl0.loadClass("org.jboss.chap2.ex0.ExObj");
        StringBuffer buffer = new StringBuffer("ExObj Info");
        Debug.displayClassInfo(objClass, buffer, false);
        ucl0Log.info(buffer.toString());
        Object value = objClass.newInstance();
        
        File jar1 = new File(chapDir+"/j0.jar");
        Logger ucl1Log = Logger.getLogger("UCL1");
        ucl1Log.info("jar1 path: "+jar1.toString());
        URL[] cp1 = {jar1.toURL()};
        URLClassLoader ucl1 = new URLClassLoader(cp1);
        Thread.currentThread().setContextClassLoader(ucl1);
        Class ctxClass2 = ucl1.loadClass("org.jboss.chap2.ex0.ExCtx");
        buffer.setLength(0);
        buffer.append("ExCtx Info");
        Debug.displayClassInfo(ctxClass2, buffer, false);
        ucl1Log.info(buffer.toString());
        Object ctx2 = ctxClass2.newInstance();
        
        try {
            Class[] types = {Object.class};
            Method useValue = ctxClass2.getMethod("useValue", types);
            Object[] margs = {value};
            useValue.invoke(ctx2, margs);
        } catch(Exception e) {
            ucl1Log.error("Failed to invoke ExCtx.useValue", e);
            throw e;
        }
    }
}

예제 2.2. 예제에서 사용하는 ExCtx, ExObj, 및 ExObj2 클래스

package org.jboss.chap2.ex0;

import java.io.IOException;
import org.apache.log4j.Logger;
import org.jboss.util.Debug;

/**
 * 다양한 클래스에서 로딩될때 발생될 수 있는
 * 문제들을 보여주는 클래스
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.11 $
 */
public class ExCtx
{
    ExObj value;
    
    public ExCtx() 
        throws IOException
    {
        value = new ExObj();
        Logger log = Logger.getLogger(ExCtx.class);
        StringBuffer buffer = new StringBuffer("ctor.ExObj");
        Debug.displayClassInfo(value.getClass(), buffer, false);
        log.info(buffer.toString());
        ExObj2 obj2 = value.ivar;
        buffer.setLength(0);
        buffer = new StringBuffer("ctor.ExObj.ivar");
        Debug.displayClassInfo(obj2.getClass(), buffer, false);
        log.info(buffer.toString());
    }

    public Object getValue()
    {
        return value;
    }

    public void useValue(Object obj) 
        throws Exception
    {
        Logger log = Logger.getLogger(ExCtx.class);
        StringBuffer buffer = new StringBuffer("useValue2.arg class");
        Debug.displayClassInfo(obj.getClass(), buffer, false);
        log.info(buffer.toString());
        buffer.setLength(0);
        buffer.append("useValue2.ExObj class");
        Debug.displayClassInfo(ExObj.class, buffer, false);
        log.info(buffer.toString());
        ExObj ex = (ExObj) obj;
    }

    void pkgUseValue(Object obj) 
        throws Exception
    {
        Logger log = Logger.getLogger(ExCtx.class);
        log.info("In pkgUseValue");
    }
}

예제 2.3. 예제에서 사용되는 ExObj 와 ExObj2 클래스

package org.jboss.chap2.ex0;

import java.io.Serializable;

/**
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.11 $
 */
public class ExObj
    implements Serializable
{
    public ExObj2 ivar = new ExObj2();
}


--------------------------------------------------------
package org.jboss.chap2.ex0;

import java.io.Serializable;

/**
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.11 $
 */
public class ExObj2 
    implements Serializable
{
}

ExCCEc.main 메쏘드는 어플리케이션 클래스 로더로부터 ucl0ucl1클래스 로더에 의해 로딩되어진 클래스들을 분리시키기 위해 리플렉션(reflection)을 사용합니다. 두 클래스 모두 아래와 같은 컨텐츠를 갖는 output/chap2/j0.jar로부터 클래스를 로드시켜 설정되어 집니다:

[nr@toki examples]$ jar -tf output/chap2/j0.jar

org/jboss/chap2/ex0/ExCtx.class
org/jboss/chap2/ex0/ExObj.class
org/jboss/chap2/ex0/ExObj2.class

클래스의 캐스트 예외발생이 어떻게 발생하는지를 구현예를 통해 실행해보고 이때 발생된 문제를 살펴볼 것 입니다. 이 책에서 사용하는 예제들을 설치하는 방법은 부록 B, 예제 설치를 참조하십시오. 예제의 실행은 예제가 들어있는 디렉터리에서 다음의 명령을 사용하시면 됩니다:

[nr@toki examples]$ ant -Dchap=chap2 -Dex=0c run-example
...
     [java] [ERROR,UCL1] Failed to invoke ExCtx.useValue
     [java] java.lang.reflect.InvocationTargetException
     [java] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
     [java] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
     [java] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
     [java] at java.lang.reflect.Method.invoke(Method.java:324)
     [java] at org.jboss.chap2.ex0.ExCCEc.main(ExCCEc.java:58)
     [java] Caused by: java.lang.ClassCastException
     [java] at org.jboss.chap2.ex0.ExCtx.useValue(ExCtx.java:44)
     [java] ... 5 more

여기에서는 예외발생에 대한 것만 보여지고 있습니다. 출력내용은 logs/chap2-ex0c.log 파일에 모두 들어가게 됩니다. ExCCEc.java의 55번째 줄에서 로드된 인스턴스에서 ExcCCECtx.useValue(Object)를 호출하고 있으며, ucl1을 사용하여 37-48 라인에서 생성됩니다. 넘겨받은 ExObjucl0를 통해 라인 25-35에서 로드되고 만들어진 것입니다. ExCtx.useValue 코드가 ExObj에 넘겨받은 인수를 캐스트시키려고 할 때 예외가 발생됩니다. 예외가 발생되는 이유를 알기위해서는 예제 2.4, “chap2-ex0c.log - ExObj 클래스에서 나오는 디버깅 정보”chap2-ex0c.log 파일에서 관련된 디버깅 출력정보를 살펴보아야 합니다.

예제 2.4. chap2-ex0c.log - ExObj 클래스에서 나오는 디버깅 정보

[INFO,UCL0] ExObj Info
org.jboss.chap2.ex0.ExObj(113fe2).ClassLoader=java.net.URLClassLoader@6e3914
..java.net.URLClassLoader@6e3914
....file:/C:/Scott/JBoss/Books/AdminDevel/education/books/admin-devel/examples/output/
  chap2/j0.jar
++++CodeSource:
    (file:/C:/Scott/JBoss/Books/AdminDevel/education/books/admin-devel/examples/output/
  chap2/j0.jar <no certificates>)
Implemented Interfaces:
++interface java.io.Serializable(7934ad)
++++ClassLoader: null
++++Null CodeSource
 
[INFO,ExCtx] useValue2.ExObj class
org.jboss.chap2.ex0.ExObj(415de6).ClassLoader=java.net.URLClassLoader@30e280
..java.net.URLClassLoader@30e280
....file:/C:/Scott/JBoss/Books/AdminDevel/education/books/admin-devel/examples/output/
  chap2/j0.jar
++++CodeSource:
    (file:/C:/Scott/JBoss/Books/AdminDevel/education/books/admin-devel/examples/output/
  chap2/j0.jar <no certificates>)
Implemented Interfaces:
++interface java.io.Serializable(7934ad)
++++ClassLoader: null
++++Null CodeSource

[INFO,UCL0]로 시작되는 첫 번째 출력내용으로부터 ExCCEc.java:31번째 라인에서 로드된 ExObj 클래스가 113fe2 해쉬코드를 갖으며, ucl0와 대응되는 6e3914 해쉬코드를 갖는 URLClassLoader 인스턴스에 관련되어 있다는 것을 알 수 있습니다. 이것은 ExCtx.useValue 메쏘드에 넘겨지는 인스턴스를 만드는데 사용된 클래스입니다. [INFO,ExCtx]로 시작하는 두 번째 출력에서는 ExCtx.useValue 메쏘드의 컨텍스트에서 보여지던 ExObj 클래스가 415de6 해쉬코드를 갖으며 ucl1과 대응되는 30e280 해쉬코드와 관련된 URLClassLoader 인스턴스를 갖는 다는 것을 알 수 있습니다. ExObj 클래스가 동일한 j0.jar로부터 만들어지기 때문에 실제 바이트코드는 같지만, 위에서 본 것처럼 해쉬 코드와 관련된 URLClassLoader 인스턴스가 모두 다르기 때문에 서로 다른 경우에 대한 ExObj 인스턴스의 캐스트는 ClassCastException 으로 나타나는 것입니다.

이러한 형태의 오류는 재배치된 어플리케이션에서 다른 어플리케이션이 클래스의 참조를 계속 갖고 있을 때 그 어플리케이션을 재배치시키려고 하면 흔히 발생합니다. 즉, 표준 WAR에 EJB가 들어있는 경우를 생각해보도록 하겠습니다. 이제 여러분이 어플리케이션을 재배치시키려고 할 때, 모든 종속된 어플리케이션들은 반드시 자신들의 클래스 참조를 깨끗이 비워야만 합니다. 일반적으로 이러한 과정은 종속적인 어플리케이션 자체적으로 재비채되어질 때 필요한 사항입니다.

재배치해야 되는 상황에서 독립적인 배치가 가능하도록 하는 또 다른 방법으로는 참조에 의한 호출(call-by-reference)보다는 표준 값에 의한 호출(call-by-value) 방법을 사용하여 EJB 레이어를 설정해 배치의 독립성을 확보함으로써 JBoss가 동일한 VM내의 컴포넌트들을 통합시키도록 하게 하는 것입니다. 값에 의한 호출 방법은 5 장, JBoss에서의 EJBs에서 그 사용예를 찾아 볼 수 있습니다.

2.2.2.2. IllegalAccessException - 하지말아야 하는 것을 했을때

사용이 가능하지 않은 메쏘드나 멤버를 액세스하려고 할 때 java.lang.IllegalAccessException이 발생합니다. 전형적인 예로는 private 혹은 protected 메쏘드나 인스턴스 변수들에 액세스하려는 시도입니다. 또 다른 일반적인 예로는 클래스에서 protected된 패키지의 메쏘드나 멤버를 올바른 패키지에서 액세스는 하였으나 다른 클래스 로더에 의해 로딩된 클래스를 호출하거나 호출받기 때문에 발생합니다. 예제 2.6, “제약사항의 로드가 필요한 클래스의 구현예” 에서는 이러한 상황을 보여주는 코드가 제시되어 있습니다.

예제 2.5. 클래스 로더가 중복되어 IllegalAccessException이 발생되는 것을 보여주기 위해 사용된 ExIAEd 클래스

package org.jboss.chap2.ex0;

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.lang.reflect.Method;

import org.apache.log4j.Logger;

import org.jboss.util.ChapterExRepository;
import org.jboss.util.Debug;

/**
 * 2개의 클래스 로더에 의해 로드된 클래스 때문에 
 * IllegalAccessExceptions이 발생되는 예제
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.11 $
 */
public class ExIAEd
{
    public static void main(String[] args) throws Exception
    {
	ChapterExRepository.init(ExIAEd.class);

	String chapDir = System.getProperty("chapter.dir");
	Logger ucl0Log = Logger.getLogger("UCL0");
	File jar0 = new File(chapDir+"/j0.jar");
	ucl0Log.info("jar0 path: "+jar0.toString());
	URL[] cp0 = {jar0.toURL()};
	URLClassLoader ucl0 = new URLClassLoader(cp0);
	Thread.currentThread().setContextClassLoader(ucl0);
	
	StringBuffer buffer = new StringBuffer("ExIAEd Info");
	Debug.displayClassInfo(ExIAEd.class, buffer, false);
	ucl0Log.info(buffer.toString());
	
	Class ctxClass1 = ucl0.loadClass("org.jboss.chap2.ex0.ExCtx");
	buffer.setLength(0);
	buffer.append("ExCtx Info");
	Debug.displayClassInfo(ctxClass1, buffer, false);
	ucl0Log.info(buffer.toString());
	Object ctx0 = ctxClass1.newInstance();

	try {
	    Class[] types = {Object.class};
	    Method useValue =
		ctxClass1.getDeclaredMethod("pkgUseValue", types);
	    Object[] margs = {null};
	    useValue.invoke(ctx0, margs);
	} catch(Exception e) {
	    ucl0Log.error("Failed to invoke ExCtx.pkgUseValue", e);
	}
    }
}

ExIAEd.main 메쏘드는 ExIEAd 클래스가 어플리케이션의 클래스 로더에 의해 로드되어진 반면 ucl0 클래스 로드를 통해 ExCtx 클래스를 로딩하도록 리플렉션(reflection)을 사용합니다. 우리는 IllegalAccessException이 어떻게 발생하고 이 경우 발생되는 메시지들을 살펴보기 위해 이번 예제를 실행해보록 하겠습니다. 다음의 명령을 통해 예제를 실행시킬 수 있습니다:

[orb@toki examples]$ ant -Dchap=chap2 -Dex=0d run-example
Buildfile: build.xml
...
[java] [ERROR,UCL0] Failed to invoke ExCtx.pkgUseValue
[java] java.lang.IllegalAccessException: Class org.jboss.chap2.ex0.ExIAEd 
  can not access a member of class org.jboss.chap2.ex0.ExCtx with modifiers ""
[java] at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:57)
[java] at java.lang.reflect.Method.invoke(Method.java:317)
[java] at org.jboss.chap2.ex0.ExIAEd.main(ExIAEd.java:48)

여기서 보여주는 결과는 일부분만을 추출한 것으로, 이를 통해 우리는 IllegalAccessException이 발생하였음을 알 수 있습니다. 출력되는 결과는 모두 logs/chap2-ex0d.log 파일에 저장됩니다. ExIAEd.java의 48번째 라인에서 ExCtx.pkgUseValue(Object) 메쏘드가 리플렉션을 통해 호출되어졌습니다. pkgUseValue 메쏘드는 protected된 패키지 액세스를 갖으며 그들의 메쏘드가 org.jboss.chap2.ex0 패키지내에 위치된 것을 호출하는 ExIAEdExCtx 클래스를 호출한다 하더라도 서로 다른 클래스 로더들에 의해 로드되어진 2개의 클래스라는 사실때문에 호출이 올바르지 않다고 나타나게 되는 것입니다. chap2-ex0d.log file 파일에 저장된 디버깅 정보에서 이것을 확인할 수 있습니다.

[INFO,UCL0] ExIAEd Info
org.jboss.chap2.ex0.ExIAEd(65855a).ClassLoader=sun.misc.Launcher$AppClassLoader@3f52a5
..sun.misc.Launcher$AppClassLoader@3f52a5
...
[INFO,UCL0] ExCtx Info
org.jboss.chap2.ex0.ExCtx(70eed6).ClassLoader=java.net.URLClassLoader@113fe2
..java.net.URLClassLoader@113fe2
...

ExIAEd 클래스는 기본 어플리케이션 클래스 로더 인스턴스인 sun.misc.Launcher$AppClassLoader@3f52a5를 통해 로딩되어진 것을 볼 수 있으며, 반면 ExCtx 클래스는 java.net.URLClassLoader@113fe2 인스턴스에 의해 로드되어집니다. 서로 다른 클래스 로더에 의해 클래스가 로딩되어지기 때문에 protected된 패지키 매쏘드로의 액세스에는 보안위배가 발생됩니다. 결국 클래스 이름과 클래스 로더의 완전한 이름을 적어주는 것 뿐만 아니라 패지키의 스코프(scope)도 마찬가지로 완전한 경로까지 타이핑해야 합니다.

실제 상황에서 이런 경우가 발생하는 예는 2개의 서로다른 SAR 도큐먼트내에 동일한 클래스가 포함되어 있는 경우가 됩니다. 배치되는 곳에 있는 클래스가 protected 관계를 갖는 패지키를 포함하고 있을 때, SAR 서버스의 사용자들은 첫 번째에 SAR 클래스를 로드한 클래스를 끝내야만 다음번 SAR로 부터 또 다른 클래스를 로드시킬 수 있습니다. 2개의 클래스가 교차되는 상황에서는 protected 액세스 관계때문에 IllegalAccessError가 발생합니다. 이에 대한 해결 방안은 SAR에 의해 참조되는 별도의 jar내에 클래스를 포함시키거나 SAR를 통합하여 단일 배치시켜야 합니다. 즉, 하나의 SAR로 만들던가 아니면 두 개의 SAR를 포함하는 하나의 EAR로 만들어 주시면 됩니다.

2.2.2.3. LinkageErrors - 여러분이 누군인지, 누가 여러분을 말해주는지 확실하게 해야 합니다

초기 자바 VM의 타입-안전성 문제를 해결하기 위해서 제약조건의 로드를 위한 표시가 자바 1.2 스펙에 추가되었습니다. 제약조건을 로딩하여 클래스 로더 스코프의 컨텍스트내에서 타입 검사가 가능하도록 하여 여러 클래스 로더가 포함된 경우에 클래스 X가 항상 동일한 클래스일 수 있도록 하게 됩니다. Linkage 오류는 클래스가 로드되고 사용될 때 VM에의해 강제적으로 클래스 캐스트 될 때 발생되는 예외 상황의 확장에 필연적입니다.

제약조건을 로딩하는 것에 대한 이해와 타입-안전성을 어떻게 확보하는지를 알아보기 위해 본 문서와 함께 제공되는 예제에 포함된 Liang씨와 Bracha씨의 논문에 소개된 전문적인 용어를 먼저 소개할 것입니다. 클래스 로더에는 2가지 형태, initiating 과 defining이 있습니다. initiating 클래스 로더는 명칭을 갖는 클래스의 로딩을 초기화시키기 위해 호출되는 ClassLoader.loadClass 메쏘드입니다. defining 클래스 로더는 클래스 바이트 코드를 Class 인스턴스로 변환시키기 위한 ClassLoader.defineClass 메쏘드들중에 하나를 호출하는 로더입니다. 클래스의 완전한 표현은 <C,Ld>Li 이며, 여기서 C는 클래스 이름의 완전한 경로이며, Ld는 defining 클래스 로더가 되고 Li는 initiating 클래스 로더를 의미합니다. initiating 클래스 로더가 중요하지 않은 경우에는 <C,Ld>와 같이 표시되기도 하며, 이와 반대로 defining 클래스 로더가 중요하지 않은 경우에는 CLi 로 표시할 수도 있습니다. 후자의 경우, 여전히 defining 클래스 로더는 존재하며 단지 defining 클래스 로더의 식별성(identity)이 중요하지 않다는 것 뿐입니다. 또한 타입은 <C,Ld>로 완벽하게 정의됩니다. 로딩되는 제약조건이 올바를때만 initiating 로더도 적절하게 됩니다. 이제 예제 2.6, “제약사항의 로드가 필요한 클래스의 구현예”에서 보여주는 클래스에 대해 생각해보도록 하겠습니다.

예제 2.6. 제약사항의 로드가 필요한 클래스의 구현예

class <C,L1> {
    void f() {
        <Spoofed, L1>L1x = <Delegated, L2>L2
        x.secret_value = 1; // Should not be allowed
    }
}
class <Delegated,L2> {
    static <Spoofed, L2>L3 g() {...}
    }
}
class <Spoofed, L1> {
    public int secret_value;
}
class <Spoofed, L2> {
    private int secret_value;
}

C 클래스는 L1으로 정의되고, 따라서 C.f() 메쏘드에서 참조되는 SpoofedDelegated 클래스의 초기화 로딩에 L1이 사용됩니다. Spoofed 클래스는 L1에 의해 정의되지만, Delegated 클래스는 L2로 정의되는데 그 이유는 L1L2로 위임되기 때문입니다. DelegatedL2로 정의되기 때문에 L2Delegated.g() 메쏘드에서 Spoofed의 로딩 초기화에 사용되게 됩니다. 예제에서는 L1L2 모두 예제 2.6, “제약사항의 로드가 필요한 클래스의 구현예”의 마지막 부분에서 볼 수 있는 2개의 버전에서 가리키는 서로 다른 버전의 Spoofed를 정의합니다. C.f()x<Spoofed,L1>의 인스턴스로 믿기때문에, Delegated.g()로부터 리턴받은 <Spoofed,L2>의 private 필드 secret_value에 자바 VM 1.1 이전 버전에서 발생되는 오류를 고려하여 클래스의 타입이 완전한 경로로 타이핑되는 클래스 이름과 클래스 로더의 정의를 통해 클래스 타입을 결정짓도록 하여 액세스가 가능해집니다.

자바 1.2 이상에서는 사용되는 타입들이 서로 다른 defining 클래스 로더들로부터 나오는 경우 타입 일치성을 점검하기 위해 로더의 제약조건을 생성시켜 이 문제를 해결합니다. 예제 2.6, “제약사항의 로드가 필요한 클래스의 구현예”의 예제에서, VM은 Spoofed 타입이 L1 혹은 L2를 사용하여 초기화시킨 Spoofed를 로드시켰는지와 상관없이 타입 Spoofed가 반드시 동일하다는 것을 알려주기위해 C.f() 메쏘드의 첫 번째 라인을 검증할 때 SpoofedL1=SpoofedL2 제약을 생성시킵니다. L1 이나 L2 혹은 다른 클래스 로더가 Spoofed를 정의했다하더라도 상관은 없습니다. 중요한 것은 로딩을 초기화하는데 사용했던 것이 L1 이나 L2 어느 것이든 정의된 Spoofed 클래스가 오직 하나이여야 한다는 것입니다. L1 또는 L2를 사용하여 이미 별도의 Spoofed 버전을 정의했을 경우에는 점검과정에서 바로 LinkageError 가 만들어지게 됩니다. 만약 그렇지 않다면, 제약조건은 저장되어지고 Delegated.g()가 실행되면 Spoofed의 중복된 버전을 로드하려는 시도때문에 결과적으로 LinkageError가 발생하게 됩니다.

이제 여러분들과 함께 실제적인 예제를 갖고 어떻게 LinkageError가 발생하는지를 살펴보도록 하겠습니다. 예제 2.7, “LinkageError의 실제 예”에서는 example 메인 클래스와 함께 커스텀 클래스 로더를 사용한 것입니다.

예제 2.7. LinkageError의 실제 예

  1 package org.jboss.chap2.ex0;
    import java.io.File;
    import java.net.URL;
    
  5 import org.apache.log4j.Logger;
    import org.jboss.util.ChapterExRepository;
    import org.jboss.util.Debug;
    
    /** 
 10  * 비표준적인 클래스 로딩 환경에서 하나 이상의 클래스 로더를 사용하여
     * 정의된 클래스때문에 LinkageError가 발생되는 예
     *
     * @author Scott.Stark@jboss.org
     * @version $Revision: 1.11 $
 15  */
    public class ExLE
    {
        public static void main(String[] args) 
    	throws Exception
 20     {
            ChapterExRepository.init(ExLE.class);
    
            String chapDir = System.getProperty("chapter.dir");
            Logger ucl0Log = Logger.getLogger("UCL0");
 25         File jar0 = new File(chapDir+"/j0.jar");
            ucl0Log.info("jar0 path: "+jar0.toString());
            URL[] cp0 = {jar0.toURL()};
            Ex0URLClassLoader ucl0 = new Ex0URLClassLoader(cp0);
            Thread.currentThread().setContextClassLoader(ucl0);
 30         Class ctxClass1  = ucl0.loadClass("org.jboss.chap2.ex0.ExCtx");
            Class obj2Class1 = ucl0.loadClass("org.jboss.chap2.ex0.ExObj2");
            StringBuffer buffer = new StringBuffer("ExCtx Info");
            Debug.displayClassInfo(ctxClass1, buffer, false);
            ucl0Log.info(buffer.toString());
 35         buffer.setLength(0);
            buffer.append("ExObj2 Info, UCL0");
            Debug.displayClassInfo(obj2Class1, buffer, false);
            ucl0Log.info(buffer.toString());
            
 40         File jar1 = new File(chapDir+"/j1.jar");
            Logger ucl1Log = Logger.getLogger("UCL1");
            ucl1Log.info("jar1 path: "+jar1.toString());
            URL[] cp1 = {jar1.toURL()};
            Ex0URLClassLoader ucl1 = new Ex0URLClassLoader(cp1);
 45         Class obj2Class2 = ucl1.loadClass("org.jboss.chap2.ex0.ExObj2");
            buffer.setLength(0);
            buffer.append("ExObj2 Info, UCL1");
            Debug.displayClassInfo(obj2Class2, buffer, false);
            ucl1Log.info(buffer.toString());
 50         
            ucl0.setDelegate(ucl1);
            try {
                ucl0Log.info("Try ExCtx.newInstance()");
                Object ctx0 = ctxClass1.newInstance();
 55             ucl0Log.info("ExCtx.ctor succeeded, ctx0: "+ctx0);
            } catch(Throwable e) {
                ucl0Log.error("ExCtx.ctor failed", e);
            }
        }
 60 }
package org.jboss.chap2.ex0;

import java.net.URLClassLoader;
import java.net.URL;

import org.apache.log4j.Logger;

/** 
 * 표준 부모 위임 모델을 오버라이드시킨
 * 커스텀 클래스 로더
 *
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.11 $
 */
public class Ex0URLClassLoader extends URLClassLoader
{
    private static Logger log = Logger.getLogger(Ex0URLClassLoader.class);
    private Ex0URLClassLoader delegate;

    public Ex0URLClassLoader(URL[] urls)
    {
        super(urls);
    }
    
    void setDelegate(Ex0URLClassLoader delegate)
    {
        this.delegate = delegate;
    }
    
    protected synchronized Class loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        Class clazz = null;
        if (delegate != null) {
            log.debug(Integer.toHexString(hashCode()) +
		      "; Asking delegate to loadClass: " + name);
            clazz = delegate.loadClass(name, resolve);
            log.debug(Integer.toHexString(hashCode()) +
		      "; Delegate returned: "+clazz);
        } else {
            log.debug(Integer.toHexString(hashCode()) + 
		      "; Asking super to loadClass: "+name);
            clazz = super.loadClass(name, resolve);
            log.debug(Integer.toHexString(hashCode()) + 
		      "; Super returned: "+clazz);
        }
        return clazz;
    }

    protected Class findClass(String name)
        throws ClassNotFoundException
    {
        Class clazz = null;
        log.debug(Integer.toHexString(hashCode()) + 
		  "; Asking super to findClass: "+name);
        clazz = super.findClass(name);
        log.debug(Integer.toHexString(hashCode()) + 
		  "; Super returned: "+clazz);
        return clazz;
    }
}

이번 예제의 핵심 콤퍼넌트는 URLClassLoader의 서브클래스인 Ex0URLClassLoader 입니다. 이 클래스 로더는 기본 부모 위임 모델을 오버라이드하여 ucl0ucl1 인스턴스 모두에서 ExObj2 클래스를 로드한 후, ucl0 로부터 ucl1으로 위임 관계를 설정할 수 있게 합니다. 30과 31번 라인에서 ucl0 Ex0URLClassLoaderExCtxExObj2 클래스를 로드시키는데 사용되었습니다. ExLE.main의 45번째 라인에서는 ucl1 Ex0URLClassLoaderExObj2를 다시 로드하는데 사용되었습니다. 여기서 ucl0ucl1 클래스 로더 모두는 ExObj2 클래스 정의를 갖습니다. ucl0 에서 ucl1으로의 위임 관계는 51번째 라인의 ucl0.setDelegate(ucl1) 메쏘드 호출을 통해 설정되어집니다. 마지막으로 ExLE.main의 54번째 라인에서 ExCtx의 인스턴스가 ucl0을 통해 로드된 클래스를 사용하여 만들어집니다. ExCtx 클래스는 예제 2.2, “예제에서 사용되는 ExCtx, ExObj, 그리고 ExObj2 클래스들”에 보여지는 것과 동일한 것이며, 생성자(constructor)는 다음과 같습니다:

public ExCtx() 
    throws IOException
{
    value = new ExObj();
    Logger log = Logger.getLogger(ExCtx.class);
    StringBuffer buffer = new StringBuffer("ctor.ExObj");
    Debug.displayClassInfo(value.getClass(), buffer, false);
    log.info(buffer.toString());
    ExObj2 obj2 = value.ivar;
    buffer.setLength(0);
    buffer = new StringBuffer("ctor.ExObj.ivar");
    Debug.displayClassInfo(obj2.getClass(), buffer, false);
    log.info(buffer.toString());
}    

이제, ExCtx 클래스가 ucl0 클래스 로더에 의해 정의되어졌고 ExCtx 생성자가 실행된 시점에서 ucl0ucl1으로 위임되었기 때문에 ExCtx 생성자 24번 라인에서 완전한 타입 표현식의 형태로 다시 씌여지며 그 형식은 다음과 같습니다:

<ExObj2,ucl0>ucl0 obj2 = <ExObj,ucl1>ucl0 value * ivar

ExObj2의 타입은 ucl0ucl1 클래스 로더 인스턴스 모두에 대해서 일관성을 가져야만 하기 때문에 ExObj2ucl0 = ExObj2ucl1 의 제약조건 로딩이 생성됩니다. 우리는 위임 관계가 설정되기 이전에 ucl0ucl1 모두를 사용하여 ExObj2를 로드시켰기 때문에 제약조건은 위배되게 되며 실행시 LinkageError가 만들어질 수 밖에 없습니다. 예제는 다음의 명령으로 실행시킬 수 있습니다:

[nr@toki examples]$ ant -Dchap=chap2 -Dex=0e run-example
Buildfile: build.xml
...
[java] java.lang.LinkageError: loader constraints violated when linking org/jboss/chap2/ex0/ExObj2 class
[java] at org.jboss.chap2.ex0.ExCtx.<init>(ExCtx.java:24)
[java] at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
[java] at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
[java] at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
[java] at java.lang.reflect.Constructor.newInstance(Constructor.java:274)
[java] at java.lang.Class.newInstance0(Class.java:308)
[java] at java.lang.Class.newInstance(Class.java:261)
[java] at org.jboss.chap2.ex0.ExLE.main(ExLE.java:53)

우리가 예상했던대로 ExCtx 생성자의 24번째 라인에서 필요로하는 로더의 제약조건을 점검하는 중에 LinkageError가 발생됩니다.

2.2.2.3.1. 클래스 로딩에서 발생되는 문제를 디버깅하기

클래스 로딩에서 발생되는 문제점들을 디버깅하는 것은 클래스가 어디서 로드되었는지를 찾는것으로부터 시작합니다. 이를 위한 유용한 툴은 이 책의 예제에서 제공하는 org.jboss.util.Debug 클래스에서 찾을 수 있는 예제 2.8, “클래스에 대한 디버깅 정보 얻기”에서 보여주고 있는 코드조각입니다.

예제 2.8. 클래스에대한 디버깅 정보 얻기

Class clazz =...;
StringBuffer results = new StringBuffer();

ClassLoader cl = clazz.getClassLoader();
results.append("\n" + clazz.getName() + "(" + 
               Integer.toHexString(clazz.hashCode()) + ").ClassLoader=" + cl);
ClassLoader parent = cl;

while (parent != null) {
    results.append("\n.."+parent);
    URL[] urls = getClassLoaderURLs(parent);

    int length = urls != null ? urls.length : 0;
    for(int u = 0; u < length; u ++) {
	results.append("\n...."+urls[u]);
    }

    if (showParentClassLoaders == false) {
	break;
    }
    if (parent != null) {
	parent = parent.getParent();
    }
}

CodeSource clazzCS = clazz.getProtectionDomain().getCodeSource();
if (clazzCS != null) {
    results.append("\n++++CodeSource: "+clazzCS);
} else {
    results.append("\n++++Null CodeSource");
}

중요 항목들은 볼드체로 표시되어 있습니다. 첫 번째 항목은 모든 클래스 객체가 자신의 defining ClassLoader를 알고 있으며 이는 getClassLoader() 메쏘드를 통해 가능하다는 것입니다. 클래스 캐스트 예외상황, 올바르지 않은 액세스 예외상황 및 linkage 오류등과 같은 이전 섹션들에서 보았었던 바대로 알려진 Class타입에서의 스코프를 정의합니다. ClassLoader로부터 여러분은 부모 위임 체인을 구성하는 클래스 로더들의 계층도를 볼 수 있게 됩니다. 또한 클래스 로더가 URLClassLoader라고 한다면, 여러분은 클래스와 리소스 로딩에 사용되는 URL도 알 수 있습니다.

Class의 defining ClassLoaderClass가 어디서 로드되었는지를 말해줄 수는 없습니다. 이를 결정하기위해서 여러분은 반드시 java.security.ProtectionDomain을 획득한 후 java.security.CodeSource도 얻어야만 합니다. 클래스가 원래 로드되어진 URL 위치를 갖는 것이 바로 CodeSource 입니다. 모든 ClassCodeSource를 갖지 않는다는 것에 주의해야 합니다. 클래스가 자신의 CodeSource가 아닌 bootstrap 클래스 로더에 의해 로드되어진 경우에는 널이됩니다. 이에 대한 예로 java.*javax.* 패키지에 포함되어 있는 모든 클래스들이 해당됩니다.

이외에도 JBoss 서버로 로드되어지는 클래스들의 상세한 뷰가 유용할 수 있습니다. 예제 2.9, “클래스 로딩에 대한 상세한 로깅이 가능하도록 log4j.xml 설정하는 예제”에서 보여지는 것처럼 Log4j의 환경 설정을 변경함으로써 JBoss 클래스 로딩 레이어의 상세한 로그가 가능하도록 할 수 있습니다.

예제 2.9. 클래스 로딩에 대한 상세한 로깅이 가능하도록 log4j.xml 설정하는 예제

<appender name="UCL" class="org.apache.log4j.FileAppender">
    <param name="File" value="${jboss.server.home.dir}/log/ucl.log"/>
    <param name="Append" value="false"/>
    <layout class="org.apache.log4j.PatternLayout">
        <param name="ConversionPattern" value="[%r,%c{1},%t] %m%n"/>
    </layout>
</appender>
<category name="org.jboss.mx.loading" additivity="false">
    <priority value="TRACE" class="org.jboss.logging.XLevel"/>
    <appender-ref ref="UCL"/>
</category>

위의 설정을 통해 org.jboss.mx.loading 패키지내의 클래스들은 서버측 로그가 쌓이는 디렉터리에 있는 ucl.log 파일내에 결과를 저장하게 됩니다. 여러분이 클래스 로딩 코드를 살펴보지 않아 비록 이것이 의미없는 일이 될 수도 있지만, 버그 리포트를 제출하거나 클래스 로딩에서 발생되는 문제점을 제시하고자 할 때는 매우 중요한 정보가 됩니다. 여러분이 클래스 로딩에 따른 문제점을 찾아 버그 리포트에 그 내용을 보내고자 할 때 이 로그 파일을 첨부하여 SourceForge의 JBoss 프로젝트쪽에 올려주시면 됩니다. 만일 로그 파일이 크다면, 압축해서 scott.stark@jboss.org쪽으로 메일을 보내주셔도 됩니다.

2.2.2.4. JBoss 클래스 로딩 아키텍쳐의 내부

이제 우리는 자바 타입 시스템에서 정의된 클래스 로더의 역할에 대해 알아보았으며, 다음으로 JBoss에서의 클래스 로딩 아키텍쳐를 알아보도록 할 것입니다. 그림 2.3, “JBoss 클래스 로딩 컴포넌트의 핵심”.

JBoss 클래스 로딩 컴포넌트의 핵심

그림 2.3. JBoss 클래스 로딩 컴포넌트의 핵심

핵심이 되는 컴포넌트는 org.jboss.mx.loading.UnifiedClassLoader3 (UCL) 클래스 로더입니다. 이것은 표준 java.net.URLClassLoader의 확장판으로써 클래스들과 리소스들의 공유된 레포지터리를 사용할 수 있는 표준 부모 위임 모델(delegation model)을 오버라이드시킨 것 입니다. 이 공유된 레포지터리는 org.jboss.mx.loading.UnifiedLoaderRepository3 입니다. 모든 UCL들은 한 개의 UnifiedLoaderRepository3에 관련되어 있으며, 하나의 UnifiedLoaderRepository3는 보통 많은 UCL들을 갖습니다. 하나의 UCL은 이것과 관계되어 있는 클래스와 리소스의 로딩을 위한 다수의 URL들을 갖을 수 있습니다. 배치자는 탑-레벨의 배치 UCL을 공유된 클래스 로더로써 사용하고 모든 배치 아카이브들은 이 클래스 로더에 할당됩니다. 우리는 JBoss 배치자와 클래스 로딩 시스템사이의 상호작용에 대해 2.4.2 절, “JBoss MBean 서비스”에서 보다 상세히 다루게 될 것입니다.

UCL이 클래스의 로딩을 요청받게되면, 맨 처음에는 그 클래스가 이미 로드되었는지 확인하기 위해 레포지터리 캐쉬쪽을 살펴보게 됩니다. 레포지터리내에 존재하지 않는 클래스에 한해서만 UCL에 의해 레포지터리쪽으로 로드되게 됩니다. 기본적으로 모든 UCL 인스턴스들에 걸쳐 공유된 단일의 UnifiedLoaderRepository3가 존재하게 됩니다. 즉, UCL들은 단일 플랫 클래스 로더의 네임스페이스로 구성된다는 의미가 됩니다. UnfiedClassLoader3.loadClass(String, boolean) 메쏘드가 호출된 후 발생하는 시퀀스는 다음과 같습니다:

  1. UnifiedClassLoader3와 관련된 UnifiedLoaderRepository3 클래스 캐쉬를 점검합니다. 만일 클래스가 캐쉬내에 존재한다면 이를 리턴하여 주게 됩니다.
  2. 캐쉬내에 없다면, 클래스를 로드할 수 있는지를 UnfiedClassLoader3에 묻습니다. 이는 슈퍼클래스 URLClassLoader.loadClass(String, boolean) 메쏘드를 호출하는데 필요하며, 이 메쏘드를 통해 클래스가 클래스 로더와 관련된 URL에 존재하는지 혹은 부모 클래스 로더에서 찾을 수 있는지를 알 수 있게 됩니다. 여기서 클래스를 찾게되면 레포지터리 클래스 캐쉬에 위치시킨 후 리턴하게 됩니다.
  3. 윗 단계까지에서도 클래스를 로드하지 못했다면, UCL 맵의 레포지터리 패키지 이름에 기반하여 클래스를 제공해줄 수 있는지를 모든 UCL에 대해 질의하게 됩니다. UCL이 레포지터리에 추가되면 UCL과 관련된 URL들에서 가용한 패지키 이름들사이의 관계가 만들어지고, 패키지 이름들과 갱신된 패지키내의 클래스와 함께 UCL들의 매핑이 이루어집니다. 이를 통해 어떤 UCL에서 클래스를 로딩시켜야 되는지를 보다 빠르게 결정할 수 있습니다. 그런 후, UCL들은 레포지터리에 추가되어진 UCL들에서 순차적으로 요청된 클래스의 질의가 발생합니다. 이제 UCL이 클래스 로딩이 가능하다는 것을 발견하게 되면 리턴해주게 되며, 그렇치 않다면 java.lang.ClassNotFoundException이 발생됩니다.
2.2.2.4.1. 로더 레포지터리내에서의 클래스 뷰

클래스 정보에 대한 또다른 유용한 소스로는 UnifiedLoaderRepository 그 자신이 있습니다. 이것은 클래스와 패키지 정보를 보여줄 수 있는 오퍼레이션을 갖는 MBean입니다. 기본 레포지터리는 JMImplementation:name=Default,service=LoaderRepository라는 이름을 갖는 표준 JMX 아래에 존재하며, 그것의 MBean은 JMX 콘솔의 첫 페이지에서 해당 링크를 통해 액세스할 수 있습니다. 이 MBean의 JMX 콘솔 뷰는 그림 2.4.1, “JMX 콘솔에서의 디폴트 클래스 LoaderRepository MBean 뷰” 또는 그림 2.4.2, “웹 콘솔에서의 디폴트 클래스 LoaderRepository MBean 뷰” 처럼 보여지게 됩니다.

JMX 콘솔에서의 디폴트 클래스 LoaderRepository MBean 뷰

그림 2.4.1 JMX 콘솔에서의 디폴트 클래스 LoaderRepository MBean 뷰

웹 콘솔에서의 디폴트 클래스 LoaderRepository MBean 뷰

그림 2.4.2 웹 콘솔에서의 디폴트 클래스 LoaderRepository MBean 뷰

여기서 여러분은 2개의 유용한 오퍼레이션인 getPackageClassLoaders(String)displayClassInfo(String)를 찾을 수 있습니다. getPackageClassLoaders 오퍼레이션은 포함된 클래스나 주어진 패키지 이름에 해당하는 리소스들이 색인화된 클래스 로더의 세트를 리턴합니다. 패키지 이름은 끝이 점으로 이어지도록 되어야 합니다. org.jboss.ejb. 패지키 이름을 타이핑했다면, 다음과 같이 보여지게 될 것입니다:

[org.jboss.mx.loading.UnifiedClassLoader3@c9d92c{ url=file:/C:/cygwin/jboss-4.0/build/output/jboss-4.0.1RC2/server/default/tmp/deploy/tmp55621jboss-service.xml ,addedOrder=2}]

이것은 셋트를 표현하는 문자열입니다. 이 문자열을 통해 우리는 default/tmp/deploy/tmp55621jboss-service.xml 서술자를 가르키는 프라이머리 URL을 갖는 하나의 UnifiedClassLoader3 인스턴스를 볼 수 있습니다. 그리고 레포지터리에 2번째로 추가된 클래스(addedOrder=2로 표현된 것으로부터)라는 것과 서버의 환경을 담고 있는 디렉터리의 lib내에(즉, server/default/lib) 있는 모든 JAR 파일들을 갖는 클래스 로더는 것을 알 수 있습니다. 여러분이 패키지 이름을 org.jboss.jmx.adaptor.rmi.라고 입력했다면, 다음과 같은 세트가 보여지게 됩니다:

[org.jboss.mx.loading.UnifiedClassLoader3@145f0e3{ url=file:/C:/cygwin/jboss-4.0/build/output/jboss-4.0.1RC2/server/default/tmp/deploy/tmp46154jboss-service.xml ,addedOrder=2}]

이번에도 default/tmp/deploy/tmp46154jboss-service.xml UnifiedClassLoader3 인스턴스를 볼 수 있습니다.

해당 클래스에 대한 정보의 뷰는 완전한 형태의 클래스 이름를 displayClassInfo 오퍼레이션에 넘겨주어 볼 수 있습니다. 가령, org.jboss.jmx.adaptor.html.HtmlAdaptorServlet 에 대한 정보를 입력하여 오퍼레이션을 실행시키면 다음과 같은 화면을 볼 수 있습니다:

org.jboss.jmx.adaptor.html.HtmlAdaptorServlet Information
Repository cache version:
org.jboss.jmx.adaptor.html.HtmlAdaptorServlet(1b93526).ClassLoader=org.jboss.mx.loading.UnifiedClassLoader3@304648
{ url=file:/C:/cygwin/jboss-4.0/build/output/jboss-4.0.1RC2/server/default/deploy/jmx-console.war/ ,addedOrder=31}
..org.jboss.mx.loading.UnifiedClassLoader3@304648{ url=file:/C:/cygwin/jboss-4.0/build/output/jboss-4.0.1RC2/server/default/deploy/jmx-console.war/ ,addedOrder=31}
....file:/C:/cygwin/jboss-4.0/build/output/jboss-4.0.1RC2/server/default/deploy/jmx-console.war/
....file:/C:/cygwin/jboss-4.0/build/output/jboss-4.0.1RC2/server/default/deploy/jmx-console.war/WEB-INF/classes/
..org.jboss.system.server.NoAnnotationURLClassLoader@e53108
..sun.misc.Launcher$AppClassLoader@253498
....file:/C:/cygwin/jboss-4.0/build/output/jboss-4.0.1RC2/bin/run.jar
....file:/C:/cygwin/jdk1.4.2/lib/tools.jar
..sun.misc.Launcher$ExtClassLoader@9fef6f
....file:/C:/cygwin/jdk1.4.2/jre/lib/ext/dnsns.jar
....file:/C:/cygwin/jdk1.4.2/jre/lib/ext/ldapsec.jar
....file:/C:/cygwin/jdk1.4.2/jre/lib/ext/localedata.jar
....file:/C:/cygwin/jdk1.4.2/jre/lib/ext/sunjce_provider.jar
++++CodeSource: (file:/C:/cygwin/jboss-4.0/build/output/jboss-4.0.1RC2/server/default/deploy/jmx-console.war/WEB-INF/classes/ )
Implemented Interfaces:

### Instance0 found in UCL: org.jboss.mx.loading.UnifiedClassLoader3@304648{ url=file:/C:/cygwin/jboss-4.0/build/output/jboss-4.0.1RC2/server/default/deploy/jmx-console.war/ ,addedOrder=31}


### Instance1 via UCL: org.jboss.mx.loading.UnifiedClassLoader3@304648{ url=file:/C:/cygwin/jboss-4.0/build/output/jboss-4.0.1RC2/server/default/deploy/jmx-console.war/ ,addedOrder=31}

여기서 보여지는 정보들은 가용한 클래스 파일이 존재할 경우 클래스 로더에 의해 일단 로드된 후, 로더의 레포지터리내에 존재하게 되는 클래스 인스턴스의 정보를 덤프시킨 것입니다. 하나의 클래스가 하나 이상의 클래스 로더와 연관되어 있다면, 이럴 경우 클래스 로딩과 관련된 오류가 발생할 수 있는 여지가 있습니다.

2.2.2.4.2. 클래스의 범위(Scoping)

어플리케이션의 다양한 버전을 배치시킬 필요가 있다면, 3.x 클래스 로딩 모델의 기본 환경에서는 각각 별도의 JBoss 서버에 배치시켜주어야 합니다. 이러한 시나리오는 보안과 리소스 모니터링등과 같은 제어에 대한 권한을 보다 많이 확보하는 관점에서는 선호될 수도 있으나, 여러 서버의 인스턴스에 분산되기 때문에 관리의 어려움이 증가됩니다. 범위에 기반을 둔 배치방식을 사용하여 하나의 어플리케이션에서 파생된 다양한 버전들을 배치시킴으로써 이를 해결할 수 있는 방법도 존재합니다.

범위에 기반한 배치(deployment based scoping) 방법을 사용할 경우, 각각의 배치는 자신만의 클래스 로더 레포지터리를 디폴트 UnifiedLoaderRepository3로 위임하기전에 EAR에 포함된 배치 유닛의 UnifiedClassLoader3 인스턴스에서 처음으로 찾아볼 수 있는 하나의 HeirarchicalLoaderRepository3 폼으로 생성합니다. EAR에 따른 별도의 로더 레포지터리를 활성화하려면, 예제 2.10, “EAR 레벨에서 범위화된 클래스 로딩이 가능하도록 구성된 jboss-app.xml 예제.”에서 보여지는 것처럼 META-INF/jboss-app.xml 서술자를 말들어주어야 합니다.

예제 2.10. EAR 레벨에서 범위화된 클래스 로딩이 가능하도록 구성된 jboss-app.xml 예제.

<jboss-app>
    <loader-repository>some.dot.com:loader=webtest.ear</loader-repository>
</jboss-app>

로더-레포지터리 요소의 값은 EAR에 대해 생성된 레포지터리에 할당되는 JMX 객체 이름이 됩니다. 이 값은 반드시 고유해야 하며 JMX 객체이름으로 합당해야하지만 이름이 실제로 중요하지는 않습니다.

2.2.2.4.3. 완전한 클래스 로딩 모델

앞에서 논의되었던 핵심 클래스 로딩 컴포넌트에서 우리는 커스텀 UnifiedClassLoader3와 공유된 클래스 로딩 공간을 형성해주는 UnifiedLoaderRepository3 클래스에 대해 소개하였습니다. 클래스 로딩의 완전한 형태에는 UnifiedClassLoader3에서 사용되는 부모 클래스 로더와 함께 범위화에 대한 사항과 특수한 목적을 갖는 클래스 로딩을 위한 클래스 로더 또한 포함되어야만 합니다. 그림 2.5, “완전한 클래스 로더 뷰” 에서는 EJB들과 WAR 파일들을 포함하는 하나의 EAR 배치에 있을 수 있는 클래스의 계층 구조에 대한 아웃트라인을 보여주고 있습니다.

완전한 클래스 로더 뷰

그림 2.5. 완전한 클래스 로더 뷰

위 그림에는 다음과 같은 사항들이 포함되어 있습니다:

  • System ClassLoaders: 시스템 클래스로더에는 VM 메인 쓰레드의 쓰레드 컨텍스트 클래스 로더(TCL)나 JBoss 서버를 로딩하는 어플리케이션의 쓰레드를 참조하는 노드입니다.
  • ServerLoader: 서버로더는 다음의 부트 URL을 포함하는 시스템 클래스로더로 위임되는 URLClassLoader를 참조하는 노드입니다:
    • 모든 URL들은 시스템 속성 jboss.boot.library.list을 통해 참조됩니다. 이는 jboss.lib.url 속성에서 정의된 libraryURL로부터 상대적인 경로입니다. jboss.lib.url 속성이 지정되어 있지 않은 경우, 기본값은 jboss.home.url + /lib/이 됩니다. jboss.boot.library 속성이 지정되어 있지 않은 경우, 기본값은 jaxp.jar, log4j-boot.jar, jboss-common.jar, 그리고 jboss-system.jar 입니다.
    • JAXP JAR는 Main 엔트리 항목의 -j 옵션에 따라 crimson.jarxerces.jar가 됩니다. 기본값은 crimson.jar입니다.
    • JBoss JMX jar 와 GNU regex jar, jboss-jmx.jar 그리고 gnu-regexp.jar.
    • Oswego concurrency classes JAR, concurrent.jar
    • -L 커맨드 라인 옵션을 통해 라이브러리로 지정되는 JAR
    • -C 커맨드 라인 옵션을 통해 지정되는 JAR나 디렉토리들
  • Server: 서버 노드는 org.jboss.system.server.Server 인터페이스 수행으로 생성되어진 UCL의 컬렉션을 지칭합니다. 기본 수행으로 서버의 conf 디렉터리뿐만 아니라 patchDir 엔트리를 위한 UCL들이 생성됩니다. 마지막에 만들어지는 UCL은 JBoss 메인 쓰레드 컨텍스트 클래스 로더 세트입니다. 이것은 단일 UCL로 합쳐져 지원되는 UCL당 여러 URL들을 갖습니다.
  • All UnifiedClassLoader3s: All UnifiedClassLoader3 노드는 배치자들에 의해 만들어진 UCL들을 지칭합니다. 여기에는 EARs, jars, WARs, SARs 및 배치 검색자에의해 검색된 디렉토리들 뿐만 아니라 자신의 목록(manifest)에 의해 참조되는 JARs와 그 자신을 포함한 중첩된 배치 유닛들까지도 포함됩니다. 맨 처음 로드된 것만을 사용하게 될 경우, 그 결과는 우리가 예상하는 것처럼 되지 않을 수도 있습니다. 우리가 2.2.2.4.2 절, “클래스의 범위”에서 논의했었던 EAR 배치 유닛에 따른 범위화를 통한 방법을 이용하여 하나의 JBoss 서버에서 클래스의 여러 버전을 배치시킬 필요가 있을 때 사용할 수 있습니다.
  • EJB DynClassLoader: EJB DynClassLoader 노드는 간단한 HTTP 웹서비스를 통해 RMI 다이나믹 클래스 로딩을 제공하는데 사용하는 URLClassLoader의 서브클래스입니다. 여기에는 비어있는 한개의 URL[]과 자신의 부모 클래스 로더로써 TCL에 위임됩니다. 만일 웹서비스가 시스템 레벨의 클래스로 로드될 수 있도록 설정되어있다면, 시스템 클래스경고뿐만 아니라 UnifiedLoaderRepository3 내의 모든 클래스들까지 HTTP로 사용이 가능해집니다.
  • EJB ENCLoader: EJB ENCLoader노드는 EJB 배치자의 java:comp JNDI 컨텍스트를 위한 고유한 컨텍스트를 제공해주는 것만을 목적으로 존재하는 URLClassLoader입니다. 여기에는 빈 하나의 URL[]을 지정하고, 부모 클래스 로더로써 EJB DynClassLoader에 위임합니다.
  • Web ENCLoader: Web ENCLoader 노드는 웹 배치자의 java:comp JNDI 컨텍스트의 고유한 컨텍스트를 제공만을 목적으로 하는 URLClassLoader 입니다. 여기에는 빈 URL[]과 자신의 부모 클래스 로더로써 TCL에 위임됩니다.
  • WAR Loader: WAR Loader는 자신의 부모 클래스 로더로써의 Web ENCLoader에 위임되는 서브릿 컨테이너 특정 클래스로더입니다. 기본 비헤이비어는 자신의 부모 클래스 로더로부터 로드된 다음 WAR WEB-INFclasseslib 디렉터리에서 로드됩니다. 서블릿 2.3 클래스 로딩 모델이 활성화되어있다면, 먼저 자신의 WEB-INF 디렉토리에서 로드된 후 부모 클래스 로더가 로드됩니다.

현재의 폼에서는 JBoss 클래스 로딩 아키텍쳐에 장점과 단점 모두 존재하게 됩니다. 잇점은 다음과 같습니다.:

  • 클래스에 액세스할 수 있도록 배치 유닛에 복사할 필요가 없습니다.
  • 도메인으로 레포지터리의 노벨 파티셔닝, 디텍션의 의존성과 충돌등과 같은 많은 차기기능들을 이용할 수 있습니다.

단점들은 다음과 같습니다:

  • 기존의 배치자는 클래스의 중복을 피하기 위해 다시 패키지되어야 합니다. 로더 레포지터리내에서의 클래스 중복은 클래스 캐스트 예회상황을 만들어 낼 수 있으며 클래스가 로드된 방식에 따라 linkage 오류가 발생할 수도 있습니다.
  • 주어진 클래싀의 버전에 따라 배치자는 별도의 EARs로 분리되어야만 할 때도 생기며 이 경우에는 jboss-app.xml 서술자를 사용하여 고유한 HeirarchicalLoaderRepository3를 정의해주어야 합니다.

2.2.3. JBoss XMBeans

XMBeans은 JMX 모델 MBean의 JBoss JMX 수행 버전입니다. XMBeans는 DynamicMBean 인터페이스의 직접 수행에서 필요로 하는 번거로운 프로그래밍없이 동적인 MBean 메터데이터의 풍부한 기능을 이용할 수 있습니다. JBoss 모델 MBean 수행으로 XML 서술자를 통해 컴포넌트의 관리 인터페이스를 지정할 수 있게되므로, XMBean의 X가 이를 표시해준다고 할 수 있습니다. 동적인 MBean에서 필요로 하는 메터데이터를 기술하기 위한 간단한 메커니즘을 제공하기위해, XMBeans에서는 속성 영속성의 스펙 , 비헤이비어의 캐슁 및 MBean 수생 인터셉터와 같은 고급 커스터마이제이션까지도 허용됩니다. XMBean 서술자를 위한 jboss_xmbean_1_0.dtd의 고수준 요소(element)들은 그림 2.6, “JBoss 1.0 XMBean DTD 개요 (jboss_xmbean_1_0.dtd)”에 제시되어 있습니다.

The JBoss 1.0 XMBean DTD Overview (jboss_xmbean_1_0.dtd)

그림 2.6. JBoss 1.0 XMBean DTD 개요 (jboss_xmbean_1_0.dtd)

mbean 요소는 하나의 MBean(constructors, attributes, operations 및 notifications) 관리 인터페이스를 기술하는데 필요한 요소들을 포함하는 문서의 루트 요소입니다. 또한 여기에는 선택적 서술 요소까지 포함될 수 있으며, 이를 통해 영속성 정책의 스펙, 속성 캐쉬등과 같은 것을 허용하도록 하는 등 MBean의 목적을 기술하는데 사용되는 선택적 서술자 요소가 포함될 수 있습니다.

2.2.3.1. Descriptors

Descriptors 요소에는 subelement를 포함하는 모든 요소들에 대한 모든 descriptors가 포함됩니다. descriptors는 JBoss에서 미리 정의한 요소와 속성에 사용하는 것 뿐만 아니라 JMX 스펙내에서도 사용되기 때문에, 그림 2.7, “ Descriptors element content model”에서 보여지는 것처럼 커스텀 descriptors는 이름과 값 속성을 갖는 일반적인 descriptor 요소를 갖습니다.

The descriptors element content model

그림 2.7.  Descriptors element content model

중요한 descriptors 자식 요소에는 다음이 포함됩니다:

  • interceptors: interceptors 요소는 기본 스택을 대체할 때 사용될 수 있는 interceptor의 커스터마이징된 스택을 지정합니다. 현재까지는 MBean 레벨에서 지정될 때만 사용되어지지만, 향후 커스텀 속성이나 오퍼레이션 레벨의 interceptor 스택을 정의할 수도 있게 될 것입니다. interceptor 요소의 컨텐츠에는 커스텀 interceptor 스택을 지정합니다. 어떠한 interceptor 요소도 지정하지 않았다면, 표준 ModelMBean interceptors 가 사용되게 될 것입니다. 표준 interceptors 는 다음과 같습니다:

    • org.jboss.mx.interceptor.PersistenceInterceptor
    • org.jboss.mx.interceptor.MBeanAttributeInterceptor
    • org.jboss.mx.interceptor.ObjectReferenceInterceptor

    커스텀 interceptor 스택을 지정하게 되는 경우, 일반적으로 여러분은 대응되는 표준 interceptor를 대체하지 않는 경우 여러분의 것과 함께 표준 interceptors를 포함시키게 됩니다.

    각각의 interceptor 요소의 컨텐츠 값에는 interceptor 수행의 완전한 경로를 갖는 클래스의 이름을 지정해주게 됩며, 클래스는 반드시 org.jboss.mx.interceptor.Interceptor 인터페이스를 수행해야만 합니다. 또한 interceptor 클래스는 인자가 없는 컨스트럭쳐나 한 쌍(javax.management.MBeanInfo, org.jboss.mx.server.MBeanInvoker)을 받는 컨스트럭쳐 어느것이라도 가져야만 합니다.

    interceptor 요소는 interceptor 클래스 수행에 따른 JavaBean 스타일 속성에 대응되는 속성의 갯수를 갖을 수 있습니다. 각각의 interceptor 요소의 속성이 지정된 경우, interceptor 클래스는 매칭되는 셋터 메쏘드를 질의하게 됩니다. 속성값은 타입과 관련된 java.beans.PropertyEditor를 사용하여 interceptor 클래스 속성의 트루 타입으로 변환됩니다. 셋터(setter)가 없거나 PropertyEditor가 없는 경우 속성을 지정하라는 오류가 나타납니다.

  • persistence: persistence 요소는 JMX 스펙에서 권장하고 있는 persistPolicy, persistPeriod, persistLocationpersistName 영속성의 속성들의 스펙지정을 가능하게 합니다. persistence 요소의 속성에는 다음과 같은 것이 있습니다:

    • persistPolicy: persistPolicy 속성은 속성이 영속적이어야 하며 그 값이 다음중 하나이어야 할때 정의합니다

      • Never: 속성 값은 절대로 영속적이지 않은 변경되는 값들입니다.
      • OnUpdate: 속성 값은 갱신이 될 때마다 영속적인 값들입니다.
      • OnTimer: 속성 값은 persistPeriod에 의해 지정된 시간을 토대로 영속적인 값들입니다.
      • NoMoreOftenThan: 속성 값은 persistPeriod보다 자주 갱신되어지지 않을 때 영속적인 값들입니다.
    • persistPeriod: persistPeriod 속성은 perisitPolicy 속성이 NoMoreOftenThan 또는 OnTimer일 경우, 밀리세컨드 단위로 갱신되는 주기를 지정합니다.
    • persistLocation: persistLocation 속성은 영속적으로 저장되어지는 장소를 지정합니다. 이것의 형태는 JMX 영속성 수행에 따라 달라집니다. 현재 이것은 JBoss의 디폴트 영속성 관리자를 사용하여 속성들을 시리얼라이즈시키는 디렉토리를 참조하도록 해야 합니다.
    • persistName: persistName 속성은 persistLocation 속성과 함께 붙어 사용되어 보다 양질의 영속적인 저장 위치를 지정하게 합니다. persistLocation 디렉터리에서 persistName은 디렉토리내에 저장되어지는 속성이 담길 파일을 지정합니다.
  • currencyTimeLimit: currencyTimeLimit 요소에는 속성이 정당한 동안 캐쉬되는 값의 시간을 초단위로 지정합니다. 이 속성 값은 초 단위로 지정하십시오. 값이 0 인 경우에는 캐쉬하지 않고 항상 MBean으로부터 가져오라는 의미가 됩니다. -1 로 값을 지정하면 항상 캐쉬된 값을 가져오라는 의미가 됩니다.
  • state-action-on-update: state-action-on-update 요소에는 MBean의 속성중에 하나라도 갱신되어질 때 어떤 일을 발생시켜야 하는지를 지정하게 됩니다. 취해질 액션은 value 속성에 지정하십시오. value 속성은 MBean의 속성중에 하나가 갱신되어지면 mbean의 라이프사이클에서 발생되어질 액션을 정의합니다. 여기에 올 수 있는 값들은 다음중 하나가 됩니다: keep-running, restart, reconfigure, reinstantiate. 하지만, 현재 이 descriptor는 사용되지 않고 있다는 것에 주의해야 합니다.
  • display-name: display-name 요소에는 아이템의 이름을 사람이 쉽게 인식할 수 있도록 하는 이름으로 부여합니다.
  • default: default 요소에는 필드가 설정되어 있지 않을 경우에 사용되는 디폴트 값을 지정합니다. 이 값은 jboss-service.xml 속성 요소 컨텐츠 값의 경우에서처럼 구동시 MBean에 씌여지지 않습니다. 디폴트 값이 어떠한 속성의 값이 정의되지 않았을 경우에만 사용되지만, 어떠한 value 요소도 정의되지는 않습니다.
  • value: value 요소에는 관리 속성의 현재 값이 지정됩니다. default 요소와는 다르게, value 요소는 구동시 MBean을 통해 씌여질 수 있는 setter 메쏘드의 사용이 가능합니다.
  • persistence-manager: persistence-manager 요소에는 영속성 관리자로써 사용될 클래스의 이름을 지정합니다. value 속성에는 org.jboss.mx.persistence.PersistenceManager 인터페이스 수행을 공급하는 클래스의 이름을 지정합니다. 현재 JBoss에서 지원되는 수행부는 오직 ModelMBeanInfo 컨텐츠를 자바 시리얼라이제이션을 사용하여 파일로 시리얼라이즈시키는 org.jboss.mx.persistence.ObjectStreamPersistenceManager 뿐입니다.
  • descriptor: descriptor 요소에는 JBoss에서 알지 못하는 임의의 descriptor를 지정합니다. 이것의 name 속성에는 descriptor의 타입을 지정하고 value 속성에는 descriptor의 값을 지정합니다. descriptor 요소는 임의의 관리 메터데이터의 추가가 가능하게 합니다.

어떠한 컨스트럭쳐, 속성, 오퍼레이션 혹은 통지(notification) 요소라도 임의의 확장 descriptor 셋팅뿐만 아니라 descriptors 정의를 지정해주는 descriptors 요소를 갖을 수 있다는 것에 주의하십시오.

2.2.3.2. 관리 클래스

class 요소에는 XMBean descriptor에 의해 기술된 관리 인터페이스를 갖는 관리되는 객체의 완전한 경로를 포함한 이름을 지정합니다.

2.2.3.3. 컨스트럭쳐(Constructors)

constructor 요소는 관리되는 객체의 인스턴스를 생성할 수 있는 가용한 컨스트럭쳐를 지정합니다. 컨스트럭쳐 요소와 컨텐츠 모델은 그림 2.8, “XMBean 컨스트럭쳐 요소와 컨텐츠 모델”에 제시되어 있습니다.

XMBean 컨스트럭쳐 요소와 컨텐츠 모델

Figure 2.8. XMBean 컨스트럭쳐 요소와 컨텐츠 모델

중요 자식 요소들은 다음과 같습니다:

  • description: 컨스트럭쳐의 설명.
  • name: 수행 클래스와 동일해야하는 컨스트럭쳐의 이름.
  • parameter: 컨스트럭쳐 매개변수를 기술합니다. parameter 요소는 다음과 같은 속성들을 갖습니다:

    • description: 매개변수의 선택적 설명.
    • name: 매개변수의 필요한 변수 이름.
    • type: 매개변수 타입에 필요한 완전한 경로를 포함한 클래스의 이름.
  • descriptors: 컨스트럭쳐 메터데이터와 관련된 descriptors.

2.2.3.4. 속성(Attribute)

attribute 요소는 MBean에 의해 노출된 관리 속성들을 지정합니다. attribute 요소와 컨텐츠 모델은 그림 2.9, “XMBean attribute 요소와 컨텐츠 모델”에 표시되어 있습니다.

XMBean attribute 요소와 컨텐츠 모델

그림 2.9. XMBean attribute 요소와 컨텐츠 모델

attribute 요소는 다음과 같은 속성들을 지원합니다:

  • access: 선택적으로 사용되는 access 속성은 속성의 읽기/쓰기 액세스 모드를 정의합니다. 다음중 하나의 값을 갖아야만 합니다:

    • read-only: 읽기만 가능한 속성.
    • write-only: 쓰기만 가능한 속성.
    • read-write: 읽기와 쓰기 모두 가능한 속성. 이것이 디폴트입니다.
  • getMethod: getMethod 속성은 이름이 붙은 속성을 읽는 메쏘드의 이름을 정의합니다. 관리되는 속성이 MBean 인스턴스로부터 획득되어졌다면 이것은 반드시 지정되어야만 합니다.
  • setMethod: setMethod 속성은 이름이 붙은 속성을 쓰는 메쏘드의 이름을 정의합니다. 관리되는 속성이 MBean 인스턴스로부터 획득되어졌다면 이것은 반드시 지정되어야만 합니다.

attribute 요소에 포함되는 중요 자식 요소는 다음과 같습니다:

  • description: attribute의 설명.
  • name: MBeanServer.getAttribute() 오퍼레이션내에서 사용될 수 있는 속성의 이름.
  • type: 속성 타입의 완전한 경로를 포함하는 클래스의 이름.
  • descriptors: 속성의 영속성, 캐쉬, 디폴트 값등에 영향을 미치는 추가적인 desciptors.

2.2.3.5. 오퍼레이션(Operations)

XMBean에 의해 노출된 관리 오퍼레이션은 하나 이상의 오퍼레이션 요소를 통해 지정되어 집니다. 오퍼레이션 요소와 컨텐츠 모델은 The management operations exposed by the XMBean are specified via one or more operation elements. The operation element and its content model are shown in 그림 2.10, “XMBean 오퍼레이션 요소와 컨텐츠 모델”.

XMBean 오퍼레이션 요소와 컨텐츠 모델

그림 2.10. XMBean 오퍼레이션 요소와 컨텐츠 모델

impact 속성은 오퍼레이션 실행의 임팩트를 정의하며 다음 값중에 하나를 가져야만 합니다:

  • ACTION: MBean 콤퍼넌트의 상태를 변경시키는 오퍼레이션(쓰기 오퍼레이션).
  • INFO: 오퍼레이션이 MBean 콤퍼넌트의 상태를 변경하지 않음(읽기 오퍼레이션).
  • ACTION_INFO: 읽기/쓰기 오페러이션.

자식 요소는 다음과 같습니다:

  • description: 이 요소에는 오퍼레이션에 대한 사람이 읽을 수 있는 설명을 지정합니다.
  • name: 이 요소에는 오퍼레이션의 이름이 포함됩니다.
  • parameter: 이 요소는 오퍼레이션의 서명을 기술합니다.
  • return-type: 이 요소에는 이 오퍼레이션으로부터 반환된 타입의 완전한 경로를 포함한 클래스의 이름이 포함됩니다. 지정하지 않는다면 그 기본값은 void가 됩니다.
  • descriptors: 오퍼레이션 메터데이터와 관련된 descriptors.

2.2.3.6. 통지(Notifications)

notification 요소는 XMBean에 의해 방출될 수 있는 관리 통지를 설명합니다. notification 요소와 컨텐츠 모델은 그림 2.11, “XMBean notification 요소와 컨텐츠 모델”에 표시되어 있습니다.

XMBean notification 요소와 컨텐츠 모델

그림 2.11. XMBean notification 요소와 컨텐츠 모델

자식 요소는 다음과 같습니다:

  • description: 이 요소는 사람이 읽을 수 있는 통지의 설명을 지정합니다.
  • name: 이 요소에는 통지 클래스의 완전한 경로를 포함한 이름이 주어집니다.
  • notification-type: 이 요소에는 점으로 분리된 통지 타입 문자열이 포함됩니다.
  • descriptors: 통지 메터데이터와 관련된 descriptors.

완전한 DTD 컨텐츠 모델을 참조하려면 그림 2.12, “jboss_xmbean_1_0 DTD의 확장된 뷰”에서 제공되는 완전히 확장된 뷰를 보시면 됩니다. 우리가 JBoss MBean 서비스들을 논의하면서 XMBeans를 생성하는 예제를 통해 실제로 사용하게 될 것입니다. 이 예제들을 보시려면 2.4.3.2 절, “XMBean 예제”에 가시면 됩니다.

jboss_xmbean_1_0 DTD의 확장된 뷰

그림 2.12. jboss_xmbean_1_0 DTD의 확장된 뷰

2.3. JMX 서버에 연결하기

JBoss는 JBoss 서버 VM 외부로부터 JMX MBeanServer에 액세스할 수 있는 아답터들을 갖고 있습니다. 현재는 HTML, RMI 인터페이스 및 EJB를 포함한 아답터들이 있습니다.

2.3.1. 서버 살펴보기 - JMX 콘솔 웹 어플리케이션

JBoss에는 표준 웹 브라우저를 통해 서버의 MBeans을 볼 수 있도록 하는 JMX HTML 아답터의 수행부가 포함되어 있습니다. 콘솔 웹 어플리케이션의 기본 URL은 http://localhost:8080/jmx-console/입니다. 여러분이 이 위치로 브라우징하면, 그림 2.13, “JBoss JMX 콘솔 웹 어플리케이션 에이전트 뷰”에서 보여지는 것과 유사한 화면을 볼 수 있게 됩니다.

JBoss JMX 콘솔 웹 어플리케이션 에이전트 뷰

그림 2.13. JBoss JMX 콘솔 웹 어플리케이션 에이전트 뷰

처음의 뷰는 에이전트 뷰라고 불려지며, 여기서는 MBean의 ObjectName의 도메인 부분에 정렬되어 MBeanServer와 함께 등록되어진 모든 MBeans의 목록을 제공합니다. 각각의 도메인 아래에는 해당 도메인에 위치된 MBean들이 있습니다. 여러분이 MBeans중에 하나를 선택했다면, 선택한 MBean의 뷰를 볼 수 있게 됩니다. 이를 통해 여러분은 MBean의 뷰와 속성 편집은 물론 오퍼레이션을 호출할 수도 있습니다. 일례로, 그림 2.14, “"jboss.system:type=Server" MBean의 뷰”에서는 jboss.system:type=Server MBean의 뷰를 보여주는 화면을 볼 수 있습니다.

"jboss.system:type=Server" MBean의 뷰

그림 2.14. "jboss.system:type=Server" MBean의 뷰

JMX 콘솔 웹 어플리케이션의 소스 코드는 src/main/org/jboss/jmx 디렉터리아래의 varia 모듈에 있습니다. 이것의 웹 페이지는 varia/src/resources/jmx 아래에 위치됩니다. 이 어플리케이션은 MBeanServer를 활용성을 보여주는 JSP로 작성된 간단한 MVC 서블릿입니다.

2.3.1.1. JMX 콘솔의 보안

JMX 콘솔 웹 어플리케이션은 표준 서블릿이기 때문에 J2EE의 표준 역할 기반의 보안을 사용하여 보안기능을 구현할 수 있습니다. jmx-console.war는 언팩된 WAR로 배치되며, 여기에는 간단하게 사용자 이름과 암호 기반의 접근 제어가 가능하도록 하는 설정을 빠르게 할 수 있도록 하는 템플릿이 포함되어 있습니다. server/default/deploy 디렉터리의 jmx-console.war를 살펴보면, WEB-INF 디렉터리안에 web.xmljboss-web.xml 서술자를 찾을 수 있으며, WEB-INF/classes 아래에서 jmx-console-roles.properties와 jmx-console-users.properties 파일을 찾을 수 있습니다.

예제 2.11, “보안에 관련된 요소들의 주석을 제거시킨 jmx-console.war web.xml 서술자.”에서 처럼 web.xmljboss-web.xml 서술자에서 보안에 관련된 섹션을 주석제거하면, jmx-console 어플리케이션을 사용하려는 사용자는 admin이라는 암호를 갖는 admin 계정으로 HTTP 기본 인증과정을 거쳐야만 접근할 수 있게 됩니다. 사용자 계정과 암호는 jmx-console-users.properties 파일에 있는 admin=admin 라인에서 결정되어집니다.

예제 2.11. 보안에 관련된 요소들의 주석을 제거시킨 jmx-console.war web.xml 서술자.

<?xml version="1.0"?>
<!DOCTYPE web-app PUBLIC
          "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
          "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
    <!-- ... -->
    
    <!-- A security constraint that restricts access to the HTML JMX console
         to users with the role JBossAdmin. Edit the roles to what you want and
         uncomment the WEB-INF/jboss-web.xml/security-domain element to enable
         secured access to the HTML JMX console.
    -->
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>HtmlAdaptor</web-resource-name>
            <description> An example security config that only allows users with
                the role JBossAdmin to access the HTML JMX console web
                application </description>
            <url-pattern>/*</url-pattern>
            <http-method>GET</http-method>
            <http-method>POST</http-method>
        </web-resource-collection>
        <auth-constraint>
            <role-name>JBossAdmin</role-name>
        </auth-constraint>
    </security-constraint>
    <login-config>
        <auth-method>BASIC</auth-method>
        <realm-name>JBoss JMX Console</realm-name>
    </login-config>
    <security-role>
        <role-name>JBossAdmin</role-name>
    </security-role>
</web-app>

예제 2.12. 보안에 관련된 요소를 주석제거시킨 jmx-console.war jboss-web.xml descriptors.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss-web
    PUBLIC "-//JBoss//DTD Web Application 2.3//EN"
    "http://www.jboss.org/j2ee/dtd/jboss-web_3_0.dtd">
<jboss-web>
    <!-- 
        Uncomment the security-domain to enable security. You will
        need to edit the htmladaptor login configuration to setup the
        login modules used to authentication users.      
    -->
    <security-domain>java:/jaas/jmx-console</security-domain>
</jboss-web>

위의 변경내역을 확인한 후 JMX 콘솔 URL을 접근해보십시오. 그러면 그림 2.15, “기본 HTTP 로그인 창을 갖는 JMX 콘솔.”에서 보이는 것과 유사한 창이 나타나게 됩니다.

기본 HTTP 로그인 창을 갖는 JMX 콘솔.

그림 2.15. 기본 HTTP 로그인 창을 갖는 JMX 콘솔.

일반적으로 JMX 콘솔 어플리케이션의 액세스를 안전하게 하기위해 프로퍼티를 사용하는 것은 좋은 방법은 아닙니다. 웹 어플리케이션의 보안 설정에 대한 보다 올바른 방법은 8 장, JBoss의 보안을 참조하십시오.

2.3.2. RMI를 사용하여 JMX에 연결하기

JBoss는 JMX MBeanServer로 연결할 수 있도록 RMI 인터페이스를 제공합니다. 이 인터페이스는 org.jboss.jmx.adaptor.rmi.RMIAdaptor 이며, 예제 2.13, “RMIAdaptor 인터페이스”에서 볼 수 있습니다.

예제 2.13. RMIAdaptor 인터페이스

/*
 * JBoss, the OpenSource J2EE webOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jboss.jmx.adaptor.rmi;

import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.ObjectName;
import javax.management.QueryExp;
import javax.management.ObjectInstance;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.MBeanInfo;

import javax.management.AttributeNotFoundException;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.IntrospectionException;
import javax.management.InvalidAttributeValueException;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanRegistrationException;
import javax.management.NotCompliantMBeanException;
import javax.management.OperationsException;
import javax.management.ReflectionException;

public interface RMIAdaptor
    extends java.rmi.Remote
{
    
    public ObjectInstance createMBean(String pClassName,
                                      ObjectName pName)
        throws ReflectionException,
               InstanceAlreadyExistsException,
               MBeanRegistrationException,
               MBeanException,
               NotCompliantMBeanException,
               RemoteException;
    
    public ObjectInstance createMBean(String pClassName,
                                      ObjectName pName,
                                      ObjectName pLoaderName)
        throws ReflectionException,
               InstanceAlreadyExistsException,
               MBeanRegistrationException,
               MBeanException,
               NotCompliantMBeanException,
               InstanceNotFoundException,
               RemoteException;
    
    public ObjectInstance createMBean(String pClassName,
                                      ObjectName pName,
                                      Object[] pParams, 
                                      String[] pSignature)
        throws ReflectionException,
               InstanceAlreadyExistsException,
               MBeanRegistrationException,
               MBeanException,
               NotCompliantMBeanException,
               RemoteException;
    
    public ObjectInstance createMBean(String pClassName,
                                      ObjectName pName,
                                      ObjectName pLoaderName, 
                                      Object[] pParams, 
                                      String[] pSignature)
        throws ReflectionException,
               InstanceAlreadyExistsException,
               MBeanRegistrationException,
               MBeanException,
               NotCompliantMBeanException,
               InstanceNotFoundException,
               RemoteException;
    
    public void unregisterMBean(ObjectName pName)
        throws InstanceNotFoundException,
               MBeanRegistrationException,
               RemoteException;
    
    public ObjectInstance getObjectInstance(ObjectName pName)
        throws InstanceNotFoundException,
               RemoteException;
    
    public Set queryMBeans(ObjectName pName, QueryExp pQuery)
        throws RemoteException;
    
    public Set queryNames(ObjectName pName, QueryExp pQuery)
        throws RemoteException;
    
    public boolean isRegistered(ObjectName pName)
        throws RemoteException;
    
    public boolean isInstanceOf(ObjectName pName, String pClassName)
        throws InstanceNotFoundException,
               RemoteException;
                
    public Integer getMBeanCount()
        throws RemoteException;
    
    public Object getAttribute(ObjectName pName, String pAttribute)
        throws MBeanException,
               AttributeNotFoundException,
               InstanceNotFoundException,
               ReflectionException,
               RemoteException;
    
    public AttributeList getAttributes(ObjectName pName,
                                       String[] pAttributes)
        throws InstanceNotFoundException,
               ReflectionException,
               RemoteException;
    
    public void setAttribute(ObjectName pName, Attribute pAttribute)
        throws InstanceNotFoundException,
               AttributeNotFoundException,
               InvalidAttributeValueException,
               MBeanException,
               ReflectionException,
               RemoteException;
    
    public AttributeList setAttributes(ObjectName pName,
                                       AttributeList pAttributes)
        throws InstanceNotFoundException,
               ReflectionException,
               RemoteException;
    
    public Object invoke(ObjectName pName, String pActionName,
                         Object[] pParams, String[] pSignature)
        throws InstanceNotFoundException,
               MBeanException,
               ReflectionException,
               RemoteException;
    
    public String getDefaultDomain()
        throws RemoteException;
                
    public void addNotificationListener(ObjectName pName,
                                        ObjectName pListener,
                                        NotificationFilter pFilter, 
                                        Object pHandback)
        throws InstanceNotFoundException,
               RemoteException;
    
    public void removeNotificationListener(ObjectName pName,
                                           ObjectName pListener)
        throws InstanceNotFoundException,
               ListenerNotFoundException,
               RemoteException;
    
    public MBeanInfo getMBeanInfo(ObjectName pName)
        throws InstanceNotFoundException,
               IntrospectionException,
               ReflectionException,
               RemoteException;
    
}

RMIAdaptor 인터페이스는 org.jboss.jmx.adaptor.rmi.RMIAdaptorService MBean에 의해 JNDI쪽으로 바운드되지만 3.2.2 버전에서 이 서비스는 디폴트로는 dist deploy 디렉터리에서 빠지게 되었습니다. docs/examples/jmx 디렉터리에서 찾을 수 있으나 호출 아답터 서비스의 선호항목에는 빠지게 되었습니다. 또한 이 서비스는 RMIAdaptor 인터페이스를 지원하며 기존 클라이언트와의 이전버전 호환성을 위해 jmx/rmi/RMIAdaptor의 디폴트 위치에서 이 인터페이스의 바인딩을 지원하고 있습니다. RMIAdaptorService는 원격지의 클라이언트가 JMX 통지를 수신할 필요가 있을 때 여전히 사용됩니다. 호출 아답터 서비스는 이러한 기능을 지원하지 않기 때문에 원격 클라이언트의 JMX 통지가 필요할 경우에는 examples 디렉터리에서 jmx-invoker-adaptor-server.sarjmx-rmi-adaptor.sar 로 반드시 대체시켜주어야만 합니다.

RMIAdaptorServicejmx-rmi-adaptor.sar 패키지로 배치되며 다음과 같은 속성들을 지원합니다:

  • JndiName: RMIAdaptor 인터페이스에 바운드되는 JNDI 이름. 기본 이름은 jmx/rmi/RMIAdaptor 입니다.
  • RMIObjectPort: 내보내진 RMI 객체의 서버측 수신 포트의 번호. 기본 값은 0 으로, 사용이 가능한 임의의 포트를 의미합니다.
  • ServerAddress: 내보내지는 RMI 수신 포트에 바인딩되는 서버 인터페이스 이름이나 IP 주소. 기본값은 빈 값으로써 사용이 가능한 모든 인터페이스에 바인딩된다는 의미가 됩니다.
  • Backlog: 연결 오류가 발생하기전에 수신되는 클라이언트 연결 요청의 RMI 객체 서버의 소켓 backlog.

예제 2.14, “ RMIAdaptor를 사용하는 JMX 클라이언트”에서는 RMIAdaptor 인터페이스를 사용하여 JNDIView MBean의 MBeanInfo를 질의하는 방법을 보여주고 있습니다. 여기서는 또한 MBean의 list(boolean) 메쏘드와 그 결과를 표시해줍니다.

예제 2.14.  RMIAdaptor를 사용하는 JMX 클라이언트

public class JMXBrowser
{
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args)
	throws Exception
    {
	InitialContext ic = new InitialContext();
	RMIAdaptor server = (RMIAdaptor) ic.lookup("jmx/rmi/RMIAdaptor");
                
	// Get the MBeanInfo for the JNDIView MBean
	ObjectName name = new ObjectName("jboss:service=JNDIView");
	MBeanInfo info = server.getMBeanInfo(name);
	System.out.println("JNDIView Class: "+info.getClassName());

	MBeanOperationInfo[] opInfo = info.getOperations();
	System.out.println("JNDIView Operations: ");

	for (int o = 0; o < opInfo.length; o ++) {
	    MBeanOperationInfo op = opInfo[o];

	    String returnType = op.getReturnType();
	    String opName = op.getName();

	    System.out.print(" +"+returnType+" "+opName+"(");

	    MBeanParameterInfo[] params = op.getSignature();
	    for (int p = 0; p < params.length; p++) {
		MBeanParameterInfo paramInfo = params[p];

		String pname = paramInfo.getName();
		String type = paramInfo.getType();

		if (pname.equals(type)) {
		    System.out.print(type);
		} else {
		    System.out.print(type+" "+name);
		}

		if (p < params.length-1) {
		    System.out.print(',');
		}
	    }
	    System.out.println(")");
	}
                
	// Invoke the list(boolean) op
	String[] sig = {"boolean"};
	Object[] opArgs = {Boolean.TRUE};
	Object result = server.invoke(name,
				      "list", opArgs, sig);
	System.out.println("JNDIView.list(true) output:\n"+result);
    }
}

클라이언트가 RMIAdaptor를 사용하는지를 테스트하려면, 다음과 같은 명령을 통해 실행시키십시오:

[orb@toki examples]$ ant -Dchap=chap2 -Dex=4 run-example
Buildfile: build.xml
 
...
                 
run-example4:
     [java] JNDIView Class: org.jboss.mx.modelmbean.XMBean
     [java] JNDIView Operations: 
     [java]  + java.lang.String list(boolean jboss:service=JNDIView)
     [java]  + java.lang.String listXML()
     [java]  + void create()
     [java]  + void start()
     [java]  + void stop()
     [java]  + void destroy()
     [java]  + void jbossInternalLifecycle(java.lang.String jboss:service=JNDIView)
     [java]  + java.lang.String getName()
     [java]  + int getState()
     [java]  + java.lang.String getStateString()
     [java] JNDIView.list(true) output:
     [java] <h1>java: Namespace</h1>
     [java] <pre>
     [java]   +- XAConnectionFactory (class: org.jboss.mq.SpyXAConnectionFactory)
     [java]   +- DefaultDS (class: org.jboss.resource.adapter.jdbc.WrapperDataSource)
     [java]   +- SecurityProxyFactory (class: org.jboss.security.SubjectSecurityProxyFacto
ry)
     [java]   +- DefaultJMSProvider (class: org.jboss.jms.jndi.JNDIProviderAdapter)
     [java]   +- comp (class: javax.naming.Context)
     [java]   +- JmsXA (class: org.jboss.resource.adapter.jms.JmsConnectionFactoryImpl)
     [java]   +- ConnectionFactory (class: org.jboss.mq.SpyConnectionFactory)
     [java]   +- jaas (class: javax.naming.Context)
     [java]   |   +- JmsXARealm (class: org.jboss.security.plugins.SecurityDomainContext)
     [java]   |   +- jbossmq (class: org.jboss.security.plugins.SecurityDomainContext)
     [java]   |   +- HsqlDbRealm (class: org.jboss.security.plugins.SecurityDomainContext)
     [java]   +- timedCacheFactory (class: javax.naming.Context)
     [java] Failed to lookup: timedCacheFactory, errmsg=null
     [java]   +- TransactionPropagationContextExporter (class: org.jboss.tm.TransactionPro
pagationContextFactory)
     [java]   +- Mail (class: javax.mail.Session)
     [java]   +- StdJMSPool (class: org.jboss.jms.asf.StdServerSessionPoolFactory)
     [java]   +- TransactionPropagationContextImporter (class: org.jboss.tm.TransactionPro
pagationContextImporter)
     [java]   +- TransactionManager (class: org.jboss.tm.TxManager)
     [java] </pre>
     [java] <h1>Global JNDI Namespace</h1>
     [java] <pre>
     [java]   +- HAILConnectionFactory[link -> ConnectionFactory] (class: javax.naming.
                LinkRef)
     [java]   +- jmx (class: org.jnp.interfaces.NamingContext)
     [java]   |   +- invoker (class: org.jnp.interfaces.NamingContext)
     [java]   |   |   +- RMIAdaptor (proxy: $Proxy26 implements interface org.jboss.jmx.ad
                aptor.rmi.RMIAdaptor,interface org.jboss.jmx.adaptor.rmi.RMIAdaptorExt)
     [java]   |   +- rmi (class: org.jnp.interfaces.NamingContext)
     [java]   |   |   +- RMIAdaptor[link -> jmx/invoker/RMIAdaptor] (class: javax.namin
g.LinkRef)
     [java]   +- HTTPXAConnectionFactory (class: org.jboss.mq.SpyXAConnectionFactory)
     [java]   +- ConnectionFactory (class: org.jboss.mq.SpyConnectionFactory)
     [java]   +- UserTransactionSessionFactory (proxy: $Proxy13 implements interface org.j
                boss.tm.usertx.interfaces.UserTransactionSessionFactory)
     [java]   +- HTTPConnectionFactory (class: org.jboss.mq.SpyConnectionFactory)
     [java]   +- XAConnectionFactory (class: org.jboss.mq.SpyXAConnectionFactory)
     [java]   +- invokers (class: org.jnp.interfaces.NamingContext)
     [java]   |   +- 0.0.0.0 (class: org.jnp.interfaces.NamingContext)
     [java]   |   |   +- pooled (class: org.jboss.invocation.pooled.interfaces.PooledInvok
erProxy)
     [java]   +- UserTransaction (class: org.jboss.tm.usertx.client.ClientUserTransaction)
     [java]   +- UILXAConnectionFactory[link -> XAConnectionFactory] (class: javax.nami
ng.LinkRef)
     [java]   +- HAILXAConnectionFactory[link -> XAConnectionFactory] (class: javax.nam
ing.LinkRef)
     [java]   +- UIL2XAConnectionFactory[link -> XAConnectionFactory] (class: javax.nam
ing.LinkRef)
     [java]   +- queue (class: org.jnp.interfaces.NamingContext)
     [java]   |   +- A (class: org.jboss.mq.SpyQueue)
     [java]   |   +- testQueue (class: org.jboss.mq.SpyQueue)
     [java]   |   +- ex (class: org.jboss.mq.SpyQueue)
     [java]   |   +- DLQ (class: org.jboss.mq.SpyQueue)
     [java]   |   +- D (class: org.jboss.mq.SpyQueue)
     [java]   |   +- C (class: org.jboss.mq.SpyQueue)
     [java]   |   +- B (class: org.jboss.mq.SpyQueue)
     [java]   +- topic (class: org.jnp.interfaces.NamingContext)
     [java]   |   +- testDurableTopic (class: org.jboss.mq.SpyTopic)
     [java]   |   +- testTopic (class: org.jboss.mq.SpyTopic)
     [java]   |   +- securedTopic (class: org.jboss.mq.SpyTopic)
     [java]   +- console (class: org.jnp.interfaces.NamingContext)
     [java]   |   +- PluginManager (proxy: $Proxy27 implements interface org.jboss.console
.manager.PluginManagerMBean)
     [java]   +- UIL2ConnectionFactory[link -> ConnectionFactory] (class: javax.naming.
LinkRef)
     [java]   +- UILConnectionFactory[link -> ConnectionFactory] (class: javax.naming.L
inkRef)
     [java]   +- UUIDKeyGeneratorFactory (class: org.jboss.ejb.plugins.keygenerator.uuid.U
UIDKeyGeneratorFactory)
     [java] </pre>

2.3.3. 커맨드 라인을 사용하여 JMX에 액세스하기

JBoss는 원격 JMX 서버 인스턴스와 상호작용할 수 있도록 하는 간단한 커맨드 라인 도구를 제공합니다. 이 툴은 twiddle(JMX를 통해 비트 조작)이라고 불리며 배포되는 디렉터리의 bin에서 찾을 수 있습니다. Twiddle은 명령어로 실행되는 도구로 일반적인 커맨드 쉘은 아닙니다. twiddle.sh 또는 twiddle.bat 스크립트를 사용하여 실행시킬 수 있으며, 기본 사용법은 -h(--help) 인수를 넣어주면 볼 수 있고, --help-commands 를 사용하여 이 툴을 이용하여 어떤 일이 가능한지 살펴볼 수 있습니다:

C:\jboss-4.0.1RC2\bin>twiddle.bat -h
A JMX client to 'twiddle' with a remote JBoss server.

usage: twiddle [options] <command> [command_arguments]

options:
    -h, --help                Show this help message
        --help-commands       Show a list of commands
    -H=<command>              Show command specific help
    -c=command.properties     Specify the command.properties file to use
    -D<name>[=<value>]        Set a system property
    --                        Stop processing options
    -s, --server=<url>        The JNDI URL of the remote server
    -a, --adapter=<name>      The JNDI name of the RMI adapter to use
    -q, --quiet               Be somewhat more quiet
C:\jboss-4.0.1RC2\bin>twiddle.bat --help-commands
twiddle commands:
    info          Get the metadata for an MBean
    get           Get the values of one or more MBean attributes
    invoke        Invoke an operation on an MBean
    create        Create an MBean
    setattrs      Set the values of one or more MBean attributes
    unregister    Unregister one or more MBeans
    query         Query the server for a list of matching MBeans
    set           Set the value of one MBean attribute
    serverinfo    Get information about the MBean server
C:\jboss-4.0.1RC2\bin>
Cygwin으로 실행시킨 twiddle.sh.

Cygwin으로 실행시킨 twiddle.sh.

2.3.3.1. twiddle로 원격 서버 접속하기

기본적으로 twiddle 커맨드는 로컬호스트의 1099 포트에 접속하여 JMX 서버와 통신하는 커넥터인 RMIAdaptor서비스에 바인딩되는 디폴트 jmx/rmi/RMIAdaptor 를 찾게 됩니다. 다른 서버/포트 조합을 사용하려면 -s (--server) 옵션을 이용하면 됩니다:

[nr@rubik bin]$ ./twiddle.sh -s toki serverinfo -d jboss
[nr@rubik bin]$ ./twiddle.sh -s toki:1099 serverinfo -d jboss

다른 RMIAdaptor 바인딩을 사용하여 연결할 때는 -a (--adapter) 옵션을 사용하십시오:

[nr@rubik bin]$ ./twiddle.sh -s toki -a jmx/rmi/RMIAdaptor serverinfo -d jboss
[nr@rubik bin]$ ./twiddle.sh -s toki --adapter=jmx/rmi/RMIAdaptor serverinfo -d jboss

2.3.3.2. twiddle 커맨드 사용 예제

서버의 기본적인 정보에 액세스하려면, serverinfo 커맨드를 사용하십시오. 이 옵션은 현재 다음 사항들을 지원합니다:

[nr@toki bin]$ ./twiddle.sh -H serverinfo
Get information about the MBean server
 
usage: serverinfo [options]
 
options:
-d, --domain Get the default domain
-c, --count Get the MBean count
-l, --list List the MBeans
-- Stop processing options
 
[nr@rubik bin]$ ./twiddle.sh --server=toki serverinfo --count
385
[nr@rubik bin]$ ./twiddle.sh --server=toki serverinfo --domain
jboss

패턴과 매칭되는 MBeans의 이름에 대한 질의를 서버에 보내고 싶다면, query 커맨드를 사용하십시오. 현재 이 커맨드에서는 다음을 지원하고 있습니다:

[nr@rubik bin]$ ./twiddle.sh -H query
Query the server for a list of matching MBeans
 
usage: query [options] <query>
options:
    -c, --count    Display the matching MBean count
    --             Stop processing options
Examples:
 query all mbeans: query '*:*'
 query all mbeans in the jboss.j2ee domain: query 'jboss.j2ee:*'
[nr@rubik bin]$ ./twiddle.sh -s toki query 'jboss:service=invoker,*'
jboss:readonly=true,service=invoker,target=Naming,type=http
jboss:service=invoker,type=jrmp
jboss:service=invoker,type=httpHA
jboss:service=invoker,type=local
jboss:service=invoker,socketType=SSL,type=jrmp
jboss:service=invoker,type=pooled
jboss:service=invoker,type=http
jboss:service=invoker,target=Naming,type=http

MBean의 속성들을 보고싶다면, get 커맨드를 사용하십시오:

[nr@toki bin]$ ./twiddle.sh -H get
Get the values of one or more MBean attributes
 
usage: get [options] <name> [<attr>+]
  If no attribute names are given all readable attributes are retrieved
options:
    --noprefix    Do not display attribute name prefixes
    --            Stop processing options
[orb@toki bin]$ ./twiddle.sh get jboss:service=invoker,type=jrmp RMIObjectPort StateString
RMIObjectPort=4444
StateString=Started
[nr@toki bin]$ ./twiddle.sh get jboss:service=invoker,type=jrmp
ServerAddress=0.0.0.0
StateString=Started
State=3
EnableClassCaching=false
SecurityDomain=null
RMIServerSocketFactory=null
Backlog=200
RMIObjectPort=4444
Name=JRMPInvoker
RMIClientSocketFactory=null

MBean의 MBeanInfo를 질의하고자 할 때는 info 커맨드를 사용하십시오:

[nr@toki bin]$ ./twiddle.sh -H info
Get the metadata for an MBean
 
usage: info <mbean-name>
  Use '*' to query for all attributes
[nr@toki bin]$ ./twiddle.sh info jboss:service=invoker,type=jrmp
Description: Management Bean.
+++ Attributes:
 Name: ServerAddress
 Type: java.lang.String
 Access: rw
 Name: StateString
 Type: java.lang.String
 Access: r-
 Name: State
 Type: int
 Access: r-
 Name: EnableClassCaching
 Type: boolean
 Access: rw
 Name: SecurityDomain
 Type: java.lang.String
 Access: rw
 Name: RMIServerSocketFactory
 Type: java.lang.String
 Access: rw
 Name: Backlog
 Type: int
 Access: rw
 Name: RMIObjectPort
 Type: int
 Access: rw
 Name: Name
 Type: java.lang.String
 Access: r-
 Name: RMIClientSocketFactory
 Type: java.lang.String
 Access: rw
+++ Operations:
 void start()
 void jbossInternalLifecycle(java.lang.String java.lang.String)
 void destroy()
 void create()
 void stop()
 

MBean의 오퍼레이션을 호출하고자 할 때는 invoker 커맨드를 사용하십시오:

 [nr@toki bin]$ ./twiddle.sh -H invoke
Invoke an operation on an MBean
 
usage: invoke [options] <query> <operation> (<arg>)*

options:
    -q, --query-type[=<type>]    Treat object name as a query
    --                           Stop processing options

query type:
    f[irst]    Only invoke on the first matching name [default]
    a[ll]      Invoke on all matching names
[nr@toki bin]$ 
<h1>java: Namespace</h1>
<pre>
  +- XAConnectionFactory (class: org.jboss.mq.SpyXAConnectionFactory)
  +- DefaultDS (class: org.jboss.resource.adapter.jdbc.WrapperDataSource)
  +- SecurityProxyFactory (class: org.jboss.security.SubjectSecurityProxyFactory)
  +- DefaultJMSProvider (class: org.jboss.jms.jndi.JNDIProviderAdapter)
  +- comp (class: javax.naming.Context)
  +- JmsXA (class: org.jboss.resource.adapter.jms.JmsConnectionFactoryImpl)
  +- ConnectionFactory (class: org.jboss.mq.SpyConnectionFactory)
  +- jaas (class: javax.naming.Context)
  |   +- JmsXARealm (class: org.jboss.security.plugins.SecurityDomainContext)
  |   +- jbossmq (class: org.jboss.security.plugins.SecurityDomainContext)
  |   +- HsqlDbRealm (class: org.jboss.security.plugins.SecurityDomainContext)
  +- timedCacheFactory (class: javax.naming.Context)
Failed to lookup: timedCacheFactory, errmsg=null
  +- TransactionPropagationContextExporter (class: org.jboss.tm.TransactionPropagationCont
extFactory)
  +- Mail (class: javax.mail.Session)
  +- StdJMSPool (class: org.jboss.jms.asf.StdServerSessionPoolFactory)
  +- TransactionPropagationContextImporter (class: org.jboss.tm.TransactionPropagationCont
extImporter)
  +- TransactionManager (class: org.jboss.tm.TxManager)
</pre>
<h1>Global JNDI Namespace</h1>
<pre>
  +- HAILConnectionFactory[link -> ConnectionFactory] (class: javax.naming.LinkRef)
  +- jmx (class: org.jnp.interfaces.NamingContext)
  |   +- invoker (class: org.jnp.interfaces.NamingContext)
  |   |   +- RMIAdaptor (proxy: $Proxy26 implements interface org.jboss.jmx.adaptor.rmi.RM
IAdaptor,interface org.jboss.jmx.adaptor.rmi.RMIAdaptorExt)
  |   +- rmi (class: org.jnp.interfaces.NamingContext)
  |   |   +- RMIAdaptor[link -> jmx/invoker/RMIAdaptor] (class: javax.naming.LinkRef)
  +- HTTPXAConnectionFactory (class: org.jboss.mq.SpyXAConnectionFactory)
  +- ConnectionFactory (class: org.jboss.mq.SpyConnectionFactory)
  +- UserTransactionSessionFactory (proxy: $Proxy13 implements interface org.jboss.tm.user
tx.interfaces.UserTransactionSessionFactory)
  +- HTTPConnectionFactory (class: org.jboss.mq.SpyConnectionFactory)
  +- XAConnectionFactory (class: org.jboss.mq.SpyXAConnectionFactory)
  +- invokers (class: org.jnp.interfaces.NamingContext)
  |   +- 0.0.0.0 (class: org.jnp.interfaces.NamingContext)
  |   |   +- pooled (class: org.jboss.invocation.pooled.interfaces.PooledInvokerProxy)
  +- UserTransaction (class: org.jboss.tm.usertx.client.ClientUserTransaction)
  +- UILXAConnectionFactory[link -> XAConnectionFactory] (class: javax.naming.LinkRef)
  +- HAILXAConnectionFactory[link -> XAConnectionFactory] (class: javax.naming.LinkRef)
  +- UIL2XAConnectionFactory[link -> XAConnectionFactory] (class: javax.naming.LinkRef)
  +- queue (class: org.jnp.interfaces.NamingContext)
  |   +- A (class: org.jboss.mq.SpyQueue)
  |   +- testQueue (class: org.jboss.mq.SpyQueue)
  |   +- ex (class: org.jboss.mq.SpyQueue)
  |   +- DLQ (class: org.jboss.mq.SpyQueue)
  |   +- D (class: org.jboss.mq.SpyQueue)
  |   +- C (class: org.jboss.mq.SpyQueue)
  |   +- B (class: org.jboss.mq.SpyQueue)
  +- topic (class: org.jnp.interfaces.NamingContext)
  |   +- testDurableTopic (class: org.jboss.mq.SpyTopic)
  |   +- testTopic (class: org.jboss.mq.SpyTopic)
  |   +- securedTopic (class: org.jboss.mq.SpyTopic)
  +- console (class: org.jnp.interfaces.NamingContext)
  |   +- PluginManager (proxy: $Proxy27 implements interface org.jboss.console.manager.Plu
ginManagerMBean)
  +- UIL2ConnectionFactory[link -> ConnectionFactory] (class: javax.naming.LinkRef)
  +- UILConnectionFactory[link -> ConnectionFactory] (class: javax.naming.LinkRef)
  +- UUIDKeyGeneratorFactory (class: org.jboss.ejb.plugins.keygenerator.uuid.UUIDKeyGenera
torFactory)
</pre>

2.3.4. 임의의 프로토콜을 사용하여 JMX에 연결하기

분리된 인보커(invoker)와 일반화된 프록시 팩토리 기능은 여러분이 InvokerAdaptorService를 사용하여 JMX 서버에 통신할 수 있게 하고 프록시 팩토리 서비스를 통해 여러분이 선택한 프로토콜로 RMIAdaptor나 이와 유사한 인터페이스를 노출시킬 수 있습니다. 우리는 2.7 절, “분리된 Invoker를 사용하여 원격으로 서비스 액세스하기”에서 프록시 팩토리를 사용하여 분리된 invoker 사용에 대해 소개할 것입니다. 프록시 팩토리 서비스가 존재하는 RMIAdaptor 인터페이스를 임의의 프로토콜을 사용하여 MBean 서버에 액세스할 수 있도록 하는 invoker 서비스의 예를 2.7.1 절, “분리된 Invoker 예제, MBeanServer Invoker 아답터 서비스”에서 살펴보실 수 있습니다.

2.4. Microkernel로써 JMX 사용하기

JBoss를 시작시키면 제일 먼저 수행되는 것중에 하나가 MBean 서버 인스턴스(javax.management.MBeanServer)를 생성하는 것입니다. JBoss 아키텍쳐내에서 JMX MBean 서버는 microkernel 역할을 담당합니다. 다른 모든 관리가능한 MBean 컴포넌트들은 MBean 서버와 함께 등록되어 JBoss내에 플러그됩니다. 이러한 관점에서 커널은 단지 프레임워크일 뿐 실제 기능의 근원은 아닙니다. 기능성은 MBeans에 의해 제공되며 실제로 모든 주요한 JBoss 컴포넌트들은 MBean 서버를 통해 서로 연결되어진 관리가능한 MBeans입니다.

2.4.1. 구동(Startup) 프로세스

이번 섹션에서 우리는 JBoss 서버의 구동 프로세스에 대하여 알아보도록 하겠습니다. JBoss 서버를 시작시키는 과정에서 발생되는 순차적인 단계를 요약해보면 다음과 같습니다:

  1. JBoss 구동 스크립트에서는 org.jboss.Main.main(String[]) 메쏘드 엔트리 포인트를 사용하여 부트 시퀀스를 초기화하게 됩니다.
  2. Main.main 메쏘드는 jboss라는 이름을 갖는 쓰레드 그룹을 만든 후 이 쓰레드 그룹에 속하는 쓰레드를 시작합니다. 이 쓰레드는 Main.boot 메쏘드를 호출합니다.
  3. Main.boot 메쏘드는 Main.main 인자들을 처리하고 인자로써 할당된 추가적인 속성이 있을 경우 시스템 속성을 사용하여 org.jboss.system.server.ServerLoader를 생성합니다.
  4. XML 파서 라이브러리, jboss-jmx.jar, concurrent.jar 그리고 추가 라이브러리들과 인수로 주어지는 클래스경로들은 ServerLoader와 함께 등록됩니다.
  5. JBoss 서버 인스턴스는 ClassLoader 인수로 넘겨받은 현재 쓰레드 컨텍스트 클래스 로더와 함께 ServerLoader.load(ClassLoader) 메쏘드를 사용하여 생성됩니다. 반환된 서버 인스턴스는 org.jboss.system.server.Server 인터페이스의 수행부입니다. 서버 인스턴스의 생성에 대한 상세한 내용은 다음과 같습니다:

    • jars의 URL들과 ServerLoader와 함께 등록된 디렉터리들을 갖는 java.net.URLClassLoader를 생성 . 이 URLClassLoader는 자신의 부모로써 넘겨진 ClassLoader를 사용하며, 쓰레드 컨텍스트 클래스 로더의 역할을 담당하게 됩니다.
    • 사용할 Server 인터페이스의 수행부 클래스 이름은 jboss.server.type 속성으로 결정됩니다. 기본값은 org.jboss.system.server.ServerImpl 입니다.
    • Server 수행 클래스는 6번째 단계에서 만들어진 URLClassLoader를 사용하여 로딩되며, 인수없는 생성자(constructor)를 사용하여 초기화됩니다. ServerLoader.load 메쏘드의 엔트리로 존재하는 쓰레드 컨텍스트 클래스 로더는 저장되고 서버 인스턴스가 반환됩니다.
  6. 서버 인스턴스는 Server.init(Properties) 메쏘드를 사용하여 ServerLoader 생성자에 넘겨진 속성들로 초기화됩니다.
  7. 그런 다음 서버 인스턴스는 Server.start() 메쏘드를 사용하여 시작됩니다. 기본 수행은 다음과 같은 절차를 따릅니다:

    • 쓰레드 컨텍스트 클래스 로더를 ServerImpl 클래스를 로드하는데 사용한 클래스 로더로 설정합니다.
    • MBeanServerFactory.createMBeanServer(String) 메쏘드를 사용하여 jboss 도메인아래에 하나의 MBeanServer를 생성합니다.
    • MBean 서버와 함께 ServerImplServerConfigImpl MBeans를 등록합니다.
    • 서버 환경설정 파일 conf 디렉토리(server/default/conf)와 옵션으로 설정한 패치 디렉토리내의 모든 JARs를 포함하는 통합된(unified) 클래스 로더 레포지터리를 초기화합니다. 각각의 JAR와 디렉터리에 대한 org.jboss.mx.loading.UnifiedClassLoader가 생성되고 통합된 레포지터리와 함께 등록됩니다. 그런 후, UnifiedClassLoader들중에 하나가 쓰레드 컨텍스트 클래스 로더로 설정됩니다. 이 쓰레드 컨텍스트 클래스 로더를 통해 모든 사용가능한 UnifiedClassLoader들을 효과적으로 만들게 됩니다.
    • org.jboss.system.ServiceController MBean이 만들어집니다. ServiceController은 JBoss MBean 서비스의 생명주기(life cycle)를 관리합니다. 우리는 JBoss MBean 서비스의 표기를 2.4.2 절, “JBoss MBean 서비스”에서 보다 상세하게 다룰 것입니다.
    • org.jboss.deployment.MainDeployer가 생성되어 시작됩니다. MainDeployer는 배치 종속성을 관리하고 올바른 배치자(deployer)로 직접 배치시킵니다.
    • org.jboss.deployment.JARDeployer가 생성되고 시작됩니다. JARDeployer는 간단한 라이브러리 JARs의 배치를 처리합니다.
    • org.jboss.deployment.SARDeployer가 만들어지고 시작됩니다. SARDeployer는 JBoss MBean 서비스의 배치를 처리합니다.
    • MainDeployer는 현재의 서버 파일 세트의 conf/jboss-service.xml에 정의된 서비스들을 배치하기 위해 호출됩니다.
    • 쓰레드 컨텍스트 클래스 로더를 저장합니다.

JBoss 서버는 시작되면서 JMX MBean서버를 위한 컨테이너 그 이상의 역할을 하지 않으며 명령어에서 넘겨주는 서버의 이름이 붙은 환경 세트를 기반으로 해당 서버의 jboss-service.xml MBean 환경설정 파일에 정의되어진 서비스들에 따라 그 성격이 정해지게 됩니다. MBean들로부터 JBoss 서버 인스턴스의 기능들이 정의되기 때문에, JBoss MBeans의 핵심이 어떻게 작성되는지를 이해하는것과 MBeans를 통해 JBoss에 기존 서비스들을 통합시켜주는 방법을 알아야하는 것은 매우 중요합니다. 이것은 다음장에서 다룰 주제들입니다.

2.4.2. JBoss MBean 서비스

우리가 지금까지 함께 다루어왔던 바대로 JBoss는 서버 인스턴스의 성격을 형성해주는 MBean 서비스들내로 로딩시켜주기 위해 JMX에 의존하게 됩니다. 표준 JBoss 배포판에서 제공하는 번들된 기능 모두는 MBeans에 기반을 두고 있습니다. 새로운 서비스들을 JBoss 서버에 추가하는 가장 좋은 방법은 여러분이 직접 JMX MBeans를 만드는 것입니다.

MBeans에는 두개의 클래스가 있습니다: 이들은 JBoss 서비스에 독립적인것과 종속적인 것입니다. JBoss 서비스에 독립적인 MBeans은 사소한 경우들입니다. 이들은 각각 JMX 스펙에 따라 작성되고 deploy/user-service.xml 파일에 mbean 태그와 함께 추가되어 JBoss 서버에 추가되어집니다. 네이밍과 같은 JBoss 서비스에 의존적인 MBean을 작성하기 위해서는 JBoss 서비스 패턴을 따라야만 합니다. JBoss MBean 서비스 패턴은 상태 변화 통지를 제공해주는 생명 주기 오퍼레이션의 세트로 구성되어 있습니다. 통지를 통해 MBean서비스는 언제 생성되고, 시작, 종료 및 소멸되었는지 알 수 있게 됩니다. MBean 서비스의 생명 주기를 관리하는것은 3개의 JBoss MBeans에서 맡습니다:SARDeployer, ServiceConfigurator 그리고 ServiceController.

2.4.2.1. SARDeployer MBean

JBoss는 자신의 MBean 서비스들의 배치를 표준 JMX MLet 환경설정파일의 XML 검증을 로드하는 커스텀 MBean을 통해 관리합니다. 커스텀 MBean은 org.jboss.deployment.SARDeployer 클래스에서 수행됩니다. SARDeployer MBean은 JBoss가 부트스트랩 프로세스의 일부분으로 구동될 때 로드되어집니다. SAR가 뜻하는 것은 service archive입니다.

SARDeployer는 서비스 아키이브를 처리합니다. 하나의 서비스 아카이브는 확장자 .sar로 끝나며 META-INF/jboss-service.xml 서술자(descriptor)를 포함하거나 혹은 *-service.xml 형태의 이름을 갖는 독립형(standalone) XML 서술자를 갖는 jar가 될 수 있습니다. 서비스 서술자의 DTD는 그림 2.16, “SARDeployer로 파싱되는 MBean 서비스 서술자의 DTD”를 참조하십시오.

SARDeployer로 파싱되는 MBean 서비스 서술자의 DTD

그림 2.16. SARDeployer로 파싱되는 MBean 서비스 서술자의 DTD

DTD의 요소들은 다음과 같습니다:

  • server/loader-repository: 이 요소에는 sar내에 배치된 클래스의 SAR 레벨 스코프를 제공하는 SAR를 위해 사용하는 UnifiedLoaderRepository MBean의 이름을 지정합니다.주어질 이름은 고유한 JMX ObjectName 문자열입니다. 또한 loader-repository-config 요소를 포함하여 임의의 설정을 지정해줄 수도 있습니다. 옵션으로 사용할 수 있는 loaderRepositoryClass 속성으로 로더의 레포지터리 수행 클래스의 완전한 경로 이름을 지정합니다. 기본값은 org.jboss.mx.loading.HeirachicalLoaderRepository3 입니다.
  • server/loader-repository/loader-repository-config: 이 옵션 요소에서는 loadRepositoryClass 설정에 사용할 수 있는 임의의 환경설정을 지정합니다. loader-repository-config 컨텐츠를 파싱하기 위해 사용되는 org.jboss.mx.loading.LoaderRepositoryFactory.LoaderRepositoryConfigParser 수행부의 완전한 경로 이름을 옵션으로 configParserClass 속성에서 줄 수 있습니다.
  • server/local-directory: 이 요소에는 MBean에서 사용하도록 server/<config>/db 디렉터리에 복사되어야만 하는 배치 아카이브내의 경로를 지정합니다. 경로 속성은 배치 아카이브내의 엔트리 이름입니다.
  • server/classpath: 이 요소에는 MBean(s)과 함께 배치되어야하는 하나 이상의 외부 JARs를 지정합니다. 옵션으로 제공되는 아카이브들의 속성에는 로드되어질 JAR 이름들의 항목을 컴마로 분리시켜 표시해주거나 로딩되어야하는 모든 jars를 의미하는 * 와일드 카드를 사용합니다. 와일드 카드는 파일 URLs과 함께 사용해야만 적용되며 WEBDAV 프로토콜을 지원하는웹 서버에서는 http URLs도 사용할 수 있습니다. codebase 속성에는 로딩되어져야하는 아카이브 속성내의 JARs가 지정된 URL을 설정합니다. codebase가 URL 문자열이 아닌 경로일 경우, 완전한 URL은 JBoss 배포판의 server/<config> 디렉터리의 상대적 경로로써 codebase 값으로 처리되게 됩니다. 아카이브내에 명시된 JARs의 순서뿐만 아니라 멀티 클래스경로 요소내의 순서까지도 JARs의 클래스경로 순서를 사용합니다. 따라서 패치를 하거나 순서대로 정렬될 필요가 있는 클래스들의 올바르지 않은 버전의 경우, 이 기능을 사용하여 올바른 순서가 되도록 합니다. codebase와 아카이브 속성 값 모두는 시스템 속성 x를 대체할 수 있도록 ${x} 패턴을 사용하여 시스템 속성을 참조할 수 있습니다.
  • server/mbean: 이 요소에는 MBean 서비스를 지정합니다. 필요한 코드 속성에는 MBean 수행부 클래스의 완전한 경로 이름이 주어집니다. 필요한 이름 속성에는 MBean의 JMX ObjectName이 주어집니다. 옵션으로 사용되는 xmbean-dd 속성에는 MBean 서비스가 모델 MBean 관리 인터페이스를 정의하는 JBoss XMbean 서술자를 사용할 때 XMBean 리소스의 경로를 지정합니다.
  • server/mbean/attribute: 각각의 attribute 요소에는 이름/값 쌍으로 구성된 Mbean의 attribute를 지정합니다. attribute의 이름은 이름 attribute으로 주어지고 attribute 요소의 바디에 값이 주어집니다. 바디는 값의 문자열 혹은 MBean attribute의 타입이 org.w3c.dom.Element인 경우 임의의 요소와 자식 요소로 표현될 수 있습니다. 문자열 값인 경우, 문자는 JavaBean java.beans.PropertyEditor 메커니즘을 사용하여 attribute 타입으로 변환됩니다.

    attribute의 문자 값은 ${x} 패턴을 사용하여 시스템 속성 x를 참조할 수 있습니다. 이럴 경우, attribute의 값은 System.getProperty("x")의 결과이거나 이에 해당되는 속성이 존재하지 않을 때는 널이 될 수 있습니다.

  • server/mbean/dependsserver/mbean/depends-list: 이 요소들에는 depends 혹은 depends-list 요소에 의해 명명된 MBean(s)에서 사용되는 요소에서 MBean과의 의존성을 지정합니다. Section 2.4.2.4, “서비스 의존성 지정하기”. 의존성 값은 중첩된 mbean을 정의하는 또 다른 mbean 요소일 수 있다는 것에 주의하십시오.

SARDeployer가 서비스를 배치하도록 요구받은 경우 다단계로 수행합니다. 그림 2.17, “시퀀스 다이어그램에서 JBoss MBean 서비스를 구동하기 위해 SARDeployer로 수행되는 메인 액티비티” 는 서비스의 시작 상태를 통해 초기화되는 과정을 보여주는 시퀀스 다이어그램입니다.

시퀀스 다이어그램에서 JBoss MBean 서비스를 구동하기 위해 SARDeployer로 수행되는 메인 액티비티

그림 2.17. 시퀀스 다이어그램에서 JBoss MBean 서비스를 구동하기 위해 SARDeployer로 수행되는 메인 액티비티

그림 2.17, “시퀀스 다이어그램에서 JBoss MBean 서비스를 구동하기 위해 SARDeployer로 수행되는 메인 액티비티”에서는 다음 사항들이 표시되어 있습니다:

  • 1.1로 시작되는 메쏘드들은 XML 서비스 서술자의 로드와 파싱에 대응됩니다.
  • 1.2로 시작되는 메쏘드들은 unified 로더 레포지터리와 함께 등록된 UnifiedClassLoader를 통해 사용가능한 jar나 디렉터리를 만들어주는 독립적인 배치를 생성하는 서비스 서술자에서 각각의 클래스경로 요소 처리에 대응됩니다.
  • 1.3으로 시작되는 메쏘드들은 서비스 서술자에서 각각의 local-directory 요소 처리에 대응됩니다. 이것은 server/<config>/db 디렉터리로 경로 속성에서 지정한 SAR 요소의 사본입니다.
  • 메쏘드 1.4. 자식 배치가 생성되고 서비스 배치 정보 서브배치 목록에 추가되는 서비스내에 중첩된 배치가능한 각각의 유닛을 처리합니다.
  • Method 2.1. SAR 배치 유닛의 UnifiedClassLoader가 MBean 서버와 함께 등록되므로 SAR MBeans의 로딩에 사용될 수 있습니다.
  • Method 2.2. 서술자내의 각각의 MBean 요소들에 대해 인스턴스를 생성하고 서비스 서술자에서 지정한 attributes 값으로 초기화합니다. 이러한 작업은 ServiceController.install 메쏘드를 호출함으로써 이루어지게 됩니다.
  • Method 2.4.1. 생성된 각각의 MBean 인스턴스에 대해 JMX ObjectName을 얻고 ServiceController에게 서비스 생명 주기의 단계를 생성할 수 있도록 요청합니다. ServiceController은 MBean 서비스의 의존성을 처리합니다. 서비스의 의존성을 만족할때에만 서비스 생성 메쏘드가 호출됩니다.
  • 3.1로 시작되는 메쏘드들은 서비스 서술자내에 정의되어 있는 각각의 MBean 서비스 시작과 대응됩니다. 생성된 각각의 MBean 인스턴스에 대해 JMX ObjectName을 얻고, ServiceController가 서비스 생명 주기의 시작 단계를 처리하도록 요청합니다. ServiceController는 MBean 서비스의 의존성을 처리합니다. 서비스의 의존성을 만족할때에만 서비스 생성 메쏘드가 호출됩니다.

2.4.2.2. 서비스 생명주기(Life Cycle) 인터페이스

JMX 스펙 사양서에는 MBeans를 위한 생명 주기나 의존성 관리에 대한 어떠한 타입 정의도 하지 않았습니다. JBoss ServiceController MBean에서 이 표기를 도입하였습니다. JBoss MBean은 자신이 서비스 수행의 생명주기로부터 생성을 완화시켜주는 기대를 받는 JMX MBean의 확장입니다. 이것은 어떠한 타입에 대해서도 의존성 관리를 수행하기 위해 필요합니다. 가령, 동작이 가능한 JNDI 네이밍 서비스를 필요로 하는 MBean을 만들고자 할 경우, 여러분의 MBean에서는 의존성이 충족되는지를 알려줄 필요가 있습니다. MBean 생성자에서만 생명 주기가 일어나게 될 경우, 이러한 관리는 불가능하다는 장애때문에 JBoss에서는 서비스 자신의 비헤이비어를 관리할 수 있는 이벤트를 설명하는 서비스의 생명 주기 인터페이스를 도입하였습니다. 다음 코드에서는 org.jboss.system.Service 인터페이스를 보여주고 있습니다:

package org.jboss.system;

public interface Service
{
    public void create() throws Exception;
    public void start() throws Exception;
    public void stop();
    public void destroy();
}

ServiceController MBean은 서비스 생명 주기중 적절한 시기에 Service 인터페이스의 메쏘드를 호출합니다. 우리는 ServiceController 섹션에서 이 메쏘드를 보다 상세하게 다룰 것입니다.

시작/종료 라이프사이클 표기를 포함하는 상태 관리 표기를 소개하는 J2EE 관리 스펙 사양 요청서(JSR 77, http://jcp.org/jsr/detail/77.jsp)에 주의해야합니다. 이 표준이 JBoss에 적용 완료될 경우 JSR 77 기반의 서비스 라이프사이클 수행의 확장까지도 지원될 것이기 때문입니다. 3.2.0 릴리즈부터 우리는 JSR77 관리 객체와 통계에 대한 전반적인 사항들을 지원하고 있지만 라이프사이클 오퍼레이션은 지원하지 않았습니다.

2.4.2.3. ServiceController MBean

JBoss는 org.jboss.system.ServiceController 커스텀 MBean을 통해 MBeans 에서의 종속성을 관리합니다. SARDeployer는 MBean 서비스들의 초기화, 생성, 구동, 정지 및 소멸 시점에서 ServiceController에 위임됩니다. 그림 2.18, “서비스 시작을 위한 SAPDeployer와 ServiceController사이의 상호작용” 에서는 SARDeployerServiceController 사이에서의 중요한 상호작용에 대한 시퀀스 다이어그램을 보여주고 있습니다.

서비스 시작을 위한 SAPDeployer와 ServiceController사이의 상호작용

그림 2.18. 서비스 시작을 위한 SAPDeployer와 ServiceController사이의 상호작용

ServiceController MBean은 서비스 생명주기의 관리를 위해 4개의 핵심 메쏘드를 갖고 있습니다: create, start, stop and destroy.

2.4.2.3.1. create(ObjectName) 메쏘드

create(ObjectName) 메쏘드는 이름이 붙여진 서비스의 상태에 영향을 미치는 이벤트가 발생할때마다 호출됩니다. 이것은 새로운 클래스의 통지(notification)나 또 다른 서비스가 자신의 상태가 생성되어질 때 SARDeployer의 명백한 호출(explicit invocation)에 의해 트리거될 수 있습니다.

서비스의 create 메쏘드가 호출되면, 이에 종속된 모든 서비스들은 각자 자신의 create 메쏘드 호출을 하게 됩니다. 따라서 MBean은 필요한 MBean들 혹은 리소스들이 존재하는지를 점검할 수 있게 됩니다. 대부분의 JBoss MBean 서비스들이 자신의 start 메쏘드를 통해 구동되기전까지는 완전한 기능을 하지 않는 것 처럼 서비스는 이 단계에서 다른 MBean 서비스들을 활성화시킬 수 없습니다. 이 때문에 서비스 도구(implementations)는 종종 단지 start 메쏘드만으로 create를 실행하지 않습니다. 이는 서비스가 완전한 기능을 할 수 있는 첫 시점이기 때문입니다.

2.4.2.3.2. start(ObjectName) 메쏘드

start(ObjectName) 메쏘드는 이름이 붙여진 서비스의 상태에 영향을 미치는 이벤트가 발생될 때마다 호출됩니다. 이 메쏘드는 새로운 클래스의 통지나 다른 서비스가 자신의 상태가 구동되어질 때 SARDeployer의 명백한 호출(explicit invocation)에 의해 트리거될 수 있습니다.

서비스의 start 메쏘드가 호출될 때, 서비스에 의존성을 가진 모든 서비스들은 자신들의 start 메쏘드가 호출됩니다. start 메소드 호출 시그널을 수신한 서비스는 관련된 서비스들이 생성되고 구동된 후 비로서 완전한 기능을 발휘할 수 있습니다.

2.4.2.3.3. stop(ObjectName) 메쏘드

stop(ObjectName) 메쏘드는 이름이 붙여진 서비스의 상태에 영향을 미치는 이벤트가 발생될 때마다 호출됩니다. 이 메쏘드는 새로운 클래스의 통지나 다른 서비스가 자신의 상태가 정지될 때 SARDeployer의 명백한 호출(explicit invocation)에 의해 트리거될 수 있습니다.

2.4.2.3.4. destroy(ObjectName) 메쏘드

destroy(ObjectName) 메쏘드는 이름이 붙여진 서비스의 상태에 영향을 미치는 이벤트가 발생될 때마다 호출됩니다. 이 메쏘드는 클래스 제거 통지나 다른 서비스가 자신의 상태가 소멸될 때 SARDeployer의 명백한 호출(explicit invocation)에 의해 트리거될 수 있습니다.

서비스 도구(implementation)는 종종 단순히 stop 메소드 혹은 stopdestroy 중 어느 하나의 실행만으로는 서비스의 상태가 없거나 리소스를 깨끗히 정리해야할 경우, destroy를 수행하지 않습니다.

2.4.2.4. 서비스 종속성 지정하기

여러분이 하나의 MBean 서비스가 다른 MBean 서비스들과 갖게되는 의존성을 지정해주기 위해서는 서비스 서술자(descriptor)의 mbean 요소내에 종속성을 선언해줄 필요가 있습니다. 이를 위해 dependsdepends-list 요소를 사용합니다. 이 두개의 요소사이의 차이점은 optional-attribute-name 속성의 사용방법에 있습니다. 단일 값을 갖는 속성을 사용하여 ObjectNames의 종속성을 살펴볼 경우에는 depends 요소를 사용해야만 합니다. java.util.List 호환 속성을 사용하여 ObjectNames의 종속성을 살펴보려 한다면, depends-list 요소를 사용해야 할 것입니다. 만약 여러분이 의존성만 지정하고 여러분의 MBean의 속성에 연결되어 있는 관련된 서비스 ObjectName에는 관심이 없다면, 사용하기 쉬운 요소라면 어떤 것을 쓰던 개의치는 않습니다. 다음에 보여드리는 코드는 의존성에 관련된 요소들의 사용방법을 보여주기 위한 서비스 서술자의 일부분입니다.

<mbean code="org.jboss.mq.server.jmx.Topic"
       name="jms.topic:service=Topic,name=testTopic">
    <!-- Declare a dependency on the "jboss.mq:service=DestinationManager" and
         bind this name to the DestinationManager attribute -->
    <depends optional-attribute-name="DestinationManager">
        jboss.mq:service=DestinationManager 
    </depends>

    <!-- Declare a dependency on the "jboss.mq:service=SecurityManager" and
         bind this name to the SecurityManager attribute -->
    <depends optional-attribute-name="SecurityManager">
        jboss.mq:service=SecurityManager
    </depends>

    <!-- ... -->

    <!-- Declare a dependency on the
         "jboss.mq:service=CacheManager" without
         any binding of the name to an attribute-->
    <depends>jboss.mq:service=CacheManager</depends>
</mbean>

<mbean code="org.jboss.mq.server.jmx.TopicMgr" 
       name="jboss.mq.destination:service=TopicMgr">
    <!-- Declare a dependency on the given topic destination mbeans and
         bind these names to the Topics attribute -->
    <depends-list optional-attribute-name="Topics">
        <depends-list-element>jms.topic:service=Topic,name=A</depends-list-element>
        <depends-list-element>jms.topic:service=Topic,name=B</depends-list-element>
        <depends-list-element>jms.topic:service=Topic,name=C</depends-list-element>
    </depends-list>
</mbean>

dependsdepends-list 요소사이의 또 다른 다른 차이점은 depends 요소의 값에 서비스의 ObjectName 만을 주기보단 완전한 MBean 서비스 환경이 와줘야 된다는 것입니다. 예제 2.15, “depends 요소를 사용하여 서비스에 종속된 완전한 환경을 설정해주는 예제.”에서는 hsqldb-service.xml 서술자를 위해 제공되는 예제입니다. 이 예제에서 org.jboss.resource.connectionmanager.RARDeployment 서비스 환경은 depends 요소의 값으로 중첩된 mbean 요소를 사용해 정의합니다. jboss.jca:service=LocalTxDS,name=hsqldbDS ObjectNameLocalTxConnectionManager 클래스의 ManagedConnectionFactoryName 속성에 연결되어 질 것입니다.

예제 2.15. depends 요소를 사용하여 서비스에 종속된 완전한 환경을 설정해주는 예제.

<mbean code="org.jboss.resource.connectionmanager.LocalTxConnectionManager" 
       name="jboss.jca:service=LocalTxCM,name=hsqldbDS">
    <depends optional-attribute-name="ManagedConnectionFactoryName">
        <!--embedded mbean-->
        <mbean code="org.jboss.resource.connectionmanager.RARDeployment" 
               name="jboss.jca:service=LocalTxDS,name=hsqldbDS">
            <attribute name="JndiName">DefaultDS</attribute>
            <attribute name="ManagedConnectionFactoryProperties">
                <properties>
                    <config-property name="ConnectionURL"
                                     type="java.lang.String">    
                        jdbc:hsqldb:hsql://localhost:1476
                    </config-property>
                    <config-property name="DriverClass" type="java.lang.String">
                        org.hsqldb.jdbcDriver
                    </config-property>
                    <config-property name="UserName" type="java.lang.String">
                        sa
                    </config-property>
                    <config-property name="Password" type="java.lang.String"/>
                </properties>
            </attribute>
            <!-- ... -->
        </mbean>
    </depends>
    <!-- ... -->
</mbean>

2.4.2.5. 불충분한 의존성들의 식별

ServiceController MBean은 의존성이 불충분하기 때문에 동작하지 않는 MBeans가 어떤 것인지를 디버깅하는 것을 도와주는 2개의 오퍼레이션을 지원합니다. 첫 번째 오퍼레이션은 listIncompletelyDeployed입니다. 이 오퍼레이션은 RUNNING 상태에 있지 않은 MBean 서비스들의 org.jboss.system.ServiceContext 객체의 java.util.List를 리턴합니다. 이 오퍼레이션은 코드 속성을 통해 지정한 클래스가 사용 가능하지 않기 때문에 배치될 수 없는 MBean 서비스들의 JMX ObjectName들의 java.util.List를 반환하게 됩니다.

2.4.2.6. 컴포넌트의 Hot Deployment, URLDeploymentScanner

URLDeploymentScanner MBean 서비스는 JBoss에 hot deployment 기능을 제공합니다. 이 서비스는 하나 이상의 배치가능한 아카이브 URL들을 살펴보다가 아카이브가 발견되거나 변경된 경우 배치시켜주게 됩니다. 이미 배치되어 있던 어플리케이션이 제거된 경우 배치된 어플리케이션의 제거 또한 담당합니다. 환경 속성은 다음과 같습니다:

  • URLs: 변경사항들 관찰할 수 있는 위치에 대한 URL 문자열들이 콤마로 구분되는 목록입니다. 올바르지 못한 URL일 경우 파일 경로로 처리됩니다. 상대적 파일 경로는 서버의 홈 URL을 기준으로 해석됩니다. 즉, 기본 환경 파일 셋트인 JBOSS_DIST/server/default가 되는 것입니다. URL이 파일을 나타날 경우에는 파일의 배치이후의 갱신이나 제거과정이 관찰되게 됩니다. 디렉터리를 나타내기 위해 URL을 /로 끝내는 경우, 디렉터리내의 컨텐츠들은 배치될 수 있는 컬렉션으로 다루어지게 되며, 갱신 혹은 삭제 과정이 관찰되는 컨텐츠가 됩니다. 디렉터리를 식별할 수 있도록 끝에 / 를 붙인 URL의 구성조건은 RFC2518 컨벤션을 준수하며, 아카이브를 그냥 풀은 디렉터리들과 컬렉션사이의 식별을 허용하게 하는 것입니다.

    URLs 속성의 디폴트 값은 deploy/ 이며, 이것이 의미하는 것은 server/<name>/deploy 디렉터리에 들어가는 어떠한 SARs, EARs, JARs, WARs, RARs 등등도 자동적으로 배치되고 갱신에 대한 관찰이 된다는 것입니다.

    ULSs에 포함되는 것들의 예:

    • "deploy/"는 ${jboss.server.url}/deploy/를 스캔하는여, 이는 서버를 부트시킨 URL에 따라 로컬 혹은 리모트가 될 수 있습니다.
    • "${jboss.server.home.dir}/deploy/"는 ${jboss.server.home.dir)/deploy를 스캔하며, 여기서는 항상 로컬이 됩니다.
    • "file:/var/opt/myapp.ear"는 로컬에 있는 myapp.ear을 배치합니다.
    • "file:/var/opt/apps/"는 지정한 디렉터리를 스캔합니다.
    • "http://www.test.com/netboot/myapp.ear" 은 리모티 위치에 있는 myapp.ear을 배치합니다.
    • "http://www.test.com/netboot/apps/"은 WebDAV를 통해 지정된 리모트 위치를 스캔합니다. 이는 리모트 http 서버가 WebDAV PROPFIND 명령어를 지원할 때만 동작합니다.
  • ScanPeriod: 스캐너 쓰레드의 실행 간격을 밀리세컨즈로 지정합니다. 기본 값은 5000(5 초)입니다.
  • URLComparator: 스캔된 디렉터리내에서 찾은 배치에 대한 배치 순서를 지정할 때 사용하는 java.util.Comparator 도구의 클래스 이름입니다. 도구에는 비교 메쏘드로 넘겨지는 두개의 java.net.URL 객체를 비교할 수 있어야만 합니다. 기본 설정은 배치 URL 접두사에 기반한 순서를 갖는 org.jboss.deployment.DeploymentSorter 클래스입니다. 접두사의 순서는 다음과 같습니다: "sar", "service.xml", "rar", "jar", "war", "wsr", "ear", "zip".

    이와 유사한 도구로는 org.jboss.deployment.scanner.PrefixDeploymentSorter 클래스가 있습니다. 이를 사용하여 숫자형식의 접두사를 기반으로 URLs의 순서를 정할 수 있습니다. 앞쪽에 오는 숫자는 정수(앞쪽에 오는 0은 무시)로 변환되며, 작은 숫자가 큰 숫자보다 앞서게 됩니다. 앞쪽에 어떠한 숫자도 없는 배치는 다른 모든 숫자를 갖는 배치들이 배치된 이후에 배치가 됩니다. 동일한 값을 갖는 경우에는 DeploymentSorter 로직에 의해 정렬되어집니다.

  • Filter: java.io.FileFilter 클래스 이름은 스캔되어진 디렉터리들의 컨텐츠들을 필터링하는데 사용됩니다. 이 필터에 의해 수락되지 않은 어떠한 파일도 배치되지 않습니다. 기본값은 다음과 같은 패턴을 갖도록 적용된 org.jboss.deployment.scanner.DeploymentFilter 입니다:

    "#*", "%*", ",*", ".*", "_$*", "*#", "*$", "*%", "*.BAK", "*.old", "*.orig", "*.rej", "*.bak", "*,v", "*~", ".make.state", ".nse_depinfo", "CVS", "CVS.admin", "RCS", "RCSLOG", "SCCS", "TAGS", "core", "tags"

  • RecursiveSearch: 이 속성은 배치가 가능한 컨텐츠를 갖고 있는 하부 디렉터리들의 배치 여부를 지정합니다. 이것이 false일 경우, 이름내에 '.'이 포함되지 않은 배치 서브 디렉토리들은 중첩된 하부배치를 갖는 풀린 jars처럼 보여지게 됩니다. true인 경우에는 배치 서브디렉터리들은 배치가능한 컨텐츠의 그룹핑일 뿐 입니다. 이 두개의 뷰의 차이점은 JBoss에서 지원하는 첫번째 배치 모델의 깊이와 관련있습니다. 중첩된 컨텐츠를 갖는 풀린 jars로 인식되는 디렉터리로 설정되는 false는 jar 디렉토리가 배치되는 즉시 중첩된 컨텐츠의 배치가 이루어지게 됩니다. true로 설정하면 단순하게 디렉토리를 무시하고 해당 컨텐츠를 배치가능한 목록에 추가하고 앞에서 살펴본 필터 로직에 따라 그 순위가 결정됩니다. 기본 값은 true 입니다만 jboss-3.2.1 릴리즈에서는 기본 설정값이 false로 되어 있습니다.
  • Deployer: org.jboss.deployment.Deployer 인터페이스 오퍼레이션을 수행하는 MBean의 JMX ObjectName 문자열입니다. 기본 설정은 bootstrap 구동 프로세스에 의해 만들어진 MainDeployer를 사용합니다.

2.4.3. JBoss MBean 서비스 만들기

JBoss 서버에 통합되는 커스텀 MBean 서비스를 제작하기 위해서는 커스텀 서비스가 다른 서비스들과 종속되어 있는 경우 org.jboss.system.Service 인터페이스 패턴을 사용할 필요가 있습니다. 커스텀 MBean이 다른 MBean 서비스들에 종속적인 경우, JMX가 종속성 표기를 갖지 않기 때문에 어떠한 javax.management.MBeanRegistration 인터페이스 메쏘드에서도 임의의 서비스 종속적인 초기화가 수행되지 않습니다. 대신 여러분은 반드시 서비스 인터페이스 create 및/혹은 start 메쏘드를 사용하여 종속성 상태를 관리해야만 합니다. 이를 위해서는 다음과 같은 방법을 통해 가능하게 됩니다:

  • 여러분이 원하는 임의의 서비스 메쏘드를 여러분의 MBean에서 호출되도록 하려면 MBean 인터페이스에 추가합니다. 이렇게 해줌으로써 여러분의 MBean은 JBoss만의 인터페이스에 따르는 종속성을 피해 수행할 수 있습니다.
  • 여러분의 MBean 인터페이스가 org.jboss.system.Service 인터페이스를 확장하도록 합니다.
  • 여러분의 MBean 인터페이스가 org.jboss.system.ServiceMBean 인터페이스를 확장할 수 있도록 합니다. 이것은 String getName(), int getState(), 그리고 String getStateString() 메쏘드를 추가하는 org.jboss.system.Service의 서브인터페이스입니다.

여러분이 어떤 방식으로 처리하느냐는 JBoss에 따른 코드와 관련되도록 하느냐에 따라 달라집니다. JBoss와 상관없는 코드를 원할 경우에는 첫번째 방법을 사용하면 됩니다. JBoss의 클래스들과의 종속성에 주의를 기울이지 않고자 한다면, 가장 간단한 방법은 org.jboss.system.ServiceMBean 에서 확장된 여러분의 MBean 인터페이스를 갖게 하고 여러분의 MBean 임플리먼테이션 클래스가 abstract org.jboss.system.ServiceMBeanSupport 클래스로부터 확장되도록 하는 것입니다. 이 클래스는 org.jboss.system.ServiceMBean 인터페이스를 수행합니다. ServiceMBeanSupport는 로깅과 JBoss 서비스 상태 관리 트래킹을 통합하는 create, start, stop 및 destroy 메쏘드의 수행을 제공합니다. 각각의 메쏘드는 임의의 서브클래스에 특정한 작업을 위해 각각 createService, startService, stopService 및 destroyService 메쏘드들로 위임됩니다. ServiceMBeanSupport의 서브클래싱에서는 필요에 따라 하나 이상의 createService, startService, stopService 및 destroyService 메쏘드를 오버라이드시킬 수 있습니다.

2.4.3.1. 표준 MBean 예제

이번 섹션에서는 자신의 JndiName 속성에 의해 위치가 결정되는 JBoss JNDI 네임스페이스쪽의 HashMap에 바인딩되는 간단한 하나의 MBean을 만드는 것을 통해 커스텀 MBean을 만드는데 필요한 과정을 살펴보도록 하겠습니다. MBean이 JNDI를 사용하기 때문에 JBoss 네이밍 서비스 MBean에 종속적이며, 네이밍 서비스가 사용가능한지를 통지해주는 JBoss MBean 서비스 패턴을 사용해야만 합니다.

여러분이 개발하게 될 MBean의 이름은 JNDIMap입니다. 서비스 인터페이스 메쏘드 패턴에 기반을 둔 JNDIMapMBean 인터페이스와 JNDIMap 임플리멘테이션 클래스의 첫 번째 버전은 예제 2.16, “서비스 인터페이스 메쏘드 패턴에 기반한 JNDIMapMBean 인터페이스와 임플리멘테이션”에서 볼 수 있습니다. 첫 번째 버전에서의 인터페이스는 올바르게 구동될 필요가 있는 서비스 인터페이스 메쏘드와 협력하는 첫 번째 방법을 사용하지만, JBoss에 특정한 인터페이스를 사용해서 하지 않습니다. 인터페이스는 Service.start 메쏘드를 포함하고 있는데, 이는 모든 필요한 서비스들이 시작되었는지를 알려주며 stop 메쏘드는 서비스를 정리해줍니다.

예제 2.16. 서비스 인터페이스 메쏘드 패턴에 기반한 JNDIMapMBean 인터페이스와 임플리멘테이션

 package org.jboss.chap2.ex1;
                
// The JNDIMap MBean interface
import javax.naming.NamingException;
                
public interface JNDIMapMBean
{
    public String getJndiName();
    public void setJndiName(String jndiName) throws NamingException;
    public void start() throws Exception;
    public void stop() throws Exception;
}
package org.jboss.chap2.ex1;

// The JNDIMap MBean implementation
import java.util.HashMap;
import javax.naming.InitialContext;
import javax.naming.Name;
import javax.naming.NamingException;
import org.jboss.naming.NonSerializableFactory;

public class JNDIMap implements JNDIMapMBean
{
    private String jndiName;
    private HashMap contextMap = new HashMap();
    private boolean started;
    
    public String getJndiName()
    {
        return jndiName;
    }
    public void setJndiName(String jndiName) throws NamingException
    {
        String oldName = this.jndiName;
        this.jndiName = jndiName;
        if (started) {
            unbind(oldName);
            try {
                rebind();
            } catch(Exception e) {
                NamingException ne = new NamingException("Failedto update jndiName");
                ne.setRootCause(e);
                throw ne;
            }
        }
    }

    public void start() throws Exception
    {
        started = true;
        rebind();
    }
                
    public void stop()
    {
        started = false;
        unbind(jndiName);
    }
                
    private void rebind() throws NamingException
    {
        InitialContext rootCtx = new InitialContext();
        Name fullName = rootCtx.getNameParser("").parse(jndiName);
        System.out.println("fullName="+fullName);
        NonSerializableFactory.rebind(fullName, contextMap, true);
    }

    private void unbind(String jndiName)
    {
        try {
            InitialContext rootCtx = new InitialContext();
            rootCtx.unbind(jndiName);
            NonSerializableFactory.unbind(jndiName);
        } catch(NamingException e) {
            e.printStackTrace();
        }
    }
}
package org.jboss.chap2.ex1;
                
// The JNDIMap MBean interface
import javax.naming.NamingException;
                
public interface JNDIMapMBean
{
    public String getJndiName();
    public void setJndiName(String jndiName) throws NamingException;
    public void start() throws Exception;
    public void stop() throws Exception;
}
package org.jboss.chap2.ex1;
// The JNDIMap MBean implementation
import java.util.HashMap;
import javax.naming.InitialContext;
import javax.naming.Name;
import javax.naming.NamingException;
import org.jboss.naming.NonSerializableFactory;

public class JNDIMap implements JNDIMapMBean
{
    private String jndiName;
    private HashMap contextMap = new HashMap();
    private boolean started;
    
    public String getJndiName()
    {
        return jndiName;
    }

    public void setJndiName(String jndiName) throws NamingException
    {
        String oldName = this.jndiName;
        this.jndiName = jndiName;
        if (started) {
            unbind(oldName);
            try {
                rebind();
            } catch(Exception e) {
                NamingException ne = new NamingException("Failed to update jndiName");
                ne.setRootCause(e);
                throw ne;
            }
        }
    }

    public void start() throws Exception
    {
        started = true;
        rebind();
    }
                
    public void stop()
    {
        started = false;
        unbind(jndiName);
    }
    
    private void rebind() throws NamingException
    {
        InitialContext rootCtx = new InitialContext();
        Name fullName = rootCtx.getNameParser("").parse(jndiName);
        System.out.println("fullName="+fullName);
        NonSerializableFactory.rebind(fullName, contextMap, true);
    }

    private void unbind(String jndiName)
    {
        try {
            InitialContext rootCtx = new InitialContext();
            rootCtx.unbind(jndiName);
            NonSerializableFactory.unbind(jndiName);
        } catch(NamingException e) {
            e.printStackTrace();
        }
    }
}

ServiceMBean 인터페이스와 ServiceMBeanSupport 클래스에 시반을 두고 있는 JNDIMapMBean 인터페이스와 JNDIMap 임플리멘테이션 클래스의 두번째 버전은 예제 2.16, “서비스 인터페이스 메쏘드 패턴에 기반한 JNDIMapMBean 인터페이스와 임플리멘테이션”에서 찾을 수 있습니다. 이 버전에서 임플리멘테이션 클래스는 ServiceMBeanSupport 클래스를 확장하였으며 startService 메쏘드와 stopService 메쏘드를 오버라이드시켰습니다. 또한 JNDIMapMBean은 abstract getName을 수행하여 MBean의 descriptive 이름을 반환합니다. JNDIMapMBean 인터페이스는 org.jboss.system.ServiceMBean 인터페이스를 확장시킨 것이며, ServiceMBean으로부터 서비스 생명 주기 메쏘드를 상속받았기 때문에 JndiName 속성을 위한 setter와 getter 메쏘드만을 선언해주었습니다. 이것은 2.4.2 장, “JBoss MBean 서비스”의 시작부분에서 언급된 세번째 접근방식입니다. The implementation differences between 예제 2.16, “서비스 인터페이스 메쏘드 패턴에 기반한 JNDIMapMBean 인터페이스와 임플리멘테이션” 예제 2.17, “ServiceMBean 인터페이스와 ServiceMBeanSupport 클래스에 기반을 둔 JNDIMap MBean 인터페이스와 임플리멘테이션”사이에서 임플리멘테이션의 차이점은 예제 2.17, “ServiceMBean 인터페이스와 ServiceMBeanSupport 클래스에 기반을 둔 JNDIMap MBean 인터페이스와 임플리멘테이션”의 굵은체로 표시되어 있습니다.

예제 2.17. ServiceMBean 인터페이스와 ServiceMBeanSupport 클래스에 기반을 둔 JNDIMap MBean 인터페이스와 임플리멘테이션

package org.jboss.chap2.ex2;

// The JNDIMap MBean interface
import javax.naming.NamingException;

public interface JNDIMapMBean extends org.jboss.system.ServiceMBean
{
    public String getJndiName();
    public void setJndiName(String jndiName) throws NamingException;
}

package org.jboss.chap2.ex2;
// The JNDIMap MBean implementation
import java.util.HashMap;
import javax.naming.InitialContext;
import javax.naming.Name;
import javax.naming.NamingException;
import org.jboss.naming.NonSerializableFactory;

public class JNDIMap extends org.jboss.system.ServiceMBeanSupport
    implements JNDIMapMBean
{
    private String jndiName;
    private HashMap contextMap = new HashMap();
    
    public String getJndiName()
    {
        return jndiName;
    }

    public void setJndiName(String jndiName) 
        throws NamingException
    {
        String oldName = this.jndiName;
        this.jndiName = jndiName;
        if (super.getState() == STARTED) {
            unbind(oldName);
            try {
                rebind();
            } catch(Exception e) {
                NamingException ne = new NamingException("Failed to update jndiName");
                ne.setRootCause(e);
                throw ne;
            }
        }
    }
    
    public void startService() throws Exception
    {
        rebind();
    }

    public void stopService()
    {
        unbind(jndiName);
    }
    
    private void rebind() throws NamingException
    {
        InitialContext rootCtx = new InitialContext();
        Name fullName = rootCtx.getNameParser("").parse(jndiName);
        log.info("fullName="+fullName);
        NonSerializableFactory.rebind(fullName, contextMap, true);
    }

    private void unbind(String jndiName)
    {
        try {
            InitialContext rootCtx = new InitialContext();
            rootCtx.unbind(jndiName);
            NonSerializableFactory.unbind(jndiName);
        } catch(NamingException e) {
            log.error("Failed to unbind map", e);
        }
    }
}

서비스 서술자들과 함께 이 MBeans들의 소스코드는 examples/src/main/org/jboss/chap2/{ex1,ex2} 디렉터리에 위치합니다.

예제 1 서비스 서술자는 예제 클라이언트에 사용된 코드의 일부분을 아랫쪽에서 볼 수 있습니다. JNDIMap MBean은 inmemory/maps/MapTest JNDI 이름아래의 HashMap 객체에 바인드되고, 클라이언트 코드의 발췌된 부분에서는 inmemory/maps/MapTest 위치에서 HashMap 객체를 가져오는 것을 보여주고 있습니다.

<!-- The SAR META-INF/jboss-service.xml descriptor -->
<server>
    <mbean code="org.jboss.chap2.ex1.JNDIMap" 
           name="chap2.ex1:service=JNDIMap">
        <attribute name="JndiName">inmemory/maps/MapTest</attribute>
        <depends>jboss:service=Naming</depends>
    </mbean>
</server>
                
// Sample lookup code
InitialContext ctx = new InitialContext();
HashMap map = (HashMap) ctx.lookup("inmemory/maps/MapTest");

2.4.3.2. XMBean 예제들

이번 섹션에서 우리는 이전 섹션에서 소개한 JBoss XMBean 프레임워크를 사용하여 자신의 관리 메터데이터를 노출시켜 JNDIMap MBean을 변경시키는 개발을 하게 될것입니다. 우리의 중요한 관리되는 컴포넌트는 JNDIMap 클래스에서의 중요 코드와 동일하지만, 인터페이스와 관련된 어떠한 특정한 관리의 임플리멘트도 하지 않습니다. 우리는 여기서 표준 MBean으로 가능하지 않은 다음과 같은 기능들을 보여줄 것입니다:

  • 속성과 오퍼레이션에 풍부한 설명을 추가시키는 기능
  • 통지 정보를 노출시키는 기능
  • 속성들의 영속성 추가 기능
  • typed 인터페이스를 통해 보안과 원격 액세스를 위한 커스텀 인터셉터를 추가시키는 기능
2.4.3.2.1. 버전 1, JNDIMap XMBean의 주석

우선 JNDIMap의 표준 MBean에 변화를 주어 속성들과 오퍼레이션 그리고 인수에 대한 서술적인 정보를 추가시켜주는 간단한 XMBean으로 시작하도록 하겠습니다. 다음에 주어진 코드는 jboss-service.xml 서술자와 jndimap-xmlbean1.xml XMBean 서술자입니다. 소스코드는 이 책의 예제 디렉터리 src/main/org/jboss/chap2/xmbean 에서 찾을 수 있습니다.

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE server PUBLIC    
                     "-//JBoss//DTD MBean Service 3.2//EN"
                     "http://www.jboss.org/j2ee/dtd/jboss-service_3_2.dtd">
<server>
    <mbean code="org.jboss.chap2.xmbean.JNDIMap"
           name="chap2.xmbean:service=JNDIMap" 
           xmbean-dd="META-INF/jndimap-xmbean.xml">
        <attribute name="JndiName">inmemory/maps/MapTest</attribute>
        <depends>jboss:service=Naming</depends>
    </mbean>
</server>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mbean PUBLIC
          "-//JBoss//DTD JBOSS XMBEAN 1.0//EN"
          "http://www.jboss.org/j2ee/dtd/jboss_xmbean_1_0.dtd">
<mbean>
    <description>The JNDIMap XMBean Example Version 1</description>
    <descriptors>
        <persistence persistPolicy="Never" persistPeriod="10"
            persistLocation="data/JNDIMap.data" persistName="JNDIMap"/>
        <currencyTimeLimit value="10"/>
        <state-action-on-update value="keep-running"/>
    </descriptors>
    <class>org.jboss.test.jmx.xmbean.JNDIMap</class>
    <constructor>
        <description>The default constructor</description>
        <name>JNDIMap</name>
    </constructor>  
    <!-- Attributes -->
    <attribute access="read-write" getMethod="getJndiName" setMethod="setJndiName">
        <description>
            The location in JNDI where the Map we manage will be bound
        </description>
        <name>JndiName</name>
        <type>java.lang.String</type>
        <descriptors>
            <default value="inmemory/maps/MapTest"/>
        </descriptors>
    </attribute>
    <attribute access="read-write" getMethod="getInitialValues"
               setMethod="setInitialValues">
        <description>The array of initial values that will be placed into the
            map associated with the service. The array is a collection of
            key,value pairs with elements[0,2,4,...2n] being the keys and
            elements [1,3,5,...,2n+1] the associated values. The
            "[Ljava.lang.String;" type signature is the VM representation of the
            java.lang.String[] type. </description>
        <name>InitialValues</name>
        <type>[Ljava.lang.String;</type>
        <descriptors>
            <default value="key0,value0"/>
        </descriptors>
    </attribute>  
    <!-- Operations -->
    <operation>
        <description>The start lifecycle operation</description>
        <name>start</name>
    </operation>
    <operation>
        <description>The stop lifecycle operation</description>
        <name>stop</name>
    </operation>
    <operation impact="ACTION">
        <description>Put a value into the map</description>
        <name>put</name>
        <parameter>
            <description>The key the value will be store under</description>
            <name>key</name>
            <type>java.lang.Object</type>
        </parameter>
        <parameter>
            <description>The value to place into the map</description>
            <name>value</name>
            <type>java.lang.Object</type>
        </parameter>
    </operation>
    <operation impact="INFO">
        <description>Get a value from the map</description>
        <name>get</name>
        <parameter>
            <description>The key to lookup in the map</description>
            <name>get</name>
            <type>java.lang.Object</type>
        </parameter>
        <return-type>java.lang.Object</return-type>
    </operation>  
    <!-- Notifications -->
    <notification>
        <description>The notification sent whenever a value is get into the map
            managed by the service</description>
        <name>javax.management.Notification</name>
        <notification-type>org.jboss.chap2.xmbean.JNDIMap.get</notification-type>
    </notification>
    <notification>
        <description>The notification sent whenever a value is put into the map
            managed by the service</description>
        <name>javax.management.Notification</name>
        <notification-type>org.jboss.chap2.xmbean.JNDIMap.put</notification-type>
    </notification>
</mbean>

앞에서도 언급하였듯이, 릴리즈 3.2.2에서는 인보커 아답터 서비스가 RMIAdaptor 인터페이서의 바인딩으로 대체되었으며, 이 서비스는 아직까지는 JMX 통지의 리모팅을 지원하지는 않습니다. 결국, 우리는 RMIAdaptorService를 사용하는 환경을 만들어줘야만 합니다. 설치된 jmx-rmi-adaptor.sar를 사용하여 rmi-adaptor를 설정한 환경 타겟이 있습니다. 설정은 다음을 이용합니다:

[nr@toki]$ ant -Dchap=chap2 config
...
 
     [echo] Preparing rmi-adaptor configuration fileset
     [copy] Copying 214 files to /tmp/jboss-3.2.6/server/rmi-adaptor
     [copy] Copied 2 empty directories to /tmp/jboss-3.2.6/server/rmi-adaptor
     [copy] Copying 2 files to /tmp/jboss-3.2.6/server/rmi-adaptor/deploy/jmx-rmi-adaptor.s
                        ar
   [delete] Deleting directory /tmp/jboss-3.2.6/server/rmi-adaptor/deploy/jmx-invoker-adapt
                        or-server.sar
   [delete] Deleting directory /tmp/jboss-3.2.6/server/rmi-adaptor/deploy/management

이제, 다음과 같이 XMBean의 rmi-adaptor 환경을 실행하고 빌드한 후 배치하고 테스트합니다:

[nr@toki examples]$ ant -Dchap=chap2 -Dex=xmbean1 -Djboss.deploy.conf=rmi-adaptor run-example
...
run-examplexmbean1:
     [copy] Copying 1 file to /tmp/jboss-3.2.6/server/rmi-adaptor/deploy
     [java] JNDIMap Class: org.jboss.mx.modelmbean.XMBean
     [java] JNDIMap Operations: 
     [java]  + void start()
     [java]  + void stop()
     [java]  + void put(java.lang.Object chap2.xmbean:service=JNDIMap,java.lang.Object cha
p2.xmbean:service=JNDIMap)
     [java]  + java.lang.Object get(java.lang.Object chap2.xmbean:service=JNDIMap)
     [java]  + java.lang.String getJndiName()
     [java]  + void setJndiName(java.lang.String chap2.xmbean:service=JNDIMap)
     [java]  + [Ljava.lang.String; getInitialValues()
     [java]  + void setInitialValues([Ljava.lang.String; chap2.xmbean:service=JNDIMap)
     [java] handleNotification, event: null
     [java] key=key0, value=value0
     [java] handleNotification, event: javax.management.Notification[source=chap2.xmbean:s
ervice=JNDIMap,type=org.jboss.chap2.xmbean.JNDIMap.put,sequenceNumber=3,timeStamp=10986315
27823,message=null,userData=null]
     [java] JNDIMap.put(key1, value1) successful
     [java] handleNotification, event: javax.management.Notification[source=chap2.xmbean:s
ervice=JNDIMap,type=org.jboss.chap2.xmbean.JNDIMap.get,sequenceNumber=4,timeStamp=10986315
27940,message=null,userData=null]
     [java] JNDIMap.get(key0): null
     [java] handleNotification, event: javax.management.Notification[source=chap2.xmbean:s
ervice=JNDIMap,type=org.jboss.chap2.xmbean.JNDIMap.get,sequenceNumber=5,timeStamp=10986315
27985,message=null,userData=null]
     [java] JNDIMap.get(key1): value1
     [java] handleNotification, event: javax.management.Notification[source=chap2.xmbean:s
ervice=JNDIMap,type=org.jboss.chap2.xmbean.JNDIMap.put,sequenceNumber=6,timeStamp=10986315
27999,message=null,userData=null]

기능은 JMX 통지의 통지가능한 예외를 갖는 표준 MBean과 많은 부분에서 동일합니다. 표준 MBean은 통지를 내보내도록 하는 선언을 할 수 있는 방법이 없습니다. 하나의 XMBean에서는 버전 1 서술자에서 보여지는 것처럼 통지 요소를 사용하여 통지를 방출할 수 있는 선언부를 가질 수 있습니다. 우리는 테스트 클라이언트 콘솔의 결과창에서 get과 put 오퍼레이션을 통해 통지를 볼 수 있습니다. 여기서 InitialValues속성이 변경될 때마다 jmx.attribute.change notification이 발생된다는 것에 주의하십시오. 이것은 AttributeChangeNotificationListeners를 지원하는 ModelMBeanNotificationBroadcaster의 확장된 ModelMBean 인터페이스라는 것을 인정하는 ModelMBean의 표준 기능입니다.

JNDIMap의 표준과 XMBean 버전 사이의 또다른 주요한 차이점은 서술적인 메터데이터입니다. JMX 콘솔의 chap2.xmbean:service=JNDIMap을 살펴본다면, 여러분은 그림 2.19, “버전 1의 JNDIMapXMBean jmx-console 뷰”에서 처럼 속성들의 섹션을 확인할 수 있습니다.

버전 1의 JNDIMapXMBean jmx-console 뷰

그림 2.19. 버전 1의 JNDIMapXMBean jmx-console 뷰

JMX 콘솔에서 표준 MBean의 임플리멘테이션에서 보았던 MBean Attribute 텍스트가 아닌 XMBean 서술자에 지정된 속성 서술이 보여지는 것에 주의하십시오. 오퍼레이션을 스크롤하면서 밑으로 내려보면 함수들과 매개변수들에 대한 보다 상세한 설명들도 볼 수 있습니다.

2.4.3.2.2. 버전 2, JNDIMap XMBean에 영속성 추가시키기

XMBean 버전 2에서 우리는 XMBean 속성들의 영속성 지원을 추가해봅니다. 업데이트된 XMBean 배치 서술자는 아래에 제공되어있습니다. 버전 1에 비해 달라진 사항들은 굵게 표시되어 있습니다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mbean PUBLIC
          "-//JBoss//DTD JBOSS XMBEAN 1.0//EN"
          "http://www.jboss.org/j2ee/dtd/jboss_xmbean_1_0.dtd">
<mbean>
    <description>The JNDIMap XMBean Example Version 2</description>
    <descriptors>
        <persistence persistPolicy="OnUpdate" persistPeriod="10"
            persistLocation="${jboss.server.data.dir}" persistName="JNDIMap.ser"/>
        <currencyTimeLimit value="10"/>
        <state-action-on-update value="keep-running"/>
        <persistence-manager value="org.jboss.mx.persistence.ObjectStreamPersistenceManager"/>
    </descriptors>   <class>org.jboss.test.jmx.xmbean.JNDIMap</class>
    <constructor>
        <description>The default constructor</description>
        <name>JNDIMap</name>
    </constructor>  
    <!-- Attributes -->
    <attribute access="read-write" getMethod="getJndiName" setMethod="setJndiName">
        <description>
            The location in JNDI where the Map we manage will be bound
        </description>
        <name>JndiName</name>
        <type>java.lang.String</type>
        <descriptors>
            <default value="inmemory/maps/MapTest"/>
        </descriptors>
    </attribute>
    <attribute access="read-write" getMethod="getInitialValues"
               setMethod="setInitialValues">
        <description>The array of initial values that will be placed into the
            map associated with the service. The array is a collection of
            key,value pairs with elements[0,2,4,...2n] being the keys and
            elements [1,3,5,...,2n+1] the associated values</description>
        <name>InitialValues</name>
        <type>[Ljava.lang.String;</type>
        <descriptors>
            <default value="key0,value0"/>
        </descriptors>
    </attribute>  
    <!-- Operations -->
    <operation>
        <description>The start lifecycle operation</description>
        <name>start</name>
    </operation>
    <operation>
        <description>The stop lifecycle operation</description>
        <name>stop</name>
    </operation>
    <operation impact="ACTION">
        <description>Put a value into the nap</description>
        <name>put</name>
        <parameter>
            <description>The key the value will be store under</description>
            <name>key</name>
            <type>java.lang.Object</type>
        </parameter>
        <parameter>
            <description>The value to place into the map</description>
            <name>value</name>
            <type>java.lang.Object</type>
        </parameter>
    </operation>
    <operation impact="INFO">
        <description>Get a value from the map</description>
        <name>get</name>
        <parameter>
            <description>The key to lookup in the map</description>
            <name>get</name>
            <type>java.lang.Object</type>
        </parameter>
        <return-type>java.lang.Object</return-type>
    </operation>  
    <!-- Notifications -->
    <notification>
        <description>The notification sent whenever a value is get into the map
            managed by the service</description>
        <name>javax.management.Notification</name>
        <notification-type>org.jboss.chap2.xmbean.JNDIMap.get</notification-type>
    </notification>
    <notification>
        <description>The notification sent whenever a value is put into the map
            managed by the service</description>
        <name>javax.management.Notification</name>
        <notification-type>org.jboss.chap2.xmbean.JNDIMap.put</notification-type>
    </notification>
</mbean>

XMBean의 버전2를 빌드, 배치 및 테스트 하는 방법은 다음과 같습니다:

[examples]$ ant -Dchap=chap2 -Dex=xmbean2 -Djboss.deploy.conf=rmi-adaptor run-example
...
run-examplexmbean2:
     [java] JNDIMap Class: org.jboss.mx.modelmbean.XMBean
     [java] JNDIMap Operations: 
     [java]  + void start()
     [java]  + void stop()
     [java]  + void put(java.lang.Object chap2.xmbean:service=JNDIMap,java.lang.Object cha
p2.xmbean:service=JNDIMap)
     [java]  + java.lang.Object get(java.lang.Object chap2.xmbean:service=JNDIMap)
     [java]  + java.lang.String getJndiName()
     [java]  + void setJndiName(java.lang.String chap2.xmbean:service=JNDIMap)
     [java]  + [Ljava.lang.String; getInitialValues()
     [java]  + void setInitialValues([Ljava.lang.String; chap2.xmbean:service=JNDIMap)
     [java] handleNotification, event: null
     [java] key=key10, value=value10
     [java] handleNotification, event: javax.management.Notification[source=chap2.xmbean:s
ervice=JNDIMap,type=org.jboss.chap2.xmbean.JNDIMap.put,sequenceNumber=7,timeStamp=10986326
93716,message=null,userData=null]
     [java] JNDIMap.put(key1, value1) successful
     [java] handleNotification, event: javax.management.Notification[source=chap2.xmbean:s
ervice=JNDIMap,type=org.jboss.chap2.xmbean.JNDIMap.get,sequenceNumber=8,timeStamp=10986326
93857,message=null,userData=null]
     [java] JNDIMap.get(key0): null
     [java] handleNotification, event: javax.management.Notification[source=chap2.xmbean:s
ervice=JNDIMap,type=org.jboss.chap2.xmbean.JNDIMap.get,sequenceNumber=9,timeStamp=10986326
93896,message=null,userData=null]
     [java] JNDIMap.get(key1): value1
     [java] handleNotification, event: javax.management.Notification[source=chap2.xmbean:s
ervice=JNDIMap,type=org.jboss.chap2.xmbean.JNDIMap.put,sequenceNumber=10,timeStamp=1098632
693925,message=null,userData=null]

실제 영속적이도록 변경된 속성 값을 만들지 않았기 때문에 현 단계에서는 XMBean의 이번 버전과 명백한 차이점은 없습니다. 이 테스트를 예제 xmbean2a 를 실행시켜 몇번 반복시켜 보십시오:

[nr@toki examples] ant -Dchap=chap2 -Dex=xmbean2a -Djboss.deploy.conf=rmi-adaptor \
run-example
...
     [java] InitialValues.length=2
     [java] key=key10, value=value10
[nr@toki examples] ant -Dchap=chap2 -Dex=xmbean2a -Djboss.deploy.conf=rmi-adaptor \
run-example
...
     [java] InitialValues.length=4
     [java] key=key10, value=value10
     [java] key=key2, value=value2
[nr@toki examples] ant -Dchap=chap2 -Dex=xmbean2a -Djboss.deploy.conf=rmi-adaptor \
run-example
...
     [java] InitialValues.length=6
     [java] key=key10, value=value10
     [java] key=key2, value=value2
     [java] key=key3, value=value3

이 예제에서 사용된 org.jboss.chap2.xmbean.TestXMBeanRestart는 현재 설정된 InitialValues 속성를 갖게 되며, 여기에 또 다른 키/값 쌍을 추가시키게 됩니다. 클라이언트 코드는 다음과 같습니다.

package org.jboss.chap2.xmbean;

import javax.management.Attribute;
import javax.management.ObjectName;
import javax.naming.InitialContext;

import org.jboss.jmx.adaptor.rmi.RMIAdaptor;

/**
 *  A client that demonstrates the persistence of the xmbean
 *  attributes. Every time it it run it looks up the InitialValues
 *  attribute, prints it out and then adds a new key/value to the
 *  list.
 *  
 *  @author Scott.Stark@jboss.org
 *  @version $Revision: 1.11 $
 */
public class TestXMBeanRestart
{
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) throws Exception
    {
	InitialContext ic = new InitialContext();
	RMIAdaptor server = (RMIAdaptor) ic.lookup("jmx/rmi/RMIAdaptor");
	
	// Get the InitialValues attribute
	ObjectName name = new ObjectName("chap2.xmbean:service=JNDIMap");
	String[] initialValues = (String[])
	    server.getAttribute(name, "InitialValues");
	System.out.println("InitialValues.length="+initialValues.length);
	int length = initialValues.length;
	for (int n = 0; n < length; n += 2) {
	    String key = initialValues[n];
	    String value = initialValues[n+1];
	    
	    System.out.println("key="+key+", value="+value);
	}
	// Add a new key/value pair
	String[] newInitialValues = new String[length+2];
	System.arraycopy(initialValues, 0, newInitialValues,
			 0, length);
	newInitialValues[length] = "key"+(length/2+1);
	newInitialValues[length+1] = "value"+(length/2+1);
	
	Attribute ivalues = new
	    Attribute("InitialValues", newInitialValues);
	server.setAttribute(name, ivalues);
    }
}

여기서부터는 여러분이 JBoss 서버를 셧다운시키고 다시 재시작시키더라도 그 값으로 2를 가져오는 것을 확인할 수 있어 서버의 리스타트를 통해서 속성값의 영속성을 확인할 수 있게 됩니다:

[examples]$ ant -Dchap=chap2 -Dex=xmbean2 -Djboss.deploy.conf=rmi-adaptor run-example
...
 
run-examplexmbean2:
     [java] JNDIMap Class: org.jboss.mx.modelmbean.XMBean
     [java] JNDIMap Operations: 
     [java]  + void start()
     [java]  + void stop()
     [java]  + void put(java.lang.Object chap2.xmbean:service=JNDIMap,java.lang.Object cha
p2.xmbean:service=JNDIMap)
     [java]  + java.lang.Object get(java.lang.Object chap2.xmbean:service=JNDIMap)
     [java]  + java.lang.String getJndiName()
     [java]  + void setJndiName(java.lang.String chap2.xmbean:service=JNDIMap)
     [java]  + [Ljava.lang.String; getInitialValues()
     [java]  + void setInitialValues([Ljava.lang.String; chap2.xmbean:service=JNDIMap)
     [java] handleNotification, event: null
     [java] key=key10, value=value10
     [java] key=key2, value=value2
     [java] key=key3, value=value3
     [java] key=key4, value=value4
     [java] handleNotification, event: javax.management.Notification[source=chap2.xmbean:s
ervice=JNDIMap,type=org.jboss.chap2.xmbean.JNDIMap.put,sequenceNumber=3,timeStamp=10986336
64712,message=null,userData=null]
     [java] JNDIMap.put(key1, value1) successful
     [java] handleNotification, event: javax.management.Notification[source=chap2.xmbean:s
ervice=JNDIMap,type=org.jboss.chap2.xmbean.JNDIMap.get,sequenceNumber=4,timeStamp=10986336
64821,message=null,userData=null]
     [java] JNDIMap.get(key0): null
     [java] handleNotification, event: javax.management.Notification[source=chap2.xmbean:s
ervice=JNDIMap,type=org.jboss.chap2.xmbean.JNDIMap.get,sequenceNumber=5,timeStamp=10986336
64860,message=null,userData=null]
     [java] JNDIMap.get(key1): value1
     [java] handleNotification, event: javax.management.Notification[source=chap2.xmbean:s
ervice=JNDIMap,type=org.jboss.chap2.xmbean.JNDIMap.put,sequenceNumber=6,timeStamp=10986336
64877,message=null,userData=null]
     [java] handleNotification, event: javax.management.Notification[source=chap2.xmbean:s
ervice=JNDIMap,type=org.jboss.chap2.xmbean.JNDIMap.put,sequenceNumber=7,timeStamp=10986336
64895,message=null,userData=null]
     [java] handleNotification, event: javax.management.Notification[source=chap2.xmbean:s
ervice=JNDIMap,type=org.jboss.chap2.xmbean.JNDIMap.put,sequenceNumber=8,timeStamp=10986336
64899,message=null,userData=null]
     [java] handleNotification, event: javax.management.Notification[source=chap2.xmbean:s
ervice=JNDIMap,type=org.jboss.chap2.xmbean.JNDIMap.put,sequenceNumber=9,timeStamp=10986336
65614,message=null,userData=null]   

여러분은 마지막 InitialValues 속성의 설정값을 볼 수 있습니다.

2.4.3.2.3. 버전 3, JNDIMap XMBean에 보안과 원격 액세스 추가시키기

JNDIMap XMBean의 마지막 예제 버전에서는 RMI/JRMP를 사용하는 리모트 클라이언트의 typed 프록시를 통해 XMBean 관리 인터페이스의 서브셋트를 노출시켜주는 것을 물론 서버 인터셉터 스택의 커스터마이징까지도 보여줄 것입니다. 서버쪽에서 우리는 인터셉터 환경설정내에 지정된 사용자만이 접근할 수 있는 속성이나 오퍼레이션이 가능한 간단한 보안 인터셉터를 추가시키게 됩니다. 또한 우리는 다른 커스텀 인터셉터로 Section 2.7, “서비스로의 원격 액세스, 분리된 인보커”에서 설명하고 있는 MBean의 분리된 인보커를 임플리멘트합니다. XMBean을 사용하지 않고 인보커를 사용하여 이러한 패턴을 임플리멘팅함으로써 우리는 기존의 JNDIMap 구현부의 수정없이 원격 액세스할 수 있는 방법을 보여주고 있습니다.

우리는 원격 클라이언트를 위한 ClientInterface를 노출시켜주는 JRMPProxyFactory 서비스를 사용할 것입니다.

public interface ClientInterface
{
    public String[] getInitialValues();
    public void setInitialValues(String[] keyValuePairs);
    public Object get(Object key);
    public void put(Object key, Object value);
} 

우리가 사용하는 테스트 클라이언트는 JNDI로부터 ClientInterface 프록시를 획득하고 이전에 사용되었던 RMIAdaptor 와 MBean 서버 스타일 대신 RMI 스타일 호출을 통해 XMBean과 상호작용하게 될 것입니다.

package org.jboss.chap2.xmbean;

import javax.naming.InitialContext;
import org.jboss.security.SecurityAssociation;
import org.jboss.security.SimplePrincipal;

/** 
 *  자신의 RMI 인터페이스를 통해 XMBean으로 액세스하는 클라이언트
 *  @author Scott.Stark@jboss.org
 *  @version $Revision: 1.11 $
 */
public class TestXMBean3
{
    
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) throws Exception
    {
        InitialContext ic = new InitialContext();
        ClientInterface xmbean = (ClientInterface) 
            ic.lookup("secure-xmbean/ClientInterface");
        
        // This call should fail because we have not set a security context
        try {
            String[] tmp = xmbean.getInitialValues();
            throw new IllegalStateException("Was able to call getInitialValues");
        } catch(Exception e) {
            System.out.println("Called to getInitialValues failed as expected: "
                               + e.getMessage());
        }
        
        // Set a security context using the SecurityAssociation
        SecurityAssociation.setPrincipal(new SimplePrincipal("admin"));
        
        // Get the InitialValues attribute
        String[] initialValues = xmbean.getInitialValues();
        for(int n = 0; n < initialValues.length; n += 2) {
            String key = initialValues[n];
            String value = initialValues[n+1];
            
            System.out.println("key="+key+", value="+value);
        }
        
        // Invoke the put(Object, Object) op
        xmbean.put("key1", "value1");
        System.out.println("JNDIMap.put(key1,
                        value1) successful");
        Object result0 = xmbean.get("key0");
        System.out.println("JNDIMap.get(key0): "+result0);
        Object result1 = xmbean.get("key1");
        System.out.println("JNDIMap.get(key1): "+result1);
        
        // Change the InitialValues
        initialValues[0] += ".1";
        initialValues[1] += ".2";
        xmbean.setInitialValues(initialValues);
        
        initialValues = xmbean.getInitialValues();
        for(int n = 0; n < initialValues.length; n += 2) {
            String key = initialValues[n];
            String value = initialValues[n+1];
            
            System.out.println("key="+key+", value="+value);
        }
    }
}

배치 서술자는 다음과 같습니다:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mbean PUBLIC
          "-//JBoss//DTD JBOSS XMBEAN 1.0//EN"
          "http://www.jboss.org/j2ee/dtd/jboss_xmbean_1_0.dtd"
          [<!ATTLIST interceptor adminName CDATA #IMPLIED>]>
<mbean>
    <description>The JNDIMap XMBean Example Version 3</description>
    <descriptors>
        <interceptors>
            <interceptor code="org.jboss.chap2.xmbean.ServerSecurityInterceptor" 
                         adminName="admin"/>
            <interceptor code="org.jboss.chap2.xmbean.InvokerInterceptor"/>
            <interceptor code="org.jboss.mx.interceptor.PersistenceInterceptor2"/>
            <interceptor code="org.jboss.mx.interceptor.ModelMBeanInterceptor"/>
            <interceptor code="org.jboss.mx.interceptor.ObjectReferenceInterceptor"/>
        </interceptors>
        <persistence persistPolicy="Never"/>
        <currencyTimeLimit value="10"/>
        <state-action-on-update value="keep-running"/>
    </descriptors>
    <class>org.jboss.test.jmx.xmbean.JNDIMap</class>
    <constructor>
        <description>The default constructor</description>
        <name>JNDIMap</name>
    </constructor>  
    <!-- Attributes -->
    <attribute access="read-write" getMethod="getJndiName" setMethod="setJndiName">
        <description>
            The location in JNDI where the Map we manage will be bound
        </description>
        <name>JndiName</name>
        <type>java.lang.String</type>
        <descriptors>
            <default value="inmemory/maps/MapTest"/>
        </descriptors>
    </attribute>
    <attribute access="read-write" getMethod="getInitialValues" 
               setMethod="setInitialValues">
        <description>The array of initial values that will be placed into the
            map associated with the service. The array is a collection of
            key,value pairs with elements[0,2,4,...2n] being the keys and
            elements [1,3,5,...,2n+1] the associated values</description>
        <name>InitialValues</name>
        <type>[Ljava.lang.String;</type>
        <descriptors>
            <default value="key0,value0"/>
        </descriptors>
    </attribute>  
    <!-- Operations -->
    <operation>
        <description>The start lifecycle operation</description>
        <name>start</name>
    </operation>
    <operation>
        <description>The stop lifecycle operation</description>
        <name>stop</name>
    </operation>
    <operation impact="ACTION">
        <description>Put a value into the nap</description>
        <name>put</name>
        <parameter>
            <description>The key the value will be store under</description>
            <name>key</name>
            <type>java.lang.Object</type>
        </parameter>
        <parameter>
            <description>The value to place into the map</description>
            <name>value</name>
            <type>java.lang.Object</type>
        </parameter>
    </operation>
    <operation impact="INFO">
        <description>Get a value from the map</description>
        <name>get</name>
        <parameter>
            <description>The key to lookup in the map</description>
            <name>get</name>
            <type>java.lang.Object</type>
        </parameter>
        <return-type>java.lang.Object</return-type>
    </operation>
</mbean>

JNDIMap XMBean의 이전 버전에 추가된 부분은 리스트내에 굵은 폰트로 보여지는 인터셉터 요소입니다. 이는 모든 MBean 속성이 액세스하고 오퍼레이션을 통과하는 인터셉터 스택을 정의합니다. 처음 두개의 인터셉터인 org.jboss.chap2.xmbean.ServerSecurityInterceptororg.jboss.chap2.xmbean.InvokerInterceptor는 커스텀 인터셉터의 예입니다. 나머지 세개의 인터셉터들은 표준 ModelMBean 인터셉터들입니다. persistence의 정책이 Never이기 때문에 우리는 표준 org.jboss.mx.interceptor.PersistenceInterceptor2를 제거할 수 있습니다. JMX 인터셉터들은 필터의 정해진 순서 체인입니다. 인터셉터의 표준 기초 클래스는 다음과 같습니다.

package org.jboss.mx.interceptor;

import javax.management.MBeanInfo;
import org.jboss.mx.server.MBeanInvoker;

/**
 * Base class for all interceptors.
 *
 * @see org.jboss.mx.interceptor.StandardMBeanInterceptor
 * @see org.jboss.mx.interceptor.LogInterceptor
 *
 * @author <a href="mailto:juha@jboss.org">Juha Lindfors</a>.
 * @version $Revision: 1.11 $
 *
 */
public class AbstractInterceptor implements Interceptor
{
    // Attributes ----------------------------------------------------
    protected Interceptor next = null;
    protected String name = null;
    protected MBeanInfo info;
    protected MBeanInvoker invoker;
    
    // Constructors --------------------------------------------------
    public AbstractInterceptor()
    {
        this(null);
    }
    public AbstractInterceptor(String name)
    {
        this.name = name;
    }
    public AbstractInterceptor(MBeanInfo info,
                               MBeanInvoker invoker)
    {
        this.name = getClass().getName();
        this.info = info;
        this.invoker = invoker;
    }
    
    // Public --------------------------------------------------------
    public Object invoke(Invocation invocation) 
        throws InvocationException
    {
        return getNext().invoke(invocation);
    }
    
    public Interceptor getNext()
    {
        return next;
    }
    
    public Interceptor setNext(Interceptor interceptor)
    {
        this.next = interceptor;
        return interceptor;
    }
    
}

XMBean 세번째 버전에서의 커스텀 인터셉터들은 ServerSecurityInterceptorInvokerInterceptor 입니다. ServerSecurityInterceptor 인터셉터는 오퍼레이션들을 호출하고 관리자 정책을 포함한 Invocation 컨텍스트의 정당성을 입증합니다.

package org.jboss.chap2.xmbean;

import java.security.Principal;

import org.jboss.logging.Logger;
import org.jboss.mx.interceptor.AbstractInterceptor;
import org.jboss.mx.interceptor.Invocation;
import org.jboss.mx.interceptor.InvocationException;
import org.jboss.security.SimplePrincipal;


/** 
 * 단일 정책으로 액세스를 제한하는 보안 인터셉터의 간단한 예제
 *
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.11 $
 */
 
public class ServerSecurityInterceptor extends AbstractInterceptor
{
    private static Logger log = Logger.getLogger(ServerSecurityInterceptor.class);
    private SimplePrincipal admin = new SimplePrincipal("admin");
    
    public String getAdminName()
    {
        return admin.getName();
    }
    public void setAdminName(String name)
    {
        admin = new SimplePrincipal(name);
    }

    public Object invoke(Invocation invocation) 
        throws InvocationException
    {
        String opName = invocation.getName();

        // If this is not the invoke(Invocation) op just pass it along
        if (opName.equals("invoke") == false) {
            return getNext().invoke(invocation);
        }

        Object[] args = invocation.getArgs();
        org.jboss.invocation.Invocation invokeInfo =
            (org.jboss.invocation.Invocation) args[0];
        Principal caller = invokeInfo.getPrincipal();
        log.info("invoke, opName="+opName+", caller="+caller);

        // Only the admin caller is allowed access
        if (caller == null || caller.equals(admin) == false) {
            throw new InvocationException(new SecurityException("Caller=" + 
                                                                caller + 
                                                                " is not allowed access"));
        }
        return getNext().invoke(invocation);
    }
}

InvokerInterceptor는 분리된 인보커 패턴을 수행합니다. 이에 대한 설명은 서비스로의 원격 접근, 분리된 인보커에서 다루도록 하겠습니다.

package org.jboss.chap2.xmbean;

import java.lang.reflect.Method;
import java.util.HashMap;
import javax.management.Descriptor;
import javax.management.MBeanInfo;

import org.jboss.logging.Logger;
import org.jboss.mx.interceptor.AbstractInterceptor;
import org.jboss.mx.interceptor.Invocation;
import org.jboss.mx.interceptor.InvocationException;
import org.jboss.mx.server.MBeanInvoker;
import org.jboss.invocation.MarshalledInvocation;

/** An interceptor that handles the
 *
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.11 $
 */
public class InvokerInterceptor 
    extends AbstractInterceptor
{
    private static Logger log = Logger.getLogger(InvokerInterceptor.class);
    private Class exposedInterface = ClientInterface.class;
    private HashMap methodMap = new HashMap();
    private HashMap invokeMap = new HashMap();

    public InvokerInterceptor(MBeanInfo info, MBeanInvoker invoker)
    {
        super(info, invoker);
        try {
            Descriptor[] descriptors = invoker.getDescriptors();
            Object resource = invoker.getResource();
            Class[] getInitialValuesSig = {};
            Method getInitialValues =
		exposedInterface.getDeclaredMethod("getInitialValues",
						   getInitialValuesSig);
            Long hash = new Long(MarshalledInvocation.calculateHash(getInitialValues));
            InvocationInfo invokeInfo = 
		new InvocationInfo("InitialValues",
				   Invocation.ATTRIBUTE, 
				   Invocation.READ, getInitialValuesSig,
				   descriptors, resource);
            methodMap.put(hash, getInitialValues);
            invokeMap.put(getInitialValues, invokeInfo);
            log.debug("getInitialValues hash:"+hash);

            Class[] setInitialValuesSig = {String[].class};
            Method setInitialValues = 
		exposedInterface.getDeclaredMethod("setInitialValues",
						   setInitialValuesSig);

            hash = new Long(MarshalledInvocation.calculateHash(setInitialValues));
            invokeInfo = new InvocationInfo("InitialValues",
                                            Invocation.ATTRIBUTE, 
					    Invocation.WRITE, 
					    setInitialValuesSig,
                                            descriptors, resource);
            methodMap.put(hash, setInitialValues);
            invokeMap.put(setInitialValues, invokeInfo);
            log.debug("setInitialValues hash:"+hash);

            Class[] getSig = {Object.class};
            Method get = exposedInterface.getDeclaredMethod("get",
                                                            getSig);
            hash = new Long(MarshalledInvocation.calculateHash(get));
            invokeInfo = new InvocationInfo("get",
                                            Invocation.OPERATION, 
					    Invocation.READ, getSig,
                                            descriptors, resource);
            methodMap.put(hash, get);
            invokeMap.put(get, invokeInfo);
            log.debug("get hash:"+hash);

            Class[] putSig = {Object.class, Object.class};
            Method put = exposedInterface.getDeclaredMethod("put",
                                                            putSig);
            hash = new Long(MarshalledInvocation.calculateHash(put));
            invokeInfo = new InvocationInfo("put",
                                            Invocation.OPERATION, 
					    Invocation.WRITE, putSig,
                                            descriptors, resource);
            methodMap.put(hash, put);
            invokeMap.put(put, invokeInfo);
            log.debug("putt hash:"+hash);
        } catch(Exception e) {
            log.error("Failed to init InvokerInterceptor", e);
        }
    }

    public Object invoke(Invocation invocation) 
        throws InvocationException
    {
        String opName = invocation.getName();
        Object[] args = invocation.getArgs();
        Object returnValue = null;
        if (opName.equals("invoke") == true) {
            org.jboss.invocation.Invocation invokeInfo =
                (org.jboss.invocation.Invocation) args[0];
            // Set the method hash to Method mapping
            if (invokeInfo instanceof MarshalledInvocation) {
                MarshalledInvocation mi = (MarshalledInvocation) invokeInfo;
                mi.setMethodMap(methodMap);
            }

            // Invoke the exposedInterface method via reflection if
            // this is an invoke
            Method method = invokeInfo.getMethod();
            Object[] methodArgs = invokeInfo.getArguments();
            InvocationInfo info = (InvocationInfo) invokeMap.get(method);
            Invocation methodInvocation = info.getInvocation(methodArgs);
            returnValue = getNext().invoke(methodInvocation);
        } else {
            returnValue = getNext().invoke(invocation);
        }
        return returnValue;
    }

    /**
     * A class that holds the ClientInterface method info needed to build
     * the JMX Invocation to pass down the interceptor stack.
     */
    private class InvocationInfo
    {
        private int type;
        private int impact;
        private String name;
        private String[] signature;
        private Descriptor[] descriptors;
        private Object resource;


        InvocationInfo(String name, int type, int impact,
                       Class[] signature, Descriptor[] descriptors, 
                       Object resource)
        {
            this.name = name;
            this.type = type;
            this.impact = impact;
            this.descriptors = descriptors;
            this.resource = resource;
            this.signature = new String[signature.length];
            for(int s = 0; s < signature.length; s ++) {
                this.signature[s] = signature[s].getName();
            }
        }

        Invocation getInvocation(Object[] args)
        {
            return new Invocation(name, type, impact, args, signature,
                                  descriptors, resource);
        }
    }
}

배치 서술자에서는 인터셉터 스택을 반드시 포함해야 합니다.

<?xml version='1.0' encoding='UTF-8' ?>
<server>
    <mbean code="org.jboss.chap2.xmbean.JNDIMap"
        name="chap2.xmbean:service=JNDIMap,version=3" 
        xmbean-dd="META-INF/jndimap-xmbean3.xml">
        <attribute name="JndiName">inmemory/maps/MapTest</attribute>
        <depends>jboss:service=Naming</depends>
    </mbean>  
    <!-- The JRMP invoker proxy configuration for
                        the naming service -->
    <mbean code="org.jboss.invocation.jrmp.server.JRMPProxyFactory" 
           name="jboss.test:service=proxyFactory,type=jrmp,target=JNDIMap">
        <!-- Use the standard JRMPInvoker from
                        conf/jboss-service.xxml -->
        <attribute name="InvokerName">jboss:service=invoker,type=jrmp</attribute>
        <attribute name="TargetName">chap2.xmbean:service=JNDIMap,version=3</attribute>
        <attribute name="JndiName">secure-xmbean/ClientInterface</attribute>
        <attribute name="ExportedInterface">
            org.jboss.chap2.xmbean.ClientInterface
        </attribute>
        <attribute name="ClientInterceptors">
            <iterceptors>
                <interceptor>org.jboss.proxy.ClientMethodInterceptor</interceptor>
                <interceptor>org.jboss.proxy.SecurityInterceptor</interceptor>
                <interceptor>org.jboss.invocation.InvokerInterceptor</interceptor>
            </iterceptors>
        </attribute>
        <depends>jboss:service=invoker,type=jrmp</depends>
        <depends>chap2.xmbean:service=JNDIMap,version=3</depends>
    </mbean>
</server>
[nr@toki examples] ant -Dchap=chap2 -Dex=xmbean3 config
...
config:
     [echo] Preparing rmi-adaptor configuration fileset
     [copy] Copying 60 files to /tmp/jboss-3.2.6/server/rmi-adaptor
   [delete] Deleting directory /tmp/jboss-3.2.6/server/rmi-adaptor/deploy/jmx-invoker-adap
tor-server.sar
   [delete] Deleting directory /tmp/jboss-3.2.6/server/rmi-adaptor/deploy/management
[nr@toki examples]$ ant -Dchap=chap2 -Dex=xmbean3 run-example
...
run-examplexmbean3:
     [java] Called to getInitialValues failed as expected: Caller=null is not allowed access
     [java] key=key0, value=value0
     [java] JNDIMap.put(key1, value1) successful
     [java] JNDIMap.get(key0): null
     [java] JNDIMap.get(key1): value1
     [java] key=key0.1, value=value0.2
[nr@toki examples]$ ant -Dchap=chap2 -Dex=xmbean3 run-example
...
run-examplexmbean3:
     [java] Called to getInitialValues failed as expected: Caller=null is not allowed access
     [java] key=key0.1, value=value0.2
     [java] JNDIMap.put(key1, value1) successful
     [java] JNDIMap.get(key0): null
     [java] JNDIMap.get(key1): value1
     [java] key=key0.1.1, value=value0.2.2

2.4.4. 배치의 순위와 종속성

우리는 이미 서비스 배치자의 depends 와 depends-list 태그를 사용하여 종속성을 어떻게 관리하는지를 살펴보았습니다. 배치의 순위는 배치의 순서를 정하기 위한 세련되지는 않은 종속성 관리가 제공되는 배치 스캐너에 의해 지원됩니다. 종속성이 배치 패키지들로 구성되어 있는 경우, 명료한 MBean-MBean 종속성들을 나열할 수 있는 간단한 메커니즘이 있습니다. 여러분이 직접 제작한 필터를 사용하여 배치 스캐너에서 제공하는 이러한 세련되지 않은 순위 메커니즘을 바꿀 수 있습니다.

콤퍼넌트 아카이브가 배치된 경우, 중첩된 배치 유닛들은 첫번째 순서로 처리되어집니다. 컴포넌트들의 구조를 아카이브 계층구조화하는 것은 아직까지는 배치 순위를 관리하는 별도의 방식입니다.

보통 여러분들은 여러분의 패키징 구조가 종속성에 대한 문제를 풀어주지 않기 때문에 여러분의 MBean 종속성에 대한 명백한 상태가 필요하게 됩니다. 이제 EJB를 사용한 MBean으로 구성된 컴포넌트 배치의 예를 생각해보도록 하겠습니다. 예제로 사용한 EAR의 구조는 다음과 같습니다.

output/chap2/chap2-ex3.ear
+- META-INF/MANIFEST.MF
+- META-INF/jboss-app.xml
+- chap2-ex3.jar (archive) [EJB jar]
| +- META-INF/MANIFEST.MF
| +- META-INF/ejb-jar.xml
| +- org/jboss/chap2/ex3/EchoBean.class
| +- org/jboss/chap2/ex3/EchoLocal.class
| +- org/jboss/chap2/ex3/EchoLocalHome.class
+- chap2-ex3.sar (archive) [MBean sar]
| +- META-INF/MANIFEST.MF
| +- META-INF/jboss-service.xml
| +- org/jboss/chap2/ex3/EjbMBeanAdaptor.class
+- META-INF/application.xml

EAR은 chap2-ex3.jarchap2-ex3.sar를 갖습니다. chap2-ex3.jar는 EJB 아카이브이며, chap2-ex3.sar는 MBean 서비스 아카이브입니다. 우리는 사용방법을 보여주기 위해 서비스를 동적인 MBean으로 구현하였습니다.

package org.jboss.chap2.ex3;
            
import java.lang.reflect.Method;
import javax.ejb.CreateException;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.DynamicMBean;
import javax.management.InvalidAttributeValueException;
import javax.management.JMRuntimeException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanConstructorInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import org.jboss.system.ServiceMBeanSupport;

/** 
 *  An example of a DynamicMBean that exposes select attributes and
 *  operations of an EJB as an MBean.
 *  @author Scott.Stark@jboss.org
 *  @version $Revision: 1.11 $
 */
public class EjbMBeanAdaptor extends ServiceMBeanSupport
    implements DynamicMBean
{
    private String helloPrefix;
    private String ejbJndiName;
    private EchoLocalHome home;
    
    /** These are the mbean attributes we expose
     */
    private MBeanAttributeInfo[] attributes = {
        new MBeanAttributeInfo("HelloPrefix", "java.lang.String",
                               "The prefix message to append to the session echo reply",
                               true, // isReadable
                               true, // isWritable
                               false), // isIs
        new MBeanAttributeInfo("EjbJndiName", "java.lang.String",
                               "The JNDI name of the session bean local home",
                               true, // isReadable
                               true, // isWritable
                               false) // isIs
    };

    /** 
     * These are the mbean operations we expose
     */
    private MBeanOperationInfo[] operations;
    
    /** 
     * We override this method to setup our echo operation info. It
     * could also be done in a ctor.
     */
    public ObjectName preRegister(MBeanServer server,
                                  ObjectName name)
        throws Exception
    {
        log.info("preRegister notification seen");
        
        operations = new MBeanOperationInfo[5];
        
        Class thisClass = getClass();
        Class[] parameterTypes = {String.class};
        Method echoMethod =
            thisClass.getMethod("echo", parameterTypes);
        String desc = "The echo op invokes the session bean echo method and"
            + " returns its value prefixed with the helloPrefix attribute value";
        operations[0] = new MBeanOperationInfo(desc, echoMethod);
            
        // Add the Service interface operations from our super class
        parameterTypes = new Class[0];
        Method createMethod =
            thisClass.getMethod("create", parameterTypes);
        operations[1] = new MBeanOperationInfo("The
                JBoss Service.create", createMethod);
        Method startMethod =
            thisClass.getMethod("start", parameterTypes);
        operations[2] = new MBeanOperationInfo("The
                JBoss Service.start", startMethod);
        Method stopMethod =
            thisClass.getMethod("stop", parameterTypes);
        operations[3] = new MBeanOperationInfo("The
                JBoss Service.stop", startMethod);
        Method destroyMethod =
            thisClass.getMethod("destroy", parameterTypes);
        operations[4] = new MBeanOperationInfo("The
                JBoss Service.destroy", startMethod);
        return name;
    }
    
    
    // --- Begin ServiceMBeanSupport overides
    protected void createService() throws Exception
    {
        log.info("Notified of create state");
    }

    protected void startService() throws Exception
    {
        log.info("Notified of start state");
        InitialContext ctx = new InitialContext();
        home = (EchoLocalHome) ctx.lookup(ejbJndiName);
    }

    protected void stopService()
    {
        log.info("Notified of stop state");
    }

    // --- End ServiceMBeanSupport overides
            
    public String getHelloPrefix()
    {
        return helloPrefix;
    }
    public void setHelloPrefix(String helloPrefix)
    {
        this.helloPrefix = helloPrefix;
    }
    
    public String getEjbJndiName()
    {
        return ejbJndiName;
    }
    public void setEjbJndiName(String ejbJndiName)
    {
        this.ejbJndiName = ejbJndiName;
    }
    
    public String echo(String arg)
        throws CreateException, NamingException
    {
        log.debug("Lookup EchoLocalHome@"+ejbJndiName);
        EchoLocal bean = home.create();
        String echo = helloPrefix + bean.echo(arg);
        return echo;
    }
    
    // --- Begin DynamicMBean interface methods
    /** 
     *  Returns the management interface that describes this dynamic
     *  resource.  It is the responsibility of the implementation to
     *  make sure the description is accurate.
     *
     * @return the management interface descriptor.
     */
    public MBeanInfo getMBeanInfo()
    {
        String classname = getClass().getName();
        String description = "This is an MBean that uses a session bean in the"
            + " implementation of its echo operation.";
        MBeanConstructorInfo[] constructors = null;
        MBeanNotificationInfo[] notifications = null;
        MBeanInfo mbeanInfo = new MBeanInfo(classname,
                                            description, attributes,
                                            constructors, operations,
                                            notifications);
        // Log when this is called so we know when in the
        lifecycle this is used
            Throwable trace = new Throwable("getMBeanInfo trace");
        log.info("Don't panic, just a stack
                trace", trace);
        return mbeanInfo;
    }
    
    /** 
     *  Returns the value of the attribute with the name matching the
     *  passed string.
     *
     * @param attribute the name of the attribute.
     * @return the value of the attribute.
     * @exception AttributeNotFoundException when there is no such
     * attribute.
     * @exception MBeanException wraps any error thrown by the
     * resource when
     * getting the attribute.
     * @exception ReflectionException wraps any error invoking the
     * resource.
     */
    public Object getAttribute(String attribute)
        throws AttributeNotFoundException, 
               MBeanException, 
               ReflectionException
    {
        Object value = null;
        if (attribute.equals("HelloPrefix")) {
            value = getHelloPrefix();
        } else if(attribute.equals("EjbJndiName")) {
            value = getEjbJndiName();
        } else {
            throw new AttributeNotFoundException("Unknown
                attribute("+attribute+") requested");
        }
        return value;
    }
            
    /** 
     * Returns the values of the attributes with names matching the
     * passed string array.
     *
     * @param attributes the names of the attribute.
     * @return an {@link AttributeList AttributeList} of name
     * and value pairs.
     */
    public AttributeList getAttributes(String[] attributes)
    {
        AttributeList values = new AttributeList();
        for (int a = 0; a < attributes.length; a++) {
            String name = attributes[a];
            try {
                Object value = getAttribute(name);
                Attribute attr = new Attribute(name, value);
                values.add(attr);
            } catch(Exception e) {
                log.error("Failed to find attribute: "+name, e);
            }
        }
        return values;
    }
            
    /**
     *  Sets the value of an attribute. The attribute and new value
     *  are passed in the name value pair {@link Attribute
     *  Attribute}.
     *
     * @see javax.management.Attribute
     *
     * @param attribute the name and new value of the attribute.
     * @exception AttributeNotFoundException when there is no such
     * attribute.
     * @exception InvalidAttributeValueException when the new value
     * cannot be converted to the type of the attribute.
     * @exception MBeanException wraps any error thrown by the
     * resource when setting the new value.
     * @exception ReflectionException wraps any error invoking the
     * resource.
     */
    public void setAttribute(Attribute attribute)
        throws AttributeNotFoundException, 
               InvalidAttributeValueException,
               MBeanException, 
               ReflectionException
    {
        String name = attribute.getName();
        if (name.equals("HelloPrefix")) { 
            String value = attribute.getValue().toString();
            setHelloPrefix(value);
        } else if(ename.equals("EjbJndiName")) {
            String value = attribute.getValue().toString();
            setEjbJndiName(value);
        } else {
            throw new AttributeNotFoundException("Unknown attribute("+name+") requested");
        }
    }
            
    /**
     * Sets the values of the attributes passed as an
     * {@link AttributeList AttributeList} of name and new
     * value pairs.
     *
     * @param attributes the name an new value pairs.
     * @return an {@link AttributeList AttributeList} of name and
     * value pairs that were actually set.
     */
    public AttributeList setAttributes(AttributeList attributes)
    {
        AttributeList setAttributes = new AttributeList();
        for(int a = 0; a < attributes.size(); a++) {
            Attribute attr = (Attribute) attributes.get(a);
            try {
                setAttribute(attr);
                setAttributes.add(attr);
            } catch(Exception ignore) {
            }
        }
        return setAttributes;
    }
    
    /**
     *  Invokes a resource operation.
     *
     *  @param actionName the name of the operation to perform.
     *  @param params the parameters to pass to the operation.
     *  @param signature the signartures of the parameters.
     *  @return the result of the operation.
     *  @exception MBeanException wraps any error thrown by the
     *  resource when performing the operation.
     *  @exception ReflectionException wraps any error invoking the
     *  resource.
     */
    public Object invoke(String actionName, Object[] params,
                         String[] signature)
        throws MBeanException,
               ReflectionException
    {
        Object rtnValue = null;
        log.debug("Begin invoke, actionName="+actionName);
        try {
            if (actionName.equals("echo")) {
                String arg = (String) params[0];
                rtnValue = echo(arg);
                log.debug("Result: "+rtnValue);
            } else if (actionName.equals("create")) {
                super.create();
            } else if (actionName.equals("start")) {
                super.start();
            } else if (actionName.equals("stop")) {
                super.stop();
            } else if (actionName.equals("destroy")) {
                super.destroy();
            } else {
                throw new JMRuntimeException("Invalid state,
                don't know about op="+actionName);
            }
        } catch(Exception e) {
            throw new ReflectionException(e, "echo failed");
        }


        log.debug("End invoke, actionName="+actionName);
        return rtnValue;
    }
    
    // --- End DynamicMBean interface methods
    
}

여러분이 믿거나 말거나, 제시된 것은 매우 사소한 MBean 입니다. 코드의 대부분은 MBean 서버로부터 콜백을 처리하고 MBean 메터데이터를 제공하기 위한 것들입니다. 동적인 MBean이 관리 인터페이스의 원하는 것은 무엇이든 노출시킬 수 있기 때문에 이러한 코드들은 필요하게 됩니다. 사실 동적인 MBean은 getMBeanInfo 메쏘드로 부터 다른 메터데이터 값을 반환함으로써 간단히 런타임에서 관리 인터페이스를 변경시킬 수 있습니다. 물론 클라이언트가 이러한 동적인 객체를 반기지 않을 수도 있지만, MBean 서버는 동적인 MBean의 인터페이스 변경에 대한 어떠한 금지도 할 수 없습니다.

이번 예제에서는 두 가지 항목에 주의해야 합니다. 첫번째는 MBean의 몇몇 기능성을 위해 MBean이 어떻게 EJB에 의존하는지와 두번째로 동적인 관리 인터페이스를 갖는 MBean을 어떻게 만드느냐입니다. 이번 예제를 위해 정적인 인터페이스를 갖는 표준 MBean을 작성한다고 하면, 다음과 같아질 것입니다.

public interface EjbMBeanAdaptorMBean
{
    public String getHelloPrefix();
    public void setHelloPrefix(String prefix);
    public String getEjbJndiName();
    public void setEjbJndiName(String jndiName);
    public String echo(String arg) throws CreateException, NamingException;
    public void create() throws Exception;
    public void start() throws Exception;
    public void stop();
    public void destroy();
}

67-83 라인을 보면, 이 부분에서 MBean 오퍼레이션 메터데이터가 만들어지고 있습니다. echo(String), create(), start(), stop()destroy() 오퍼레이션들이 대응되는 java.lang.reflect.Method 객체의 획득과 설명을 추가로 정의됩니다. 코드를 통해 인터페이스의 구현이 이루어지는 부분에 대해서 논의해보고 어떻게 MBean이 EJB를 사용하는지를 살펴보도록 하겠습니다. 40-51 라인을 시작으로 두개의 MBeanAttributeInfo 인스턴스가 MBean의 속성을 정의함으로써 생성됩니다. 이 속성들은 getHelloPrefix/setHelloPrefix와 정적인 인터페이스인 getEjbJndiName/setEjbJndiName에 대응됩니다. 왜 동적인 MBean의 사용이 요구되는지에 대한 대답중에 하나는 속성 메터데이터와 함께 서술적인 텍스트를 관련시킬 수 있게 하는 능력을 갖을 수 있다는 것입니다. 정적인 인터페이스로는 이러한 일을 할 수 없습니다.

88-103 라인은 JBoss 서비스 생명 주기 콜백에 대응됩니다. 우리가 ServiceMBeanSupport 유틸리티 클래스를 서브 클래싱하였기 때문에, 서비스 인터페이스의 create, start, 그리고 stop 메쏘드 대신 createService, startService, 그리고 stopService 템플릿 콜백을 오버라이드하였습니다. startService 메쏘드가 호출되기 전까지는 EJB의 EchoLocalHome 인터페이스를 살펴보는 시도를 할 수 없다는 것에 주의해야 합니다. 생명 주기의 초반부에서는 EJB 컨테이너가 홈 인터페이스와 바인딩되는 연결점을 갖지 않았기 때문에 홈 인터페이스에 액세스하려는 시도들은 JNDI내에서 그 이름을 찾을 수 없다는 결과를 얻습니다. 이러한 종속성때문에 우리는 EJB 컨테이너가 시작되기전에 서비스가 시작되지 않도록 하기위해 EchoLocal EJB 컨테이너에 종속되는 MBean 서비스를 지정해줄 필요가 생기게 됩니다. 우리는 서비스 서술자에서 이러한 종속성을 지정한 것을 볼 수 있습니다.

105-121 라인은 HelloPrefixEjbJndiName 속성 처리자(accessor) 인터페이스입니다. 이것들은 MBean 서버를 통해 호출되는 getAttribute/setAttribute에 응답하기 위해 호출됩니다.

123-130 라인은 echo(String) 오퍼레이션 구현부에 대응됩니다. 이 메쏘드는 EchoLocal.echo(String) EJB 메쏘드를 호출합니다. startService 메쏘드에서 획득된 EchoLocalHome을 사용하여 로컬 빈 인터페이스가 생성됩니다.

클래스의 나머지 부분에서는 동적인 MBean 인터페이스 구현을 만들게 됩니다. 133-152 라인은 MBean 메터데이터 처리자 콜백에 대응됩니다. 이 메쏘드는 javax.management.MBeanInfo 객체의 형식으로 MBean 관리 인터페이스의 설명을 반환합니다. 이것은 생성자와 통지 정보뿐만 아니라 description, MBeanAttributeInfo 그리고 미리 생성된 MBeanOperationInfo 메터데이터로 만들어집니다. 이 MBean은 갖고 있는 정보가 없으므로 어떤 특별한 생성자나 통지 정보가 필요없습니다.

154-258 라인에서는 속성 액세스 요청을 처리합니다. 이부분은 장황스럽고 오류가 빈번히 날 수 있으므로 이 메쏘드들을 자동으로 생성하거나 도와줄 수 있는 툴킷이나 인프라스트럭쳐를 사용하는 것이 낫습니다. XBeans을 호출하는 XML 기반의 MBean 프레임워크 모델은 현재 JBoss에서 연구중에 있습니다. 현재 동적 MBean 프레임워크에는 이것 말고는 다른 것은 없습니다.

260-310 라인은 오퍼레이션 호출의 발송 엔트리 포인트에 대응됩니다. 여기서 요청 오퍼레이션 액션 이름이 MBean에서 처리하는 것에 점검을 하고 적절한 메쏘드를 호출하게 됩니다.

MBean을 위한 jboss-service.xml 서술자가 아래에 있습니다. EJB 컨테이너 MBean의 종송성은 굵은체로 강조되어 있습니다. EJB 컨테이너 MBean ObjectName의 포맷은 다음과 같습니다:

"jboss.j2ee:service=EJB,jndiName=" + <home-jndi-name>

여기서 <home-jndi-name>은 EJB 홈 인터페이스 JNDI 이름입니다.

<server>
    <mbean code="org.jboss.chap2.ex3.EjbMBeanAdaptor"
           name="jboss.book:service=EjbMBeanAdaptor">
        <attribute name="HelloPrefix">AdaptorPrefix</attribute>
        <attribute name="EjbJndiName">local/chap2.EchoBean</attribute>
        <depends>jboss.j2ee:service=EJB,jndiName=local/chap2.EchoBean</depends>
    </mbean>
</server>    

다음과 같은 명령으로 예제 ear을 배치시킵니다:

[starksm@banshee examples]$ ant -Dchap=chap2 -Dex=3 run-example
...
run-example3:
     [copy] Copying 1 file to /tmp/jboss-3.2.6/server/default/deploy

서버 콘솔상에는 다음과 비슷한 메시지들이 보여질 것입니다:

14:57:12,906 INFO  [EARDeployer] Init J2EE application: file:/private/tmp/jboss-3.2.6/serv
er/default/deploy/chap2-ex3.ear
14:57:13,044 INFO  [EjbMBeanAdaptor] Don't panic, just a stack trace
java.lang.Throwable: getMBeanInfo trace
        at org.jboss.chap2.ex3.EjbMBeanAdaptor.getMBeanInfo(EjbMBeanAdaptor.java:153)
        at org.jboss.mx.server.RawDynamicInvoker.getMBeanInfo(RawDynamicInvoker.java:172)
        at org.jboss.mx.server.RawDynamicInvoker.preRegister(RawDynamicInvoker.java:187)
...
14:57:13,088 INFO  [EjbMBeanAdaptor] preRegister notification seen
14:57:13,093 INFO  [EjbMBeanAdaptor] Don't panic, just a stack trace
java.lang.Throwable: getMBeanInfo trace
        at org.jboss.chap2.ex3.EjbMBeanAdaptor.getMBeanInfo(EjbMBeanAdaptor.java:153)
        at org.jboss.mx.server.RawDynamicInvoker.getMBeanInfo(RawDynamicInvoker.java:172)
        at org.jboss.mx.server.registry.BasicMBeanRegistry.registerMBean(BasicMBeanRegistr
y.java:207)
...
14:57:13,117 INFO  [EjbMBeanAdaptor] Don't panic, just a stack trace
java.lang.Throwable: getMBeanInfo trace
        at org.jboss.chap2.ex3.EjbMBeanAdaptor.getMBeanInfo(EjbMBeanAdaptor.java:153)
        at org.jboss.mx.server.RawDynamicInvoker.getMBeanInfo(RawDynamicInvoker.java:172)
        at org.jboss.mx.server.registry.BasicMBeanRegistry.registerMBean(BasicMBeanRegistr
y.java:235)
...        
14:57:13,140 WARN  [EjbMBeanAdaptor] Unexcepted error accessing MBeanInfo for null
java.lang.NullPointerException
        at org.jboss.system.ServiceMBeanSupport.postRegister(ServiceMBeanSupport.java:418)
        at org.jboss.mx.server.RawDynamicInvoker.postRegister(RawDynamicInvoker.java:226)
        at org.jboss.mx.server.registry.BasicMBeanRegistry.registerMBean(BasicMBeanRegistr
y.java:312)
...
14:57:13,203 INFO  [EjbMBeanAdaptor] Don't panic, just a stack trace
java.lang.Throwable: getMBeanInfo trace
        at org.jboss.chap2.ex3.EjbMBeanAdaptor.getMBeanInfo(EjbMBeanAdaptor.java:153)
        at org.jboss.mx.server.RawDynamicInvoker.getMBeanInfo(RawDynamicInvoker.java:172)
        at org.jboss.mx.server.MBeanServerImpl.getMBeanInfo(MBeanServerImpl.java:481)
... 
14:57:13,232 INFO  [EjbMBeanAdaptor] Don't panic, just a stack trace
java.lang.Throwable: getMBeanInfo trace
        at org.jboss.chap2.ex3.EjbMBeanAdaptor.getMBeanInfo(EjbMBeanAdaptor.java:153)
        at org.jboss.mx.server.RawDynamicInvoker.getMBeanInfo(RawDynamicInvoker.java:172)
        at org.jboss.mx.server.MBeanServerImpl.getMBeanInfo(MBeanServerImpl.java:481)
...
14:57:13,420 INFO  [EjbModule] Deploying Chap2EchoInfoBean
14:57:13,443 INFO  [EjbModule] Deploying chap2.EchoBean
14:57:13,488 INFO  [EjbMBeanAdaptor] Don't panic, just a stack trace
java.lang.Throwable: getMBeanInfo trace
        at org.jboss.chap2.ex3.EjbMBeanAdaptor.getMBeanInfo(EjbMBeanAdaptor.java:153)
        at org.jboss.mx.server.RawDynamicInvoker.getMBeanInfo(RawDynamicInvoker.java:172)
        at org.jboss.mx.server.MBeanServerImpl.getMBeanInfo(MBeanServerImpl.java:481)
...
14:57:13,542 INFO  [EjbMBeanAdaptor] Don't panic, just a stack trace
java.lang.Throwable: getMBeanInfo trace
        at org.jboss.chap2.ex3.EjbMBeanAdaptor.getMBeanInfo(EjbMBeanAdaptor.java:153)
        at org.jboss.mx.server.RawDynamicInvoker.getMBeanInfo(RawDynamicInvoker.java:172)
        at org.jboss.mx.server.MBeanServerImpl.getMBeanInfo(MBeanServerImpl.java:481)
...
14:57:13,558 INFO  [EjbMBeanAdaptor] Begin invoke, actionName=create
14:57:13,560 INFO  [EjbMBeanAdaptor] Notified of create state
14:57:13,562 INFO  [EjbMBeanAdaptor] End invoke, actionName=create
14:57:13,604 INFO  [EjbMBeanAdaptor] Don't panic, just a stack trace
java.lang.Throwable: getMBeanInfo trace
        at org.jboss.chap2.ex3.EjbMBeanAdaptor.getMBeanInfo(EjbMBeanAdaptor.java:153)
        at org.jboss.mx.server.RawDynamicInvoker.getMBeanInfo(RawDynamicInvoker.java:172)
        at org.jboss.mx.server.MBeanServerImpl.getMBeanInfo(MBeanServerImpl.java:481)
        at org.jboss.mx.server.MBeanServerImpl.isInstanceOf(MBeanServerImpl.java:639)
... 
14:57:13,621 INFO  [EjbMBeanAdaptor] Don't panic, just a stack trace
java.lang.Throwable: getMBeanInfo trace
        at org.jboss.chap2.ex3.EjbMBeanAdaptor.getMBeanInfo(EjbMBeanAdaptor.java:153)
        at org.jboss.mx.server.RawDynamicInvoker.getMBeanInfo(RawDynamicInvoker.java:172)
        at org.jboss.mx.server.MBeanServerImpl.getMBeanInfo(MBeanServerImpl.java:481)
        at org.jboss.mx.util.JMXInvocationHandler.<init>(JMXInvocationHandler.java:110)
        at org.jboss.mx.util.MBeanProxy.get(MBeanProxy.java:76)
        at org.jboss.mx.util.MBeanProxy.get(MBeanProxy.java:64)
 14:57:13,641 INFO  [EjbMBeanAdaptor] Begin invoke, actionName=getState
14:57:13,942 INFO  [EjbMBeanAdaptor] Begin invoke, actionName=start
14:57:13,944 INFO  [EjbMBeanAdaptor] Notified of start state
14:57:13,951 INFO  [EjbMBeanAdaptor] Testing Echo
14:57:13,983 INFO  [EchoBean] echo, info=echo info, arg=, arg=startService
14:57:13,986 INFO  [EjbMBeanAdaptor] echo(startService) = startService
14:57:13,988 INFO  [EjbMBeanAdaptor] End invoke, actionName=start
14:57:13,991 INFO  [EJBDeployer] Deployed: file:/private/tmp/jboss-3.2.6/server/default/tm
p/deploy/tmp1418chap2-ex3.ear-contents/chap2-ex3.jar
14:57:14,075 INFO  [EARDeployer] Started J2EE application: file:/private/tmp/jboss-3.2.6/s
erver/default/deploy/chap2-ex3.ear

스택 트레이스는 예외상황이 아닙니다. 이것은 150 라인의 EjbMBeanAdaptor 코드로부터 만들어지는 것으로써 클라이언트가 MBean의 능력을 찾아보기 원할때 MBean 인터페이스에 요청하는 것을 보여주기 위한 것입니다. EJB 컨테이너([EjbModule]로 시작되는 라인)는 예제 MBean([EjbMBeanAdaptor]로 시작되는 라인)이 구동되기 전에 먼저 구동된다는 것에 주의하십시오.

자 이제 JMX 콘솔 웹 어플리케이션에서 echo 메쏘드를 호출해봅니다. http://localhost:8080/jmx-console/HtmlAdaptor?action=inspectMBean&name=jboss.book%3Aservice%3DEjbMBeanAdaptor 를 웹 브라우저에서 열은 후, echo 오퍼레이션 섹션이 나타날때까지 스크롤을 밑으로 내려주십시오. 그림 2.20, “EjbMBeanAdaptor MBean 오퍼레이션 JMX 콘솔 뷰”와 같은 모습의 뷰가 보일 것입니다.

EjbMBeanAdaptor MBean 오퍼레이션 JMX 콘솔 뷰

그림 2.20. EjbMBeanAdaptor MBean 오퍼레이션 JMX 콘솔 뷰

그림에서 보이듯이, 우리는 이미 ParamValue 텍스트 필드에 "-echo-arg" 라는 문자열 인수를 입력하였습니다. 호출버튼을 누르면, 결과 페이지에 "AdaptorPrefix-echo-arg" 라는 문자열을 얻을 수 있습니다. 서버 콘솔에는 JMX 콘솔에 의해 발생되는 다양한 메터데이터 질의들로부터 여러 스택 트레이스들이 보여지게 되며, MBean이 호출한 메쏘드의 디버깅 라인은 다음과 같습니다:

10:51:48,671 INFO [EjbMBeanAdaptor] Begin invoke, actionName=echo
10:51:48,671 INFO [EjbMBeanAdaptor] Lookup EchoLocalHome@local/chap2.EchoBean
10:51:48,687 INFO [EchoBean] echo, info=echo info, arg=, arg=-echo-arg
10:51:48,687 INFO [EjbMBeanAdaptor] Result: AdaptorPrefix-echo-arg
10:51:48,687 INFO [EjbMBeanAdaptor] End invoke, actionName=echo

2.5. JBoss 배치자 아키텍쳐

JBoss는 핵심 JBoss JMX 마이크로커널로 컴포넌트들을 조합시킬 수 있도록 하는 확장가능한 배치 아키텍쳐를 갖습니다. 그림 2.21, “배치 레이어 클래스”에서는 배치 레이어내의 클래스를 보여주고 있습니다.

배치 레이어 클래스

그림 2.21. 배치 레이어 클래스

MainDeployer는 배치 엔트리의 포인트입니다. 컴포넌트의 배치를 요청은 MainDeployer로 전송되고 배치 처리를 위한 subdeployer 구현성이 있는지를 결정하고 이것이 가능한 경우에는 subdeployer에 배치를 위임합니다. 이러한 예를 우리는 MainDeployer가 SARDeployer를 사용해서 MBean 서비스들을 배치시키는 과정에서 보았었습니다. 현재 JBoss에 포함되어 있는 배치자들은 다음과 같습니다:

  • AbstractWebContainer: 이 subdeployer는 웹 어플리케이션 아카이브(WARs)를 처리합니다. 배치 아키아브들과 "war" 접미사로 끝나는 디렉토리들을 받을 수 있습니다. WARs에는 WEB-INF/web.xml 서술자가 있어야만 하고 WEB-INF/jboss-web.xml 서술자도 가질 수 있습니다.
  • EARDeployer: 이 subdeployer는 엔터프라이즈 어플리케이션 아카이브(EARs)를 처리합니다. 배치 아카이브와 "ear" 접미사로 끝나는 디렉토리들을 받을 수 있습니다. EARs는 반드시 META-INF/application.xml 서술자를 갖아야 하고, META-INF/jboss-app.xml 서술자도 가질 수 있습니다.
  • EJBDeployer: 이 subdeployer는 엔터프라이즈 빈 jars를 처리합니다. 배치 아카이브와 "jar" 접미사로 끝나는 디렉토리들을 받을 수 있습니다. EJB jars는 META-INF/ejb-jar.xml 서술자를 반드시 갖아야 하고, META-INF/jboss.xml 서술자도 가질 수 있습니다.
  • JARDeployer: 이 subdeployer는 라이브러리 jar 아카이브를 처리합니다. 아카이브에 대한 유일한 제약사항은 WEB-INF 디렉토리를 포함할 수 없다는 것입니다.
  • RARDeployer: 이 subdeployer는 JCA 리소스 아카이브(RARs)를 처리합니다. 배치 아카이브와 "rar" 접미사로 끝나는 디렉토리들을 받을 수 있습니다. RARs는 반드시 META-INF/ra.xml 서술자를 갖아야만 합니다.
  • SARDeployer: 이 subdeployer에서는 JBoss MBean 서비스 아카이브(SARs)를 처리합니다. "service.xml"로 끝나는 단일 XML 파일뿐만 아니라 배치 아카이브와 "sar" 접미사로 끝나는 디렉토리도 받을 수 있습니다. jars인 SARs에는 반드시 META-INF/jboss-service.xml 서술자를 갖아야만 합니다.

MainDeployer, JARDeployr 그리고 SARDeployer는 JBoss 서버 핵심내에 밀접하게 코딩된 배치자들입니다. AbstractWebContainer, EARDeployer, EJBDeployer 및 RARDeployer들은 addDeployer(SubDeployer) 오퍼레이션을 사용하여 MainDeployer와 함께 배치자로써 자신을 등록한 MBean 서비스들입니다. SubDeployer 인터페이스는 다음과 같습니다.

public interface SubDeployer
{
    /**
     * The <code>accepts</code> method is called by MainDeployer to
     * determine which deployer is suitable for a DeploymentInfo.
     *
     * @param sdi a <code>DeploymentInfo</code> value
     * @return a <code>boolean</code> value
     *
     * @jmx:managed-operation
     */
    boolean accepts(DeploymentInfo sdi);
    
    /**
     * The <code>init</code> method lets the deployer set
     * a few properties of the DeploymentInfo, such as the watch url.
     *
     * @param sdi a <code>DeploymentInfo</code> value
     * @throws DeploymentException if an error occurs
     *
     * @jmx:managed-operation
     */
    void init(DeploymentInfo sdi) throws DeploymentException;
    
    /**
     * Set up the components of the deployment that do not
     * refer to other components
     *
     * @param sdi a <code>DeploymentInfo</code> value
     * @throws DeploymentException Failed to deploy
     *
     * @jmx:managed-operation
     */
    void create(DeploymentInfo sdi) throws DeploymentException;
    
    /**
     * The <code>start</code> method sets up relationships
     * with other components.
     *
     * @param sdi a <code>DeploymentInfo</code> value
     * @throws DeploymentException if an error occurs
     *
     * @jmx:managed-operation
     */
    void start(DeploymentInfo sdi) throws DeploymentException;
    
    /**
     * The <code>stop</code> method removes relationships
     * between components.
     *
     * @param sdi a <code>DeploymentInfo</code> value
     * @throws DeploymentException if an error occurs
     *
     * @jmx:managed-operation
     */
    void stop(DeploymentInfo sdi) throws DeploymentException;
    
    /**
     * The <code>destroy</code> method
     removes individual components
     *
     * @param sdi a <code>DeploymentInfo</code> value
     * @throws DeploymentException if an error occurs
     *
     * @jmx:managed-operation
     */
    void destroy(DeploymentInfo sdi) throws DeploymentException;
} 

DeploymentInfo 객체는 배치가능한 컴포넌트의 완전한 상태를 감싸는 기본적인 데이터 구조입니다. MainDeployer가 배치 요청을 받게되면, MainDeployer에 등록된 subdeployer를 통해 반복하면서 accepts(DeploymentInfo) 메쏘드를 호출합니다. true를 리턴하는 첫 번째 subdeployer가 선택되고 배치 deployer와 MainDeployer는 subdeployer쪽으로 init, create, start, stop 그리고 destroy deployment life cycle 오퍼레이션들을 위임하게 됩니다.

2.5.1. Deployers 와 ClassLoaders

Deployer는 컴포넌트가 JBoss 서버에 조합되는 메커니즘입니다. 또한 Deployers는 대부분의 UCL 인스턴스 생성자이기도 하며, 프라이머리 생성자는 MainDeployer입니다. MainDeployer는 자신의 init 메쏘드가 실행되는 동안 일찌감치 배치를 위한 UCL을 생성합니다. UCL은 DeploymentInfo.createClassLoaders() 메쏘드 호출에 의해 만들어집니다. 3.0.5RC1 버전이 릴리즈되면서부터 최상위 DeploymentInfo만이 실제로 UCL을 만들게 되었습니다. 모든 subdeployment는 자신의 경로로써 deployment URL을 사용하는 독립 URLClassLoader를 갖습니다. 이 URLClassLoader는 배치 서술자와 같은 리소스의 로딩을 로칼라이즈시킬때 사용합니다. 그림 2.22, “EAR 배치에 관련된 클래스 로더를 설명하는 그림”에서 Deployers, DeploymentInfos 및 클래스 로더사이의 상호관계를 설명하고 있습니다.

EAR 배치에 관련된 클래스 로더를 설명하는 그림

그림 2.22. EAR 배치에 관련된 클래스 로더를 설명하는 그림

위의 그림은 EJB와 WAR subdeployment를 갖는 EAR 배치를 설명하고 있습니다. EJB 배치는 자신의 manifest를 통해 lib/util.jar 유틸리티 jar를 참조하고 있습니다. WAR는 WEB-INF/lib/jbosstest-web-util.jar 뿐만 아니라 자신의 WEB-INF/classes 디렉토리내에 클래스를 포함하고 있습니다. 각각의 배치는 배치 아카이브를 가르키는 하나의 URLClassLoader를 갖는 DeploymentInfo 인스턴스를 갖습니다. some.ear에 관련된 DeploymentInfo만이 생성된 하나의 UCL을 갖을 수 있습니다. ejbs.jar와 web.war DeploymentInfo는 some.ear UCL 클래스경로에 그들의 배치 아카이브를 추가하고, 이 UCL을 자신들의 배치 UCL로 공유합니다. 또한 EJBDeployer는 EAR UCL에 어떠한 manifest jars 도 추가시킬 수 있습니다.

오직 자신의 WAR 아카이브를 DeploymentInfo UCL 클래스경로에 추가한다는 것이 WARDeployer가 다른 deployers들과 다르게 동작하는 차이가 됩니다. WAR WEB-INF/classes와 WEB-INF/lib 로부터 클래스를 로등하는 것은 서블릿 컨테이너 클래스 로더에 의해 처리됩니다. 서블릿 컨테이너 클래스 로더는 그들의 부모 클래스 로더로써의 WAR DeploymentInfo UCL에 위임하지만, 서버 컨테이너 클래스 로더는 JBoss 클래스 로더 레포지터리의 일부분은 아닙니다. 그러므로, WAR내의 클래스들은 다른 컴포넌트들에게는 보이지 않습니다. 웹 어플리케이션 컴포넌트들과 EJBs와 같은 다른 컴포넌트 사이에서 공유될 필요가 있는 클래스와 MBeans은 SAR나 EJB 배치속에 클래스를 포함시키거나 manifest Class-Path 엔트리를 통해 공유된 클래스를 포함하는 jar를 참조하도록 하여 공유된 클래스 로더 레포지터리에 로드될 필요가 있습니다. SAR를 사용할 경우, 서비스 배치내에서 SAR의 classpath 요소는 jar manifest Class-Path와 같은 동일한 용도로 활용됩니다.

2.6.  SNMP를 통해 MBean 이벤트 노출시키기

3.2.2 버전부터 MBeans에서 방출되는 JMX 통지를 인터셉트하는데 사용할 수 있는 snmp-adaptor 서비스가 추가되었습니다. 이러한 snmp-adaptor를 통해 인터셉트한 JMX 통지가 SNMP 관리자로 전송됩니다. 이러한 관점에서 보면, snmp-adaptor는 마치 SNMP 에이전트처럼 동작되게 됩니다. 앞으로 릴리즈되는 버전에서는 보다 완전한 에이전트의 get/set 기능성을 지원하여 MBean 속성이나 오퍼레이션에 맵핑할 수 있게 될 것입니다.

JBoss를 보다 높은 순위를 갖는 시스템/네트워크 관리 플랫폼(가령 HP의 오픈뷰)에 MBeans를 보여줄 수 있도록 하여 통합시킬 때 사용할 수 있습니다. MBean을 개발하는 개발자들은 어떠한 중요한 이벤트(즉, 서버의 coldstart등)에 대한 통지를 만들어주는 MBeans를 이용할 수 있습니다. 그런 다음 아답터는 이러한 통지정보들을 SNMP 트랩쪽에 인터셉트하고 맵핑하도록 설정할 수 있습니다. 아답터는 SNMP 엔진으로 OpenNMS의 JoeSNMP 패키지를 사용합니다.

SNMP 서비스는 snmp-adaptor.sar내에서 설정되어집니다. 이 서비스는 오로지 all 설정내에서만 사용이 가능하므로 여러분이 이것을 사용하려면 여러분의 설정내에 복사할 필요가 있습니다. snmp-adaptor.sar 디렉토리내에는 SNMP 서비스를 컨트롤하는 2개의 설정 파일이 있습니다.

SNMP AgentService MBean은 snmp-adaptor.sar/META-INF/jboss-service.xml에서 설정합니다. 설정 매개변수는 다음과 같습니다:

  • HeartBeatPeriod: heartbeat 통지가 생성되는 초단위의 주기.
  • ManagersResName: managers.xml파일의 리소스 이름을 지정합니다.
  • NotificationMapResName: notications.xml 파일의 리소스 이름을 지정합니다.
  • TrapFactoryClassName: SNMP V1과 V2 트랩쪽으로 JMX 통지의 번역을 관장하는 org.jboss.jmx.adaptor.snmp.agent.TrapFactory 구현 클래스입니다.
  • TimerName: heartbeat 통지에 사용하는 JMX 타이머 서비스의 JMX ObjectName을 지정합니다.
  • SubscriptionList: MBeans과 통지를 수신하는 대상을 지정합니다.

SNMP 관리자 파일의 스키마

그림 2.23. SNMP 관리자 파일의 스키마

트랩 맵핑 파일로의 통지 스키마

그림 2.24. 트랩 맵핑 파일로의 통지 스키마

2.6.1. 트랩 서비스로의 이벤트

TrapdService는 SNMP 관리자처럼 동작하는 간단한 MBean 입니다. 들어오는 트랩을 수신할 수 있는 설정가능한 포트이며 시스템 로거를 사용하여 DEBUG 메시지로써 이것을 로깅합니다. 여러분은 log4j 설정을 수정하여 출력되는 로그를 파일로 바꿀 수 있습니다. SnmpAgentService와 TrapdService는 서로 종속적이지 않습니다.

2.7. 서비스로 원격 액세스하기, 분리된 호출자(Invoker)

임의의 기능성을 통합할 수 있는 능력을 위해 허용된 MBean 서비스의 개념에 더하여, JBoss는 원격에 위치한 클라이언트에서 임의의 프로토콜을 사용하여 노출된 인터페이스에 접근할 수 있는 MBean 서비스가 가능한 분리된 호출자 개념 또한 갖고 있습니다. 이 개념은 EJB 컨테이너를 위해 3.0 버전에서 처음 도입되었으며 3.2버전에 이르러서는 어떠한 MBean 서비스에 대해서도 일반화 될 수 있도록 발전되어왔습니다. 분리된 호출자라는 개념은 리모팅과 서비스에 엑세스하는데 사용된 프로토콜이 기능적인 측면 혹은 컴포넌트에 비종속적인 서비스라는 것입니다. 따라서 어느 누구라도 RMI/JRMP, RMI/HTTP, RMI/SOAP 혹은 그 어떤 임의의 커스텀 전송방식을 통해서도 사용이 가능한 네이밍 서비스를 만들 수 있습니다.

이제 분리된 호출자 아키텍쳐를 컴포넌트들이 포함된 개념과 함께 논의해보도록 하겠습니다. 분리된 호출자 아키텍쳐에서 메인 컴포넌트가 되는 것은 그림 2.25, “분리된 호출자 아키텍쳐에서의 메인 컴포넌트” 에 나타내었습니다.

분리된 호출자 아키텍쳐에서의 메인 컴포넌트

그림 2.25. 분리된 호출자 아키텍쳐에서의 메인 컴포넌트

클라이언트쪽에서 본다면, MBean 서비스의 인터페이스들을 노출시켜주는 클라이언트 프록시가 존재하게 됩니다. 이는 EJB home과 remote 인터페이스에서 사용한 똑똑하고, 컴파일이 필요없는 동적인 프록시와 동일합니다. 임의의 서비스와 EJB 프록시들 사이에서 유일한 차이점이 되는 것은 프록시내에서 클라이언트쪽 인터셉터를 찾는 것뿐만 아니라 일련의 인터페이스들을 노출시킨다는 것입니다. 클라이언트의 인터셉터들은 클라이언트 프록시내부에서 찾을 수 있는 사각형으로 표시되어 있습니다. 하나의 인터셉터는 메쏘드 호출과(혹은) 반환되는 값들의 변환을 허용하는 패턴의 어셈블리 라인 타입입니다. 클라이언트는 보통 JNDI와 같은 lookup 메커니즘을 통해 프록시를 얻습니다. 그림 2.25, “분리된 호출자 아키텍쳐에서의 메인 컴포넌트”에 RMI가 표시되어 있기는 하지만, 노출된 인터페이스와 타입들에서 실제로 필요로 하는 것은 전송계층(tansport layer) 뿐만 아니라 JNDI를 통해서 클라이언트와 서버사이를 직렬화(serializable)가능하게 하는 것입니다.

전송계층의 선택은 그림 2.26, “분리된 호출자 아키텍쳐에서의 메인 컴포넌트”에 "Invoker Interceptor"로 표시되어 있는 클라이언트 프록시내의 마지막 인터셉터에 의해 결정됩니다. 호출자 인터셉터(invoker interceptor)에는 서버측 "분리된 호출자" MBean 서비스의 전송 지정 스터브 참조가 포함되어 있습니다. 또한 호출자 인터셉터에서는 타겟 MBean으로써 동일한 VM내에서 발생되는 호출의 최적화까지도 처리됩니다. 호출자 인터셉터가 이를 발견하면 타겟 MBean과 함께 호출에 간단히 전달되는 call-by-reference 호출자쪽으로 호출을 넘겨주는 경우가 됩니다.

분리된 호출자 서비스는 분리된 호출자 핸들을 전송을 통해 범용적인 호출 오퍼레이션의 사용이 가능하도록 할 책임이 있습니다. 호출자 인터페이스는 범용적인 호출 오퍼레이션을 나타냅니다.

package org.jboss.invocation;
             
import java.rmi.Remote;
import org.jboss.proxy.Interceptor;
import org.jboss.util.id.GUID;
             
             
public interface Invoker
    extends Remote
{
    GUID ID = new GUID();
        
    Object invoke(Invocation invocation) throws Exception;
}

호출자 인터페이스는 RMI와 호환될 수 있도록 Remote를 확장합니다만, 이것이 호출자가 반드시 RMI 서비스 스텁을 노출해야만 한다는 것을 의미하지는 않습니다. 분리된 호출자 서비스는 그림 2.25, “분리된 호출자 아키텍쳐에서의 메인 컴포넌트”에서 표현된 것처럼 자신이 지정된 전송계층을 통하여 org.jboss.invocation.Invocation 객체로서 표현되는 호출들을 받아들이고, 호출의 언마샬링과 호출을 목적지 MBean 서비스로의 포워딩 하는 전송계층 게이트웨이(transport gateway)로써 간단하게 동작하며, 반환된 값이나 클라이언트로 포워딩된 콜백으로부터의 예외 결과를 마샬링합니다.

Invocation 객체는 단지 메쏘드 호출의 컨텍스트를 표현하는 것 뿐입니다. 여기에는 타겟 MBean의 이름, 메쏘드, 메쏘드 인수들, 프록시 팩토리에 의해 프록시와 관련되는 정보의 컨텍스트 및 클라이언트 프록시 인터셉터에 의해 호출되는 관련된 임의의 데이터 맵이 포함됩니다. 다음에 오는 코드에서는 invocation클래스의 중요 메쏘드들이 어떻게 사용되었는지 보여주고 있습니다:

package org.jboss.invocation;

import java.lang.reflect.Method;
import java.security.Principal;
import java.util.Map;
import java.util.HashMap;
import javax.transaction.Transaction;

public class Invocation
{
    /** The signature of the invoke() method */
    public static final String[] INVOKE_SIGNATURE = 
        {"org.jboss.invocation.Invocation"};

    /**
     *  Contextual information to the invocation that is not part of
     *  the payload.
     */
    public Map transient_payload;

    /**
     * as_is classes that will not be marshalled by the invocation
     * (java.* and javax.* or anything in system classpath is OK)
     */
    public Map as_is_payload;
    
    /** 
     * Payload will be marshalled for type hiding at the RMI layers.
     */
    public Map payload;
    
    protected InvocationContext invocationContext;
    protected Object[] args;
    protected Object objectName;
    protected Method method;
    
    public Invocation()
    {
	payload = new HashMap();
	as_is_payload = new HashMap();
	transient_payload = new HashMap();
    }
    
    public Invocation(Object id, Method m, Object[] args,
		      Transaction tx,
		      Principal identity, Object credential )
    {
	this.payload = new HashMap();
	this.as_is_payload = new HashMap();
	this.transient_payload = new HashMap();
	
	setId(id);
	setMethod(m);
	setArguments(args);
	setTransaction(tx);
	setPrincipal(identity);
	setCredential(credential);
    }

    public void setValue(Object key, Object value)
    {
	setValue(key, value, PayloadKey.PAYLOAD);
    }

    public void setValue(Object key, Object value, PayloadKey type)
    {
	if(type == PayloadKey.TRANSIENT) {
	    transient_payload.put(key,value);
	} else if(type == PayloadKey.AS_IS) {
	    as_is_payload.put(key,value);
	} else if(type == PayloadKey.PAYLOAD) {
	    payload.put(key,value);
	} else {
	    throw new IllegalArgumentException("Unknown
                PayloadKey: " + type);
	}
    }

    public Object getValue(Object key)
    {
	// find where it is
	Object rtn = payload.get(key);
	if (rtn != null) return rtn;

	rtn = as_is_payload.get(key);
	if (rtn != null) return rtn;
	
	rtn = transient_payload.get(key);
	return rtn;
    }

    public Object getPayloadValue(Object key)
    {
	return payload.get(key);
    }

    // ... Convience accessor methods deleted...
}            

클라이언트 프록시의 설정은 그림 2.25, “분리된 호출자 아키텍쳐에서의 메인 컴포넌트”에 표현한 "Proxy Factory" 콤포넌트에 의해 표시되고 있는 서버측 프록시 팩토리 MBean 서비스에 의해 완료됩니다. 프록시 팩토리는 다음과 같은 작업들을 수행합니다:

  • 타겟 MBean에서 노출시키고자 하는 인터페이스를 구현하는 동적인 프록시 생성.
  • 클라이언트 프록시 인터셉터를 동적인 프록시 핸들러와의 연계.
  • 동적인 프록시와 호출 컨텍스트의 연계. 여기에는 타겟 MBean, 분리된 호출자 스터브 및 프록시 JNDI 이름이 포함됩니다.
  • 프록시를 JNDI와 바인딩시킴으로써 클라이언트가 프록시를 사용할 수 있도록 합니다.

그림 2.26, “분리된 호출자 아키텍쳐에서의 메인 컴포넌트”의 마지막 컴포넌트는 원격 클라이언트쪽으로 호출을 위한 인터페이스를 노출시키기 원하는 "Target MBean" 서비스입니다. MBean 서비스가 주어진 인터페이스를 통해 액세스 가능하도록 하기위해 필요한 절차는 다음과 같습니다:

  • 서명(signature)과 일치하는 JMX 오퍼레이션 정의:
    public Object invoke(org.jboss.invocation.Invocation) throws Exception
  • org.jboss.invocation.MarshalledInvocation.calculateHash 메쏘드를 사용하여 long hash representation쪽으로 노출된 인터페이스인 java.lang.reflect.Methods 로부터 HashMap<Long, Method> 매핑을 생성합니다.
  • invoke(invocation) JMX 오퍼레이션을 수행하고 인터페이스 메쏘드 해쉬 매핑을 사용하여 호출된 메쏘드의 long hash representation으로부터 노출된 인터페이스의 java.lang.reflect.Method로 변환합니다. Reflection은 실제 노출된 인터페이스의 구현이 이루어진 MBean 서비스와 관련된 객체의 실질적인 호출을 수행하는데 사용되어집니다.

2.7.1. 분리된 호출자 예제 - MBeanServer Invoker Adaptor Service

JMX 서버로 연결하기 섹션에서 우리는 호출자 서비스를 사용하여 어떠한 프로토콜로도 javax.management.MBeanServer에 액세스할 수 있게 하는 서비스에 대해 언급했었습니다. 이번 섹션에서 우리는 MBean 서비스로 원격 액세스를 제공하기 위해 필요한 단계에 대한 예로써 RMI/JRMP를 통해 액세스하기위한 org.jboss.jmx.connector.invoker.InvokerAdaptorService와 환경설정을 보여줄 것입니다.

InvokerAdaptorService는 분리된 호출자 패턴에서 타겟 MBean role을 채울 수 있는 유일한 간단한 MBean 서비스입니다.

예제 2.18. InvokerAdaptorService MBean

package org.jboss.jmx.connector.invoker;
public interface InvokerAdaptorServiceMBean
    extends org.jboss.system.ServiceMBean
{
    Class getExportedInterface();
    void setExportedInterface(Class exportedInterface);

    Object invoke(org.jboss.invocation.Invocation invocation)
        throws Exception;
}

package org.jboss.jmx.connector.invoker;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import javax.management.MBeanServer;
import javax.management.ObjectName;

import org.jboss.invocation.Invocation;
import org.jboss.invocation.MarshalledInvocation;
import org.jboss.mx.server.ServerConstants;
import org.jboss.system.ServiceMBeanSupport;
import org.jboss.system.Registry;

public class InvokerAdaptorService
    extends ServiceMBeanSupport
    implements InvokerAdaptorServiceMBean, ServerConstants
{
    private static ObjectName mbeanRegistry;
    
    static {
        try {
            mbeanRegistry = new ObjectName(MBEAN_REGISTRY);
        } catch (Exception e) {
            throw new RuntimeException(e.toString());
        }
    }

    private Map marshalledInvocationMapping = new HashMap();
    private Class exportedInterface;

    public Class getExportedInterface()
    {
        return exportedInterface;
    }

    public void setExportedInterface(Class exportedInterface)
    {
        this.exportedInterface = exportedInterface;
    }

    protected void startService()
        throws Exception
    {
        // Build the interface method map
        Method[] methods = exportedInterface.getMethods();
        HashMap tmpMap = new HashMap(methods.length);
        for (int m = 0; m < methods.length; m ++) {
            Method method = methods[m];
            Long hash = new Long(MarshalledInvocation.calculateHash(method));
            tmpMap.put(hash, method);
        }

        marshalledInvocationMapping = Collections.unmodifiableMap(tmpMap);
        // Place our ObjectName hash into the Registry so invokers can
        // resolve it
        Registry.bind(new Integer(serviceName.hashCode()), serviceName);
    }

    protected void stopService()
        throws Exception
    {
        Registry.unbind(new Integer(serviceName.hashCode()));
    }


    public Object invoke(Invocation invocation)
        throws Exception
    {
        // Make sure we have the correct classloader before unmarshalling
        Thread thread = Thread.currentThread();
        ClassLoader oldCL = thread.getContextClassLoader();

        // Get the MBean this operation applies to
        ClassLoader newCL = null;
        ObjectName objectName = (ObjectName) 
            invocation.getValue("JMX_OBJECT_NAME");
        if (objectName != null) {
            // Obtain the ClassLoader associated with the MBean deployment
            newCL = (ClassLoader) 
                server.invoke(mbeanRegistry, "getValue",
                              new Object[] { objectName, CLASSLOADER },
                              new String[] { ObjectName.class.getName(),
                                             "java.lang.String" });
        }
        
        if (newCL != null && newCL != oldCL) {
            thread.setContextClassLoader(newCL);
        }

        try {
            // Set the method hash to Method mapping
            if (invocation instanceof MarshalledInvocation) {
                MarshalledInvocation mi = (MarshalledInvocation) invocation;
                mi.setMethodMap(marshalledInvocationMapping);
            }

            // Invoke the MBeanServer method via reflection
            Method method = invocation.getMethod();
            Object[] args = invocation.getArguments();
            Object value = null;
            try {
                String name = method.getName();
                Class[] sig = method.getParameterTypes();
                Method mbeanServerMethod =
                    MBeanServer.class.getMethod(name, sig);
                value = mbeanServerMethod.invoke(server, args);
            } catch(InvocationTargetException e) {
                Throwable t = e.getTargetException();
                if (t instanceof Exception) {
                    throw (Exception) t;
                } else {
                    throw new UndeclaredThrowableException(t, method.toString());
                }
            }

            return value;
        } finally {
            if (newCL != null && newCL != oldCL) {
                thread.setContextClassLoader(oldCL);
            }
        }
    }
}    

이 서비스의 핵심 사항들을 자세히 살펴보도록 하겠습니다. InvokerAdaptorService의 InvokerAdaptorServiceMBean Standard MBean 인터페이스는 각각 한개의 ExportedInterface 속성과 invoke(Invocation) 오퍼레이션을 갖고 있습니다. ExportedInterface 속성은 서비스를 클라이언트에 노출시키는 인터페이스의 타입을 커스터마이징시킬 수 있게합니다. 우리가 보게될것처럼 메쏘드 이름과 서명이라는 용어를 사용하여 MBeanServer 클래스와 "호환"되여야만 합니다. invoke(Invocation) 오퍼레이션은 분리된 호출자 패턴에 참가할 수 있도록 노출되어야하는 타겟 MBean 서비스인 필요한 엔트리 포인트입니다. 이 오퍼레이션은 InvokerAdaptorService에 액세스할 수 있도록 설정되어져 있는 분리된 호출자 서비스에 의해 호출됩니다.

54-64 라인에서 InvokerAdaptorService는 org.jboss.invocation.MarshalledInvocation.calculateHash(Method) 유틸리티 메쏘드를 사용하는 ExportedInterface 클래스의 HashMap<Long, Method>를 빌드합니다. java.lang.reflect.Method 인스턴스가 직렬화가 가능하지 않기때문에 비-직렬화 Invocation 클래스의 MarshalledInvocation 버전은 클라이언트와 서버사이에서의 호출을 마샬링하는데 사용됩니다. MarshalledInvocation은 자신의 대응되는 hash representation로 Method 인스턴스를 대체합니다. 서버쪽에서 MarshalledInvocation은 반드시 Method 매핑을 해쉬하는지를 알려주어야 합니다.

64 라인에서는 InvokerAdaptorService 서비스 이름과 hashCode representation사이의 매핑을 생성합니다. 이것은 Invocation의 타겟 MBean ObjectName이 무엇인지 결정하는 분리된 호출자에 의해 사용됩니다. 타겟 MBean 이름이 Invocation내에 저장되면, ObjectName이 상대적으로 생성하기에 많은 자원을 소비하기 때문에 자신의 hashCode도 저장합니다. org.jboss.system.Registry는 ObjectName 매핑을 하는 hashCode를 저장하는 데 사용되는 호출자인 생성자처럼 글로벌 맵입니다.

77-93 라인에서는 MBeanServer 오퍼레이션이 수행되며 MBean의 SAR 배치와 관련된 ClassLoader를 조회하는 MBean의 이름을 얻습니다. 이 정보는 JBoss JMX implementation specific 클래스인 org.jboss.mx.server.registry.BasicMBeanRegistry를 통해 사용가능합니다. 분리된 호출자 프로토콜 계층에서 호출과 관련된 타입을 언마샬링하는데 필요로 하는 클래스 로더에 액세스할 수 없기때문에 이것은 보통 MBean이 올바른 클래스 로딩 컨텍스트를 성립하는데 필요합니다.

101-105 라인에서는 invocation 인자가 type MarshalledInvocation일 경우 메쏘드 매핑을 해쉬하는 ExposedInterface 클래스 메쏘드를 인스톨합니다. 라인 54-62에서 앞서 계산된 메쏘드 맵핑이 여기서 사용됩니다.

107-114 라인에서 MBeanServer 클래스의 메쏘드를 일치시키기위한 ExposedInterface 메쏘드로부터 두번째 매핑이 수행됩니다. InvokerServiceAdaptor는 임의의 인터페이스를 허용하고 있는 MBeanServer 클래스로부터 ExposedInterface를 완화시킵니다. 표준 java.lang.reflect.Proxy 클래스만이 인터페이스들을 프록시할 수 있기때문에 이렇게 해야할 필요가 생기게 됩니다. 또한 이렇게 함으로써 MBeanServer 메쏘드의 서브셋만을 노출하면서 java.rmi.RemoteException처럼 ExposedInterface 메쏘드 서명에 전송시 발생하는 특정한 예외를 추가할 수 있게 됩니다.

115 라인에서는 InvokerAdaptorService가 배치된 MBeanServer 인스턴스쪽으로 MBeanServer 메쏘드 호출을 처리합니다. 서버 인스턴스 변수는 ServiceMBeanSupport 슈퍼클래스로부터 상속됩니다.

117-124 라인에서는 호출에 의해 발생되는 선언된 예외상황을 포함하여 재귀호출(reflective invocation)로부터 발생되는 예외상황을 처리합니다.

126 라인에서는 성공적인 MBeanServer 메쏘드 호출 결과가 리턴됩니다.

InvokerAdaptorService MBean은 어떠한 지정된 전송계층의 세부적인 사항들을 직접 다루지 않습니다. Method 맵핑을 위해 메쏘드 해쉬의 계산을 하지만 여기서는 전송에 비종속적인 세부사항입니다.

이제 우리는 "RMI를 사용하여 JMX에 연결하기"에서 보았던 것과 똑같은 RMI/JRMP를 경유하는 org.jboss.jmx.adaptor.rmi.RMIAdaptor 인터페이스를 노출시키기 위해 어떻게 InvokerAdaptorService를 사용하는지를 살펴볼 것입니다. jmx-invoker-adaptor-service.sar 배치에서의 디폴트 셋업부분에 있는 프록시 팩토리와 InvokerAdaptorService 환경설정으로 시작해보도록 하겠습니다. 예제 2.19, “디폴트 jmx-invoker-adaptor-server.sar jboss-service.xml 배치 서술자”에서는 이 배치를 위한 jboss-service.xml 서술자를 보여주고 있습니다.

예제 2.19. 디폴트 jmx-invoker-adaptor-server.sar jboss-service.xml 배치 서술자

<server>
    <!-- The JRMP invoker proxy configuration for the InvokerAdaptorService -->
    <mbean code="org.jboss.invocation.jrmp.server.JRMPProxyFactory"
           name="jboss.jmx:type=adaptor,name=Invoker,protocol=jrmp,service=proxyFactory">
        <!-- Use the standard JRMPInvoker from conf/jboss-service.xxml -->
        <attribute name="InvokerName">jboss:service=invoker,type=jrmp</attribute>
        <!-- The target MBean is the InvokerAdaptorService configured below -->
        <attribute name="TargetName">jboss.jmx:type=adaptor,name=Invoker</attribute>
        <!-- Where to bind the RMIAdaptor proxy -->
        <attribute name="JndiName">jmx/invoker/RMIAdaptor</attribute>
        <!-- The RMI compabitle MBeanServer interface -->
        <attribute name="ExportedInterface">org.jboss.jmx.adaptor.rmi.RMIAdaptor</attribute>
        <attribute name="ClientInterceptors">
            <iterceptors>
                <interceptor>org.jboss.proxy.ClientMethodInterceptor</interceptor>
                <interceptor>
                    org.jboss.jmx.connector.invoker.client.InvokerAdaptorClientInterceptor </interceptor>
                <interceptor>org.jboss.invocation.InvokerInterceptor</interceptor>
            </iterceptors>
        </attribute>
        <depends>jboss:service=invoker,type=jrmp</depends>
    </mbean>  
    <!-- This is the service that handles the RMIAdaptor invocations by routing
         them to the MBeanServer the service is deployed under. -->
    <mbean code="org.jboss.jmx.connector.invoker.InvokerAdaptorService" 
           name="jboss.jmx:type=adaptor,name=Invoker">
        <attribute name="ExportedInterface">org.jboss.jmx.adaptor.rmi.RMIAdaptor </attribute>
    </mbean>
</server>

첫 번째 MBean인 org.jboss.invocation.jrmp.server.JRMPProxyFactory는 RMI/JRMP 프로토콜을 위한 프록시를 생성하는 프록시 팩토리 MBean 서비스입니다. JRMPProxyFactory에 대한 완전한 참조 정보는 "JRMPProxyFactory 서비스 - 동적인 JRMP 프록시 만들기" 섹션에서 찾을 수 있습니다. 예제 2.19, “디폴트 jmx-invoker-adaptor-server.sar jboss-service.xml 배치 서술자”에서 보여지는 이 서비스의 설정사항에서는 JRMPInvoker가 분리된 호출자로써 사용되고 있다는 것과 InvokerAdaptorService는 요청을 포워딩하게 되는 타겟 mbean이며, 프록시가 RMIAdaptor 인터페이스를 노출하고, 프록시는 "jmx/invoker/RMIAdaptor"라는 이름으로 JNDI에 연결되어지며 프록시는 ClientMethodInterceptor, InvokerAdaptorClientInterceptor, InvokerInterceptor, 이렇게 3개의 인터셉터를 갖고 있다는 것을 설명하고 있습니다. InvokerAdaptorService의 설정에서는 노출되는 서비스의 RMIAdaptor 인터페이스를 간단하게 설정합니다.

RMI/JRMP를 경유하는 InvokerAdaptorService를 노출시키기위한 환경설정의 마지막 부분이 바로 분리된 호출자입니다. 우리가 사용하게될 분리된 호출자는 home과 remote 호출을 위한 EJB 컨테이너에서 사용되는 표준 RMI/JRMP 호출자이며, conf/jboss-service.xml 서술자에서 설정된 org.jboss.invocation.jrmp.server.JRMPInvoker MBean 서비스입니다. 동일한 서비스 인스턴스를 사용함으로써 분리된 호출자의 특성을 강조합니다. JRMPInvoker는 프록시의 노출 인터페이스나 프록시 기능의 서비스와 상관없이 모든 RMI/JRMP 프록시에 대한 RMI/JRMP 엔트포인트로써 동작합니다.

2.7.2. 분리된 호출자 레퍼런스

2.7.2.1. JRMPInvoker - RMI/JRMP 전송

org.jboss.invocation.jrmp.server.JRMPInvoker 클래스는 호출자 인터페이스의 RMI/JRMP 구현을 제공하는 MBean 서비스입니다. JRMPInvoker는 자신을 RMI 섭로 노출시킴으로써 원격 클라이언트에서 호출자로 사용될 때는 클라이언트대신 JRMPInvoker 스터브를 전송하고 RMI/JRMP 프로토콜을 사용하여 호출하게 됩니다.

JRMPInvoker MBean은 RMI/JRMP 전송계층을 설정하기위한 다양한 속성들을 지원하고 있습니다. 설정할 수 있는 속성들에는 다음과 같은 것들이 있습니다:

  • RMIObjectPort: RMI 서버 소켓이 수신되는 포트 번호. 프록시 인터페이스를 통해 통신할때 이 포트로 RMI 클라이언트들이 접속합니다. jboss-service.xml 서술자에서의 디폴트 설정값은 4444 이며, 이 포트를 지정하지 않을 경우 속성의 디폴트는 0으로 임의의 사용하지 않는 포트가 설정되게 됩니다.
  • RMIClientSocketFactory: 프록시 인터페이스의 노출과정에서 사용할 수 있는 java.rmi.server.RMIClientSocketFactory 인터페이스를 위한 완전한 형식(fully qualified)의 클래스 이름을 지정합니다.
  • RMIServerSocketFactory: 프록시 인터페이스의 노출과정에서 사용할 수 있는 java.rmi.server.RMIServerSocketFactory 인터페이스를 위한 완전한 형식의 클래스 이름을 지정합니다.
  • ServerAddress: RMI 서버 소켓 수신 포트에서 사용될 인터페이스 주소를 지정합니다. 이 주소값에는 DNS 호스트이름이나 점으로 구분된 십진수의 인터넷 주소 모두 가능합니다. RMIServerSocketFactory는 InetAddress 객체를 받는 메쏘드를 지원하지 않기때문에 이 값은 reflection을 사용하여 RMIServerSocketFactory 구현 클래스로 넘겨집니다. public void setBindAddress(java.net.InetAddress addr) 메쏘드가 만들어져있는지의 확인여부를 점검하고 만약 존재한다면, RMIServerSocketAddr 값이 RMIServerSocketFactory 구현쪽으로 넘겨집니다. 만약 RMIServerSockertFactory 구현부에서 이러한 메쏘드를 지원하지 않는다고 한다면, ServerAddress 값은 무시됩니다.
  • SecurityDomain: RMIServerSocketFactory 구현부와 관계된 org.jboss.security.SecurityDomain 인터페이스 구현부의 JNDI 이름을 지정합니다. 값은 reflection을 사용하여 public void setSecurityDomain(org.jboss.security.SecurityDomain d) 메쏘드의 존재여부를 확인하기 위해 RMIServerSocketFactory쪽으로 넘겨지게 됩니다. 만약 이러한 메쏘드가 존재하지 않는다면, SecurityDomain 값은 무시되게 됩니다.

2.7.2.2. PooledInvoker - RMI/Socket 전송

org.jboss.invocation.pooled.server.PooledInvoker는 호출자 인터페이스의 커스텀 소켓 전송 구현을 통해 RMI를 제공하는 MBean 서비스입니다. PooledInvoker는 자기 자신을 RMI 서버로 노출시키기 때문에 원격 클라이언트쪽에서 호출자로 사용될 경우에는 클라이언트대신 PooledInvoker 스터브를 전송하고 호출에서는 커스텀 소켓 프로토콜을 사용합니다.

PooledInvoker MBean은 소켓 전송 계층을 설정하기위한 다양한 속성들을 지원합니다. 그 속성들은 다음과 같습니다:

  • NumAcceptThreads: 클라이언트 연결을 수락하기위해 존재하는 쓰레드의 갯수. 기본값은 1 입니다.
  • MaxPoolSize: 클라이언트 처리를 위한 서버 쓰레드의 갯수입니다. 기본값은 300입니다.
  • SocketTimeout: Socket.setSoTimeout() 메쏘드로 전달되는 소켓의 타임아웃 값입니다. 기본값은 60000 입니다.
  • ServerBindPort: 서버 소켓에서 사용되는 포트입니다. 이 값이 0인 경우 임의의 포트가 선택되게 됩니다.
  • ClientConnectAddress: Socket(addr, port) 생성자쪽으로 넘어가는 클라이언트의 주소입니다. 기본적으로 서버의 InetAddress.getLocalHost() 값이 됩니다.
  • ClientConnectPort: Socket(addr, port) 생성자쪽으로 넘어가는 클라이언트의 포트입니다. 기본적으로 서버가 수신하는 소켓의 포트가 됩니다.
  • ClientMaxPoolSize: 클라이언트쪽의 최대 쓰레드 갯수입니다. 기본값은 300 입니다.
  • Backlog: 서버가 수신하는 소켓에 관련된 backlog 입니다. 기본값은 200 입니다.
  • EnableTcpNoDelay: 클라이언트 소켓에서 소켓에 대한 TcpNoDelay 플래그를 가능하게 할지를 지정해주는 부울린 프래그입니다. 기본값은 false 입니다.
  • ServerBindAddress: 서버가 수신하는 소켓에 연결되어 있는 주소입니다. 기본값은 비어있으며, 이는 서버에서 모든 인터페이스에 대해 연결하겠다는 것을 의미합니다.
  • TransactionManagerService: JTA 트랜잭션 관리자 서비스의 JMX ObjectName입니다.

2.7.2.3. IIOPInvoker - RMI/IIOP 전송

org.jboss.invocation.iiop.IIOPInvoker 클래스는 호출자 인터페이스의 RMI/IIOP 구현을 제공하는 MBean 서비스입니다. IIOP 요청을 CORBA servants쪽으로 라우팅하는 IIOPInvoker IIOP 호출자는 RMI/IIOP 프록시를 생성시켜주는 org.jboss.proxy.ejb.IORFactory 프록시 팩토리에서 사용됩니다. 그러나, 자바 프록시(JRMP 프록시 팩토리에서 했던것처럼)를 생성시키기 보다는 이 팩토리는 CORBA IOR을 생성합니다. <code>IORFactory</code>는 주어진 엔터프라이즈 빈에 연결되어 있습니다. IIOP 호출자와 함께 2개의 CORBA servant인 빈즈의 EJBHome을 위한 EjbHomeCorbaServant와 빈즈의 EJBObject를 위한 EjbObjectCorbaServant 를 등록합니다.

IIOPInvoker MBean에는 설정가능한 속성이 존재하지 않습니다. 그 이유는 모든 속성들이 JacORB CORBA 서비스에서 사용되는 속성 파일인 conf/jacorb.properties에서 설정되어졌기 때문입니다.

2.7.2.4. JRMPProxyFactory 서비스 - 동적인 JRMP 프록시 만들기

org.jboss.invocation.jrmp.server.JRMPProxyFactory MBean 서비스는 JRMP를 전송계층으로하는 원격 클라이언트에 액세스하기위한 RMI와 호환되는 시만틱을 갖는 어떠한 인터페이스도 노출시킬 수 있는 프록시 팩토리입니다.

JRMPProxyFactory는 다음과 같은 속성들을 지원합니다:

  • InvokerName: RMI/JRMP 전송을 담당하는 서버측 JRMPInvoker MBean 서비스 JMX ObjectName의 문자열입니다.
  • TargetName: 노출된 인터페이스를 위한 invoke(Invocation) JMX 오퍼레이션을 노출시키는 서버측 MBean 입니다. 이것은 프록시를 통해 이루어지는 어떠한 호출들에 대해서도 목적지 서비스로써 사용되게 됩니다.
  • JndiName: 프록시에 연결되어질 JNDI 이름입니다.
  • ExportedInterface: 프록시가 구현되는 인터페이스의 완전한 형태의 클래스 이름입니다. 이것은 클라이언트가 호출을 위해 사용하는 프록시의 typed view 입니다.
  • ClientInterceptors: Z각각의 인터셉터 요소의 바디가 org.jboss.proxy 이름의 완전한 형태를 갖는 클래스 이름으로 지정되어 있는 인터셉터/인터셉터 요소의 XML 조각입니다. 인터셉터 구현부는 프록시 인터셉터 스택이 포함되어 있습니다. 인터셉터/인터셉터 요소의 순서는 인터셉터의 순서를 정의합니다.

2.7.2.5. HttpInvoker - RMI/HTTP 전송

org.jboss.invocation.http.server.HttpInvoker MBean 서비스는 HTTP를 통해 JMX 버스에 호출을 만들어줄 수 있도록 지원합니다. JRMPInvoker와는 다르게 HttpInvoker는 호출자의 구현부는 아니지만 Invoker.invoke 메쏘드를 수행하게 됩니다. HttpInvoker는 org.jboss.invocation.http.servlet.InvokerServlet으로 HTTP POST를 만들어 간접적으로 액세스됩니다. HttpInvoker는 호출자의 구현부인 org.jboss.invocation.http.interfaces.HttpInvokerProxy 클래스의 형태로 클라이언트측 프록시를 노출하며 직렬화가 가능합니다. HttpInvoker는 bean-invoker와 home-invoker EJB 설정 요소의 타겟으로써 JRMPInvoker에 대한 대체입니다. HttpInvoker와 InvokerServlet은 "HTTP를 통한 JNDI 액세스"라는 제목을 갖는 섹션의 JNDI 쪽에서 논의되었던 http-invoker.sar에 배치되어집니다.

HttpInvoker는 다음과 같은 속성들을 지원합니다:

  • InvokerURL: InvokerServlet 맵핑을 위한 http URL이나 InvokerServlet으로 http URL을 얻고자 하는 클라이언트 VM내부에서 이름을 풀 수 있도록 해주는 시스템 속성의 이름중에 하나가 됩니다. 이 값이 ${x} 형태일 경우, 그 자체로도 서버내에서 해석된 시스템 속성을 참조할 수 있습니다. 여기서 x는 시스템 속성의 이름입니다. 이것은 URL이나 클라이언트쪽 시스템 속성이 한곳에 고정되도록 설정하고 InvokerServlet config 뿐만 아니라 HttpInvoker config에서도 재사용 되도록 허용됩니다.
  • InvokerURLPrefix: invokerURL이 설정되어 있지 않다면, invokerURLPrefix + the local host + invokerURLSuffix 조합으로 새로 만들어지게 됩니다. 즉, "http://"라는 접두사와 같이 설정할 수 있으며, 기본값은 "http://"입니다.
  • InvokerURLSuffix: invokerURL이 설정되어있지 않다면, invokerURLPrefix + the local host + invokerURLSuffix 조합으로 새로 만들어지게 됩니다. 즉, ":8080/invoker/JMXInvokerServlet"처럼 설정할 수 있으며, 기본값은 ":8080/invoker/JMXInvokerServlet" 입니다.
  • UseHostName: InetAddress.getHostName() 혹은 getHostAddress() 메쏘드가 invokerURLPrefix + host + invokerURLSuffix의 호스트 컴포넌트로써 사용될 수 있게 하는 부울린 플래그입니다. true로 설정하면, getHostName()이 사용되고, false면 getHostAddress()가 사용됩니다.

2.7.2.6. HA JRMPInvoker - 클러스터링된 RMI/JRMP 전송

org.jboss.proxy.generic.ProxyFactoryHA 서비스는 클러스터 인지 팩토리(cluster aware factory)인 ProxyFactoryHA의 확장입니다. ProxyFactoryHA는 JRMPProxyFactory의 모든 속성을 완전하게 지원합니다. 이것은 클러스터링된 RMI/JRMP에서도 포트, 인터페이스 그리고 소켓 전송에 연결되는 것들을 지정할 수 있음을 의미합니다. 이에 더해서 다음과 같은 클러스터를 위한 속성들도 지원합니다:

  • PartitionObjectName: 프록시에 관련된 클러스터 서비스의 JMX ObjectName입니다.
  • LoadBalancePolicy: 프록시에 관련된 org.jboss.ha.framework.interfaces.LoadBalancePolicy 인터페이스 구현부의 클래스 이름입니다.

2.7.2.7. HA HttpInvoker - 크러스터링된 RMI/HTTP 전송

JBoss-3.0.2부터 추가된 RMI/HTTP 계층은 JBoss-3.0.3의 클러스터링 환경에서 호출의 소프트웨어 로드 밸런싱이 가능하도록 확장되었습니다. HTTP 호출자의 HA 가능성은 HA-RMI/JRMP 클러스터링의 기능으로부터 빌려와 추가되었습니다.

HA-RMI/HTTP가 가능하도록 하려면, EJB 컨테이너를 위한 호출자를 설정해주어야만 합니다. 이것은 jboss.xml 서술자나 standardjboss.xml 서술자중 한곳에서 해주면 됩니다.

2.7.2.8. HttpProxyFactory - 동적인 HTTP 프록시 만들기

org.jboss.invocation.http.server.HttpProxyFactory MBean 서비스는 전송계층으로써 HTTP를 사용하는 원격 클라이언트로의 액세스를 위한 RMI 호환가능한 시맨틱을 갖는 어떠한 인터페이스라도 노출시킬 수 있는 프록시 팩토리입니다.

HttpProxyFactory는 다음과 같은 속성들을 지원합니다:

  • InvokerName: 노출된 인터페이스를 위한 invoke 오퍼레이션을 노출하는 서버측 MBean 입니다. 이름은 호출이 HttpInvoker에 의해 포워딩되는 타겟으로 HttpInvokerProxy 컨텍스트쪽에 내장됩니다.
  • JndiName: HttpInvokerProxy가 연결된 JNDI 이름입니다. 이것은 클라이언트가 서비스 인터페이스를 노출하고 HTTP를 통해 호출을 마샬링하는 동적인 프록시를 획득할 때 찾아보는이름입니다. 이 값을 빈 값으로 지정할 수도 있으며, 이럴 경우에는 프록시는 JNDI에 연결되어서는 않됩니다.
  • InvokerURL: 여기에는 InvokerServlet 맵핑을 위한 http URL이나 InvokerServlet으로 http URL을 얻는 클라이언트 VM내에서 해석되어지는 시스템 속성의 이름이 올 수 있습니다. 이 값이 ${x} 형태를 갖을 경우에는 그 자체로도 서버에서 해석되는 시스템 속성을 참조할 수 있습니다. 여기서 x는 시스템 속성의 이름입니다.
  • InvokerURLPrefix: invokerURL을 설정하지 않았다면, invokerURLPrefix + the local host + invokerURLSuffix를 통해 새로운 조합이 만들어지게 됩니다. 즉, "http://"라는 접두사와 같이 설정할 수 있으며, 기본값은 "http://"입니다.
  • InvokerURLSuffix: invokerURL 설정이 되어있지 않다면, invokerURLPrefix + the local host + invokerURLSuffix의 조합으로 만들어지게 됩니다. 즉, ":8080/invoker/JMXInvokerServlet" 와 같은 접미사가 사용될 수 있으며, 기본값은 ":8080/invoker/JMXInvokerServlet" 입니다.
  • UseHostName: InetAddress.getHostName() 혹은 getHostAddress() 메쏘드가 invokerURLPrefix + host + invokerURLSuffix의 host 컴포넌트로써 사용될 수 있도록 지정하는 부울린 플래그입니다. true이면 getHostName()이 사용되고, false면 getHostAddress()가 사용됩니다.
  • ExportedInterface: HttpInvokerProxy를 수행하는 RMI와호환되는 인터페이스의 이름입니다.

2.7.2.9. HTTP를 통해 RMI 인터페이스를 노출하는 단계

HttpProxyFactory MBean과 JMX를 사용하여 여러분은 전송계층으로 HTTP를 사용하여 액세스되는 임의의 인터페이스를 노출시킬 수 있습니다. 노출시킬 인터페이스는 RMI 인터페이스이어야만 하는것은 아니지만, 모든 메쏘드 매개변수들과 반환 값들이 직렬화가 가능(serializable)해야 하므로 RMI와 호환되어져야 합니다. 또한 이것의 스터브로 메쏘드 매개변수 혹은 반환 값으로 사용되는 RMI 인터페이스를 변환하기위한 지원은 없습니다.

여러분의 객체를 HTTP를 통해 호출이 가능하도록 만드는 데는 다음의 3 단계가 필요합니다:

  • MarshalledInvocation.calculateHash 메쏘드를 사용하여 long을 RMI 인터페이스 메쏘드의 맵핑으로 생성합니다. 예제에서는 RMI SRPRemoteServerInterface 인터페이스에 대한 절차입니다.
  • import java.lang.reflect.Method;
    import java.util.HashMap;
    import org.jboss.invocation.MarshalledInvocation;
     
    HashMap marshalledInvocationMapping = new HashMap();
     
    // Build the Naming interface method map
    Method[] methods = SRPRemoteServerInterface.class.getMethods();
    for(int m = 0; m < methods.length; m ++) {
        Method method = methods[m];
        Long hash = new Long(MarshalledInvocation.calculateHash(method));
        marshalledInvocationMapping.put(hash, method);
    }
    
  • invoke 오퍼레이션을 지원하기위해 새로 만들거나 기존의 MBean을 확장합니다. 이것의 용법은 Object invoke(Invocation invocation) throws Exception 이며, 여기서 보여주고 있는 단계는 SRPRemoteServerInterface 인터페이스에 대한 것입니다. 이 단계에서는 1단계의 marshalledInvocationMapping을 사용하여 MarshalledInvocation에서 해쉬하는 Long 메쏘드로부터 인터페이스를 위한 Method로 맵핑합니다.
  • import org.jboss.invocation.Invocation;
    import org.jboss.invocation.MarshalledInvocation;
     
    public Object invoke(Invocation invocation)
        throws Exception
    {
        SRPRemoteServerInterface theServer = <the_actual_rmi_server_object>;
        // Set the method hash to Method mapping
        if (invocation instanceof MarshalledInvocation) {
            MarshalledInvocation mi = (MarshalledInvocation) invocation;
            mi.setMethodMap(marshalledInvocationMapping);
        }
    
        // Invoke the Naming method via reflection
        Method method = invocation.getMethod();
        Object[] args = invocation.getArguments();
        Object value = null;
        try {
            value = method.invoke(theServer, args);
        } catch(InvocationTargetException e) {
            Throwable t = e.getTargetException();    
            if (t instanceof Exception) {
                throw (Exception) e;
            } else {
                throw new UndeclaredThrowableException(t, method.toString());
            }
        }
     
        return value;
    }
    
  • JNDI를 통해 RMI/HTTP 프록시를 사용가능하도록 하기위해 HttpProxyFactory MBean의 설정을 생성합니다. 다음에 그 예제를 보여주고 있습니다:
  • <!-- Expose the SRP service interface via HTTP -->
    <mbean code="org.jboss.invocation.http.server.HttpProxyFactory"
           name="jboss.security.tests:service=SRP/HTTP">
        <attribute name="InvokerURL">http://localhost:8080/invoker/JMXInvokerServlet</attribute>
        <attribute name="InvokerName">jboss.security.tests:service=SRPService</attribute>
        <attribute name="ExportedInterface">org.jboss.security.srp.SRPRemoteServerInterface
        </attribute><attribute name="JndiName">srp-test-http/SRPServerInterface</attribute>
    </mbean>
    

이제 어느 클라이언트라도 HttpProxyFactory에 지정한 이름을 사용하여 JNDI로부터 RMI 인터페이스를 찾을 수 있게 되고 (즉,srp-test-http/SRPServerInterface), RMI/JRMP 버전과 똑같은 방식으로 획득한 프록시를 사용합니다.