5 장. JBoss에서의 EJB

EJB 컨테이너의 환경설정과 아키텍쳐

JBoss의 EJB 컨테이너 아키텍쳐는 모듈방식의 플러그인 방식을 강조하는 4세대 디자인입니다. EJB 컨테이너의 모든 중요한 사항들은 개발자에 의해 커스터마이징된 플러그인 버전과/혹은 인터셉터로 대체될 수 있습니다. 이러한 방식으로 인하여 여러분의 요구에 따라 가장 최적의 EJB 컨테이너 비헤이비어를 세세하게 커스터마이징하여 튜닝할 수 있습니다. 대부분의 EJB 컨테이너의 비헤이비어는 EJB JAR META-INF/jboss.xml 서술자와 디폴트 서버 설정인 standardjboss.xml 서술자를 통해 설정이 가능합니다. 우리는 이번 장을 통해 컨테이너의 아키텍쳐를 살펴봄으로써 다양한 설정 가능성을 알아보게 됩니다.

5.1. EJB 클라이언트쪽의 뷰

우리는 home과 remote 프록시를 통해 EJB의 클라이언트 뷰를 살펴봄으로써 EJB 컨테이너의 여행을 시작하도록 하겠습니다. EJB 구현(implementation)을 위한 javax.ejb.EJBHomejavax.ejb.EJBObject를 생성시키는 것은 컨테이너 프로바이더의 책임입니다. 클라이언트는 EJB 빈 인스턴스를 직접적으로 참조하지는 않고 빈의 home 인터페이스인 EJBHome과 빈의 원격 인터페이가 구현된 EJBObject를 참조합니다. 그림 5.1, “JBoss에서의 EJBHome 프록시의 구성” 에서는 EJB home 프록시와 EJB 배치와의 관계를 보여주고 있습니다.

JBoss에서의 EJBHome 프록시의 구성.

그림 5.1. JBoss에서의 EJBHome 프록시의 구성.

그림에서 번호가 매겨진 항목들은 다음과 같습니다:

  1. EJBDeployer (org.jboss.ejb.EJBDeployer)는 EJB JAR를 배치하기위해 호출됩니다. EJBModule (org.jboss.ejb.EJBModule)은 배치 메터데이터를 감싸기(encapsulate) 위해 만들어집니다.
  2. EJBModule 생명주기(life cycle)의 생성 단계(phase)에서는 EJBModuleinvoker-proxy-bindings 메터데이타에 기반을 둔 EJB home과 remote 인터페이스 프록시의 생성을 관리하는 EJBProxyFactory(org.jboss.ejb.EJBProxyFactory)를 생성합니다. 하나의 EJB에 여러개의 프록시 팩토리가 연계될 수 있으며 이러한 정의를 어떻게 간단하게 하는지를 살펴보도록 하겠습니다.
  3. ProxyFactory는 논리적인 프록시들을 구성하고 home 인터페이스를 JNDI로 바인딩합니다. 하나의 논리적인 프록시는 동적 Proxy (java.lang.reflect.Proxy), 프록시가 노출시킨 EJB의 home 인터페이스들, ClientContainer(org.jboss.proxy.ClientContainer) 형태의 ProxyHandler(java.lang.reflect.InvocationHandler) 구현 및 클라이언트쪽의 인터셉터들로 구성되어 있습니다.
  4. EJBProxyFactory에 의해 생성된 프록시는 JDK 1.3+ 동적 프록시입니다. 이것은 EJBModule 메터데이터에서 정의된 것 처럼 EJB의 home과 remote 인터페이스들을 프록시하는 직렬화가 가능한 객체입니다. 프록시는 자신과 연계된 ClientContainer 핸들러를 사용하여 엄격한 typed EJB 인터페이스를 detyped 호출로 요청을 번역합니다. 클라이언트가 룩업(lookup)하는 EJB home 인터페이스로 JNDI에 연결되는 것은 동적인 프록시 인스턴스입니다. 클라이언트가 EJB home의 룩업을 할때, home 프록시는 ClientContainer와 자신의 인터셉터를 갖으면서 클라이언트의 VM쪽으로 전송됩니다. 동적인 프록시를 사용함으로써 다른 많은 EJB 컨테이너들에서 필요로하는 EJB에 특화된 편집 단계를 피해갈 수 있습니다.
  5. EJB home 인터페이스는 ejb-jar.xml 서술자에서 선언되며 EJBModule 메타데이터로부터 사용이 가능합니다. 동적 프록시의 중요한 속성은 자신이 노출한 인터페이스를 구현하도록 보여지게 하는 것입니다. 이것은 자바의 강력한 형식(type) 시스템의 관점에서는 사실입니다. 하나의 프록시는 어떠한 home 인터페이스로도 캐스트할 수 있으며, 프록시에 대한 반향(reflection)에서는 인터페이스의 완전한 세부적 사항들을 제공합니다.
  6. 프록시는 자신의 인터페이스에 의해 만들어진 호출을 ClientContainer 핸들러로 위임합니다. 핸들러에서 요구되는 단일 메쏘드는 다음과 같습니다: public Object invoke(Object proxy, Method m, Object[] args) throws Throwable. EJBProxyFactoryClientContainer를 생성하고 이를 ProxyHandler로 할당합니다. ClientContainer의 상태는 InvocationContext (org.jboss.invocation.InvocationContext)와 인터셉터들의 체인(org.jboss.proxy.Interceptor)으로 구성됩니다. InvocationContext 는 다음을 포함합니다:
    • Proxy와 관련된 EJB 컨테이너 MBean의 JMX ObjectName.
    • EJB를 위한 javax.ejb.EJBMetaData
    • EJB home 인터페이스의 JNDI 이름
    • 전송에 특화된 호출자(invoker) (org.jboss.invocation.Invoker)

    인터셉터 체인은 EJB home 또는 remote 인터페이스 비헤비어를 만드는 기능적 유닛들로 구성됩니다. 이것은 우리가 jboss.xml 서술자를 논의할 때 보았었던 것처럼 EJB의 설정적인 측면이며, 인터셉터 구성은 EJBModule 메터데이터를 포함합니다. 인터셉터(org.jboss.proxy.Interceptor)는 상이한 EJB 타입, 보안, 트랜잭션 그리고 전송을 처리합니다. 여러분은 자신만의 인터셉트를 추가할 수도 있습니다.

  7. 프록시와 관련된 전송에 특화된 호출자(invoker)는 EJB 메쏘드 호출의 전송에 대한 세부적인 사항들을 처리하는 서버쪽 분리된(detached) 호출자와 관련이 있습니다. 분리된 호출자는 JBoss 서버측 컴포넌트입니다.

클라이언트쪽의 인터셉터 설정은 jboss.xml내에 client-interceptors 요소를 사용하여 하면 됩니다. 그림 5.2, “jboss.xml 서술자에서의 클라이언트쪽 인터셉터 설정 요소.”에서는 클라이언트 인터셉터를 위한 jboss.xml DTD의 서브셋을 보여주고 있습니다. ClientContainer 호출 메쏘드가 호출될때는 요청을 감싸주기위해 untyped Invocation (org.jboss.invocation.Invocation)을 만듭니다. 그런 후 인터셉터 체인을 통해 전달됩니다. 체인내의 마지막 인터셉터는 서버쪽으로 어떻게 요청을 전송하며 수신을 어떻게 받을지 알고 있는 전송에 특화된 세부사항들을 관장하는 전송 핸들러를 전송하게 됩니다.

jboss.xml 서술자에서의 클라이언트쪽 인터셉터 설정 요소.

그림 5.2. jboss.xml 서술자에서의 클라이언트쪽 인터셉터 설정 요소.

클라이언트 인터셉터의 설정방법에 대한 예를 보여주기위해, server/default/standardjboss.xml 서술자내에서 찾을 수 있는 디폴트 stateless 세션 빈의 설정을 살펴보겠습니다. 예제 5.1, “표준 Stateless 세션빈 설정에서의 클라이언트-인터셉터”에서는 표준 비상태 세션빈에서 참조되는 stateless-rmi-invoker 클라이언트 인터셉터의 설정을 볼 수 있습니다.

예제 5.1. 표준 Stateless 세션빈 설정에서의 클라이언트-인터셉터

<invoker-proxy-bindings>
    <invoker-proxy-binding>
        <name>stateless-rmi-invoker</name>
        <invoker-mbean>jboss:service=invoker,type=jrmp</invoker-mbean>
        <proxy-factory>org.jboss.proxy.ejb.ProxyFactory</proxy-factory>
        <proxy-factory-config>
            <client-interceptors>
                <home>
                    <interceptor>org.jboss.proxy.ejb.HomeInterceptor</interceptor>
                    <interceptor>org.jboss.proxy.SecurityInterceptor</interceptor>
                    <interceptor>org.jboss.proxy.TransactionInterceptor</interceptor>
                    <interceptor>org.jboss.invocation.InvokerInterceptor</interceptor>
                </home>
                <bean>
                    <interceptor>
                        org.jboss.proxy.ejb.StatelessSessionInterceptor
                    </interceptor>
                    <interceptor>org.jboss.proxy.SecurityInterceptor</interceptor>
                    <interceptor>org.jboss.proxy.TransactionInterceptor</interceptor>
                    <interceptor>org.jboss.invocation.InvokerInterceptor</interceptor>
                </bean>
            </client-interceptors>
        </proxy-factory-config>
    </invoker-proxy-binding>
    <!-- ... -->
    <invoker-proxy-bindings>
        <container-configuration>
            <container-name>Standard Stateless SessionBean</container-name>
            <call-logging>false</call-logging>
            <invoker-proxy-binding-name>stateless-rmi-invoker</invoker-proxy-binding-name>
            <!-- ... -->
        </container-configuration>
    </invoker-proxy-bindings>
</invoker-proxy-bindings>

이것은 자신의 설정값을 재설정해주는 EJB JAR META-INF/jboss.xml 설정이 없는 겨우 사용되는 비상태(stateless) 세션 빈의 클라이언트 인터셉터 설정입니다. 기능들은 각각의 인터셉터에 의해 제공됩니다:

5.1.1. EJB 프록시 설정 지정하기

EJB의 호출 전송과 클라이언트 프록시 인터셉터 스택을 지정하기위해서 우리는 EJB JAR META-INF/jboss.xml descriptor나 서버의 standardjboss.xml 서술자중 한 곳에서 invoker-proxy-binding을 정의해줄 필요가 있습니다. standardjboss.xml 서술자내에는 다양한 기본 EJB 컨테이너 설정과 표준 RMI/JRMMP 그리고 RMI/IIOP 전송 프로토콜을 위한 디폴트 invoker-proxy-bindings들을 정의하고 있습니다. 현재 디폴트 프록시 설정은 다음과 같습니다:

새로운 프로토콜 바인등이나 프록시 팩토리의 커스터마이징 또는 클라이언트쪽 인터셉터 스택을 살펴보기위해서는 새로운 invoker-proxy-binding의 정의가 필요합니다. 프록시 설정의 사양에 따른 완전한 invoker-proxy-binding DTD 요소들은 그림 5.3, “invoker-proxy-binding 스키마”에 보여집니다.

The invoker-proxy-binding schema

그림 5.3. invoker-proxy-binding 스키마

invoker-proxy-binding의 자식 요소들은 다음과 같습니다:

다음 예제는 standardjboss.xml 서술자로부터 발췌한 proxy-factory-config 샘플입니다.

<invoker-proxy-bindings>
    <invoker-proxy-binding>
        <name>stateless-rmi-invoker</name>
        <invoker-mbean>jboss:service=invoker,type=jrmp</invoker-mbean>
        <proxy-factory>org.jboss.proxy.ejb.ProxyFactory</proxy-factory>
        <proxy-factory-config>
            <client-interceptors>
                <home>
                    <interceptor>org.jboss.proxy.ejb.HomeInterceptor</interceptor>
                    <interceptor>org.jboss.proxy.SecurityInterceptor</interceptor>
                    <interceptor>org.jboss.proxy.TransactionInterceptor</interceptor>
                    <interceptor>org.jboss.invocation.InvokerInterceptor</interceptor>
                </home>
                <bean>
                    <interceptor>
                        org.jboss.proxy.ejb.StatelessSessionInterceptor
                    </interceptor>
                    <interceptor>org.jboss.proxy.SecurityInterceptor</interceptor>
                    <interceptor>org.jboss.proxy.TransactionInterceptor</interceptor>
                    <interceptor>org.jboss.invocation.InvokerInterceptor</interceptor>
                </bean>
            </client-interceptors>
        </proxy-factory-config>
    </invoker-proxy-binding>
    <!-- ... -->
    <invoker-proxy-bindings>
        <container-configuration>
            <container-name>Standard Stateless SessionBean</container-name>
            <call-logging>false</call-logging>
            <invoker-proxy-binding-name>stateless-rmi-invoker</invoker-proxy-binding-name>
            <!-- ... -->
        </container-configuration>
    </invoker-proxy-bindings>
</invoker-proxy-bindings>

RMI/IIOP를 위한 프록시 팩토리인 org.jboss.proxy.ejb.IORFactory는 다음과 같은 요소들이 적용됩니다:

예제 5.2, “ IOFactory proxy-factory-config 샘플”standardjboss.xml 서술자에서 샘플 proxy-factory-config을 발췌한 것입니다.

예제 5.2.  IOFactory proxy-factory-config 샘플

<proxy-factory-config>
    <web-class-loader>org.jboss.iiop.WebCL</web-class-loader>
    <poa>per-servant</poa>
    <register-ejbs-in-jnp-context>true</register-ejbs-in-jnp-context>
    <jnp-context>iiop</jnp-context>
</proxy-factory-config>

JMS를 위한 프록시 팩토리인 org.jboss.ejb.plugins.jms.JMSContainerInvoker를 위해 MDBConfig이 존재합니다.

예제 5.3, “JMSContainerInvoker proxy-factory-config 샘플”standardjboss.xml 서술자에서 proxy-factory-config 샘플 부분을 발췌한 것입니다.

예제 5.3. JMSContainerInvoker proxy-factory-config 샘플

<proxy-factory-config>
    <JMSProviderAdapterJNDI>DefaultJMSProvider</JMSProviderAdapterJNDI>
    <ServerSessionPoolFactoryJNDI>StdJMSPool</ServerSessionPoolFactoryJNDI>
    <MaximumSize>15</MaximumSize>
    <MaxMessages>1</MaxMessages>
    <MDBConfig>
        <ReconnectIntervalSec>10</ReconnectIntervalSec>
        <DLQConfig>
            <DestinationQueue>queue/DLQ</DestinationQueue>
            <MaxTimesRedelivered>10</MaxTimesRedelivered>
            <TimeToLive>0</TimeToLive>
        </DLQConfig>
    </MDBConfig>
</proxy-factory-config> 

5.2. EJB 서버측 뷰

모든 EJB 호출은 반드시 JBoss 서버에 호스팅된 EJB 컨테이너쪽에서 마무리가 되어야 합니다. 이번 절에서 우리는 호출들이 JBoss 서버의 VM쪽으로 어떻게 전송되는지와 JMX 버스를 통해 자신들에게 맞는 EJB 컨테이너를 찾는지 살펴볼 것입니다.

5.2.1. 분리된 호출자(Detached Invokers) - 전송 중간자(Transport Middlemen)

우리는 이미 앞쪽에서 MBean 서비스의 RMI 호환 인터페이스를 노출하는 컨텍스트에서 분리된 호출자 아키텍쳐를 살펴보았습니다. 여기서는 분리된 호출자들이 어떤방식으로 EJB 컨테이너의 home과 bean 인터페이스를 클라이언트에게 노출시키기위해 사용되는지를 살펴보겠습니다. 호출자 아키텍쳐의 일반적인 뷰를 그림 5.4, “전송 호출자 서버측 아키텍쳐”에서 보여주고 있습니다.

전송 호출자 서버측 아키텍쳐

그림 5.4. 전송 호출자 서버측 아키텍쳐

home 프록시의 각각의 타입들에서는 하나의 호출자에 연결되고 전송 프로토콜로 연결됩니다. 하나의 컨테이너에는 여러개의 호출 프로토콜이 동시에 활성화될 수 있습니다. 호출자 설정을 위한 jboss.xml DTD 설정 요소들을 그림 5.5, “jboss.xml 서술자의 호출자 설정 요소들.”에 보여주고 있습니다. invoker-proxy-binding-nameinvoker-proxy-binding/name요소로 맵핑됩니다. container-configuration 레벨에서 이것은 EJB를 컨테이너에 배치시킬때 사용되는 디폴트 호출자를 지정합니다. bean 레벨에서는 invoker-bindings으로 EJB 컨테이너 MBean과 함께 사용하는 하나 이상의 호출자를 지정합니다.

jboss.xml 서술자의 호출자 설정 요소들.

그림 5.5. jboss.xml 서술자의 호출자 설정 요소들.

주어진 EJB 배치에 대해 여러 호출자를 지정한 경우, home 프록시는 반드시 고유한 JNDI 바인딩 위치가 되어야만 합니다. 이것은 invoker/jndi-name 요소의 값으로 지정합니다. EJB에 대한 여러 호출자를 갖는 경우에 대한 또다른 사항은 EJB가 다른 빈즈를 호출하는 경우 얻는 remote homes 혹은 인터페이스를 어떻게 처리하느냐는 것입니다. 이러한 인터페이스들은 얻어진 remote homes와 인터페이스가 클라이언트에서 호출할때 초기화된 프록시와 호환될 수 있도록 하기위해 외부 EJB를 호출하는데 사용하는 것과 동일한 호출자를 사용해야 할 필요가 있습니다. invoker/ejb-ref 요소는 프로토콜에 비종속적인 ENC ejb-ref로부터 참조하는 호출자 타입에 일치하는 ejb-ref target EJB home을 위한 home 프록시 바인딩에 매핑시킬 수 있습니다.

세션 빈즈용 압축된 소켓이 가능한 커스텀 JRMPInvoker MBean을 사용하는 예를 테스트용 패키지인 org.jboss.test.jrmp에서 찾을 수 있습니다. 다음에 오는 예제에서는 커스텀 JRMPInvoker 설정과 비상태 세션 빈으로 매핑하는 것을 보여주고 있습니다.

예제 5.4. 커스텀 JRMPInvoker jboss-service.xml 서술자

<server>
    <mbean code="org.jboss.invocation.jrmp.server.JRMPInvoker"
        name="jboss:service=invoker,type=jrmp,socketType=CompressionSocketFactory">
        <attribute name="RMIObjectPort">4445</attribute>
        <attribute name="RMIClientSocketFactory">
            org.jboss.test.jrmp.ejb.CompressionClientSocketFactory
        </attribute>
        <attribute name="RMIServerSocketFactory">
            org.jboss.test.jrmp.ejb.CompressionServerSocketFactory
        </attribute>
    </mbean>
</server>

예제 5.5. 커스텀 호출자를 사용한 jboss.xml 서술자

<?xml version="1.0"?>
<!DOCTYPE jboss PUBLIC
          "-//JBoss//DTD JBOSS 3.2//EN"
          "http://www.jboss.org/j2ee/dtd/jboss_3_2.dtd">
<!-- The jboss.xml descriptor for the jrmp-comp.jar ejb unit -->
<jboss>
    <enterprise-beans>
        <session>
            <ejb-name>StatelessSession</ejb-name>
            <configuration-name>Standard Stateless SessionBean</configuration-name>
            <invoker-bindings>
                <invoker>
                    <invoker-proxy-binding-name>
                        stateless-compression-invoker
                    </invoker-proxy-binding-name>
                    <jndi-name>jrmp-compressed/StatelessSession</jndi-name>
                </invoker>
            </invoker-bindings>
        </session>
    </enterprise-beans>
    <invoker-proxy-bindings>
        <invoker-proxy-binding>
            <name>stateless-compression-invoker</name>
            <invoker-mbean>
                jboss:service=invoker,type=jrmp,socketType=CompressionSocketFactory
            </invoker-mbean>
            <proxy-factory>org.jboss.proxy.ejb.ProxyFactory</proxy-factory>
            <proxy-factory-config>
                <client-interceptors>
                    <home>
                        <interceptor>org.jboss.proxy.ejb.HomeInterceptor</interceptor>
                        <interceptor>org.jboss.proxy.SecurityInterceptor</interceptor>
                        <interceptor>org.jboss.proxy.TransactionInterceptor</interceptor>
                        <interceptor>org.jboss.invocation.InvokerInterceptor</interceptor>
                    </home>
                    <bean>
                        <interceptor>
                            org.jboss.proxy.ejb.StatelessSessionInterceptor
                        </interceptor>
                        <interceptor>org.jboss.proxy.SecurityInterceptor</interceptor>
                        <interceptor>org.jboss.proxy.TransactionInterceptor</interceptor>
                        <interceptor>org.jboss.invocation.InvokerInterceptor</interceptor>
                    </bean>
                </client-interceptors>
            </proxy-factory-config>
        </invoker-proxy-binding>
    </invoker-proxy-bindings>
</jboss>     

여기서 기본 JRMPInvoker는 4445번 포트로 연결되도록 커스터마이징되었으며 전송 레벨에서 압축이 가능한 커스텀 소켓 팩토리를 사용합니다. StatelessSession EJB invoker-bindings설정에서는 JNDI 이름 jrmp-compressed/StatelessSession 아래에 연결된 home 인터페이스와 함께 사용될 stateless-compression-invoker를 지정합니다.

RMI/HTTP 프로토콜을 사용하기 위해 비상태 세션 빈을 설정하는데 HttpInvoker를 사용하는 예제는 org.jboss.test.hello 테스트용 패키지에서 찾을 수 있습니다. 예제 5.6, “비상태 세션 빈에서 RMI/HTTP를 사용하도록 한 jboss.xml 서술자 예”에서 커스텀 셋팅을 보여주고 있습니다.

예제 5.6. 비상태 세션 빈에서 RMI/HTTP를 사용하도록 한 jboss.xml 서술자 예.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss PUBLIC
          "-//JBoss//DTD JBOSS 3.2//EN"
          "http://www.jboss.org/j2ee/dtd/jboss_3_2.dtd">
<jboss>
    <enterprise-beans>
        <session>
            <ejb-name>HelloWorldViaHTTP</ejb-name>
            <jndi-name>helloworld/HelloHTTP</jndi-name>
            <invoker-bindings>
                <invoker>
                    <invoker-proxy-binding-name>
                        stateless-http-invoker
                    </invoker-proxy-binding-name>
                </invoker>
            </invoker-bindings>
        </session>
    </enterprise-beans>
    <invoker-proxy-bindings>
        <!-- A custom invoker for RMI/HTTP -->
        <invoker-proxy-binding>
            <name>stateless-http-invoker</name>
            <invoker-mbean>jboss:service=invoker,type=http</invoker-mbean>
            <proxy-factory>org.jboss.proxy.ejb.ProxyFactory</proxy-factory>
            <proxy-factory-config>
                <client-interceptors>
                    <home>
                        <interceptor>org.jboss.proxy.ejb.HomeInterceptor</interceptor>
                        <interceptor>org.jboss.proxy.SecurityInterceptor</interceptor>
                        <interceptor>org.jboss.proxy.TransactionInterceptor</interceptor>
                        <interceptor>org.jboss.invocation.InvokerInterceptor</interceptor>
                    </home>
                    <bean>
                        <interceptor>
                            org.jboss.proxy.ejb.StatelessSessionInterceptor
                        </interceptor>
                        <interceptor>org.jboss.proxy.SecurityInterceptor</interceptor>
                        <interceptor>org.jboss.proxy.TransactionInterceptor</interceptor>
                        <interceptor>org.jboss.invocation.InvokerInterceptor</interceptor>
                    </bean>
                </client-interceptors>
            </proxy-factory-config>
        </invoker-proxy-binding>
    </invoker-proxy-bindings>
</jboss>

여기서 커스텀 invoker-proxy-bindingstateless-http-invoker라는 이름으로 정의됩니다. 이것은 분리된 호출자로 HttpInvoker MBean을 사용합니다. jboss:service=invoker,type=http 이름은 http-inovker.sar/META-INF/jboss-service.xml 서술자에서 찾을 수 있는 HttpInvoker MBean의 디폴트 이름이며, 다음에 보여주는 서비스 서술자의 부분이 이것에 해당합니다:

<!-- HTTP 호출자 서비스 설정 -->
<mbean code="org.jboss.invocation.http.server.HttpInvoker"
       name="jboss:service=invoker,type=http">
    <!-- http://<hostname>:8080/invoker/EJBInvokerServlet 형태로 URL을 사용하며, 
         여기서 <hostname>은 서버가 동작하는 InetAddress.getHostname 값이 됩니다. -->
    <attribute name="InvokerURLPrefix">http://</attribute>
    <attribute name="InvokerURLSuffix">:8080/invoker/EJBInvokerServlet</attribute>
    <attribute name="UseHostName">true</attribute>
</mbean>

클라이언트 프록시는 EJB 호출 컨텐츠를 HttpInvoker 서비스 설정내에서 지정한 EJBInvokerServlet URL로 제출(post)합니다.

5.2.2. HA JRMPInvoker - 클러스터링된 RMI/JRMP 전송(Transport)

org.jboss.invocation.jrmp.server.JRMPInvokerHA 서비스는 클러스터를 인지하는 호출자인 JRMPInvoker의 확장입니다. JRMPInvokerHAJRMPInvoker의 모든 속성들을 완전히 지원합니다. 즉, 포트, 인터페이스 및 소켓 전송의 커스터마이징이 클러스터링된 RMI/JRMP에서도 역시 가능하다는 의미가 됩니다. 클러스터링 아키텍쳐에 대한 추가적인 정보와 HA RMI 프록시의 구현은 JBoss 클러스터링 문서를 참고하십시오.

5.2.3. HA HttpInvoker - 클러스터링된 RMI/HTTP 전송

RMI/HTTP 레이어는 클러스터링된 환경에서 호출의 소프트웨어적인 로드 밸런싱을 가능하도록 합니다. HTTP 호출자에서 HA가 가능하도록 한 확장은 HA-RMI/JRMP 클러스터링에서 구현되어 있는 기능들로부터 많은 부분 빌려와 추가되었습니다.

HA-RMI/HTTP가 가능하도록 하려면, EJB 컨테이너에 대한 호출자의 설정이 필요합니다. 이 설정은 jboss.xml서술자나 standardjboss.xml 서술자중 한 곳에서 할 수 있습니다. 예제 5.7, “HA-RMI/HTTP를 위한 jboss.xml 비상태 세션 설정”에서 보여지는 예는 org.jboss.test.hello 테스트용 패키지로부터 비상태 세션 설정 예를 가져온 것입니다.

예제 5.7. HA-RMI/HTTP를 위한 jboss.xml 비상태 세션 설정

<jboss>
    <enterprise-beans>
        <session>
            <ejb-name>HelloWorldViaClusteredHTTP</ejb-name>
            <jndi-name>helloworld/HelloHA-HTTP</jndi-name>
            <invoker-bindings>
                <invoker>
                    <invoker-proxy-binding-name>
                        stateless-httpHA-invoker
                    </invoker-proxy-binding-name>
                </invoker>
            </invoker-bindings>
            <clustered>true</clustered>
        </session>
    </enterprise-beans>
    <invoker-proxy-bindings>
        <invoker-proxy-binding>
            <name>stateless-httpHA-invoker</name>
            <invoker-mbean>jboss:service=invoker,type=httpHA</invoker-mbean>
            <proxy-factory>org.jboss.proxy.ejb.ProxyFactoryHA</proxy-factory>
            <proxy-factory-config>
                <client-interceptors>
                    <home>
                        <interceptor>org.jboss.proxy.ejb.HomeInterceptor</interceptor>
                        <interceptor>org.jboss.proxy.SecurityInterceptor</interceptor>
                        <interceptor>org.jboss.proxy.TransactionInterceptor</interceptor>
                        <interceptor>org.jboss.invocation.InvokerInterceptor</interceptor>
                    </home>
                    <bean>
                        <interceptor>
                            org.jboss.proxy.ejb.StatelessSessionInterceptor
                        </interceptor>
                        <interceptor>org.jboss.proxy.SecurityInterceptor</interceptor>
                        <interceptor>org.jboss.proxy.TransactionInterceptor</interceptor>
                        <interceptor>org.jboss.invocation.InvokerInterceptor</interceptor>
                    </bean>
                </client-interceptors>
            </proxy-factory-config>
        </invoker-proxy-binding>
    </invoker-proxy-bindings>
</jboss>

stateless-httpHA-invokerinvoker-proxy-bindingjboss:service=invoker,type=httpHA 호출자 서비스를 참조합니다. 이 서비스는 http-invoker.sar/META-INF/jboss-service.xml 서술자내에서 설정되며, SARdescriptor에서의 기본 설정은 다음과 같습니다:

<mbean code="org.jboss.invocation.http.server.HttpInvokerHA"
       name="jboss:service=invoker,type=httpHA">
    <!-- Use a URL of the form
         http://<hostname>:8080/invoker/EJBInvokerHAServlet
         where <hostname> is InetAddress.getHostname value on which the server
         is running.
    -->
    <attribute name="InvokerURLPrefix">http://</attribute>
    <attribute name="InvokerURLSuffix">:8080/invoker/EJBInvokerHAServlet</attribute>
    <attribute name="UseHostName">true</attribute>
</mbean>    

호출자 프록시에서 사용되는 URL은 클러스터 노드상에 배치된 EJBInvokerHAServlet의 매핑입니다. HttpInvokerHA 인스턴스는 클러스터에 걸쳐 장애복구(failover) 와/또는 로드 밸런싱을 위해 클라이언트측 프록시에서 사용이 가능한 http URL 후보자들의 컬렉션으로 구성됩니다.

5.3. EJB 컨테이너

EJB 컨테이너는 EJB의 특정한 클래스를 관리하는 컴포넌트입니다. JBoss에서는 배치된 하나의 EJB에 대한 각각의 고유한 설정에 대한 org.jboss.ejb.Container를 생성하는 하나의 인스턴스가 있습니다. 초기화되는 실제 객체는 Container의 서브클래스이며 컨테이너 인스턴스의 생성은 EJBDeployer MBean으로 관리됩니다.

5.3.1. EJBDeployer MBean

org.jboss.ejb.EJBDeployer MBean은 EJB 컨테이너의 생성을 책임지고 있습니다. 할당된 EJB JAR가 배치될 준비가되었다면, EJBDeployer가 EJB 각각의 타입에 대한 각각의 필요한 EJB 컨테이너들을 생성하고 초기화시킵니다. EJBDeployer의 설정가능한 속성들은 다음과 같습니다:

배치자(deployer)는 2개의 중요 메쏘드를 포함하고 있습니다: deploy와 undeploy. deploy 메쏘드는 EJB JAR 혹은 올바른 EJB JAR와 동일한 구조를 갖는 디렉터리(개발과정에서의 편의성을 위해 사용하는 형태)중 어느 하나를 가르키는 URL을 갖습니다. 일단 배치가 성공하면, 동일한 URL을 undeply 메쏘드를 호출함으로써 배치에서 제거할 수 있습니다. 이미 배치된 URL을 갖는 deploy 메쏘드의 호출은 re-deploy로써 undeply가 발생한 후 URL의 배치가 따르게 됩니다. JBoss는 완전한 재배치(re-deployment)를 구현(implementation)과 인터페이스 클래스 모두에서 지원하며 클래스에 어떠한 변경이 된다면 다시 로드하게 됩니다. 이러한 기능은 여러분이 EJB들을 개발하고 갱신하는 과정에서 동작하고 있는 서버를 정지시켰다가 다시 시작시키지 않아도 되도록 합니다.

EJB JAR를 배치하는 과정에서 EJBDeployer와 이에 관련된 클래스들은 3개의 메인 펑션인 EJB의 검증, 각각의 고유한 EJB에 대한 컨테이너 생성, 배치 설정 정보에 따른 컨테이너의 초기화를 수행합니다. 우리는 다음에 오는 섹션에서 이들 펑션들 각각에 대해서 논의하도록 하겠습니다.

5.3.1.1. EJB 배치의 검증(Verifying)

EJBDeployerVerifyDeployments 속성이 true일때, 배치자(deployer)는 배치되는 EJB들의 검증(verification)을 수행합니다. 검증에서는 EJB들이 EJB 사양과 호환되는지를 점검합니다. EJB 배치 유닛이 요구되는 home과 remote, local home과 local interfaces 및 이러한 인터페이스들내에 나타나는 객체들이 적절한 타입인지와 필요한 메쏘드들이 구현 클래스내에 존재하는지를 필연적으로 검증합니다. 이러한 기능은 EJB 개발자와 배치자가 올바른 EJB JAR를 잘 구성해야하는 복잡한 단계들에서 일으킬 수 있는 실수를 쉽게 바로잡을 수 있기때문에 유용합니다. 검증 단계에서는 모든 오류들을 찾아내고 배치 실패의 원인이되는 오류를 정정하는데 필요한 수정이 필요한 것들을 가르키게 됩니다.

EJB를 제작하는 프로그래밍적인 측면에서 본다면 빈 구현과 이것의 remote와 home 인터페이스 사이에는 배치 서술자 설정처럼 단절이 존재합니다. 이러한 분리된 요소들의 동기화(synch)는 쉽습니다. XDoclet은 이러한 문제를 해결해주는 툴중에 하나로써 표준 JavaDoc Doclet 엔진의 확장입니다. EJB 빈 구현 클래스내의 커스텀 JavaDoc 태그가 사라지며 배치 서술자와 같이 remote와 home 인터페이스들을 만듭니다. 보다 자세한 사항은 XDoclet 홈페이지(http://sourceforge.net/projects/xdoclet)를 참조하십시오.

5.3.1.2. 컨테이너에 EJB 배치하기

EJBDeployer에 의해 수행되는 가장 중요한 역할은 EJB 컨테이너의 생성과 컨테이너에 EJB를 배치하는 것입니다. 배치 단계는 EJB JAR내의 EJB들에 대한 반복적인 작업으로 구성되며 ejb-jar.xmljboss.xml 배치 서술자에서 기술된것처럼 빈 클래스와 메터데이터들의 압축을 풉니다. EJB JAR내의 각각의 EJB들은 다음과 같은 단계를 수행하게 됩니다:

모든 EJB들이 성공적으로 배치되면, 어플리케이션은 모든 컨테이너들을 구동하게 되고 클라이언트에서 EJB들을 사용할 수 있도록 합니다. 임의의 EJB가 배치에 실패하면, 배치 예외(exception)이 발생하고 배치 모듈은 실패합니다.

5.3.1.3. 컨테이너 설정 정보

EJB 컨테이너의 모든 셋업이 jboss_3_2.dtd에 맞는 XML 파일을 사용하지 않는다면 JBoss는 대부분의 것들을 외부화합니다. 컨테이너 설정 정보와 관련되있는 jboss_3_2 DTD 섹션이 그림 5.6, “컨테이너 설정과 관련된 jboss_3_2 DTD 요소들.”에 보여집니다.

컨테이너 설정과 관련된 jboss_3_2 DTD 요소들.

그림 5.6. 컨테이너 설정과 관련된 jboss_3_2 DTD 요소들.

container-configuration 요소와 이것의 서브 요소들에서는 container-name 요소에서 주어진대로 컨테이너의 타입에 대한 컨테이너 환경 설정을 합니다. 각각의 설정에서는 디폴트 호출자의 타입, 컨테이너 인터셉터의 구성, 인스턴스 캐쉬/풀과 이것들의 크기, 영속적인(persistence) 관리자, 보안(security) 등등과 같은 정보를 지정합니다. JBoss 컨테이너 아키텍쳐의 상세한 이해를 위해서는 필요한 정보가 상당히 많기때문에, JBoss는 4개의 EJB 타입에 대한 표준 설정을 제공하고 있습니다. 이러한 설정 파일은 standardjboss.xml로 불려지며 EJB들을 사용하는 설정 파일 세트의 conf 디렉터리내에 위치합니다. 예제 5.8, “ server/default/conf/standardjboss.xml 파일내의 복잡한 container-configuration 요소의 예제.”에서는 standardjboss.xml의 설정 샘플을 보여주고 있습니다.

예제 5.8.  server/default/conf/standardjboss.xml 파일내의 복잡한 container-configuration 요소의 예제.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss PUBLIC
          "-//JBoss//DTD JBOSS 3.2//EN"
          "http://www.jboss.org/j2ee/dtd/jboss_3_2.dtd">
<jboss>
    <!-- ... -->
    <container-configurations>
        <container-configuration>
            <container-name>Standard CMP 2.x EntityBean</container-name>
            <call-logging>false</call-logging>
            <invoker-proxy-binding-name>entity-rmi-invoker</invoker-proxy-binding-name>
            <sync-on-commit-only>false</sync-on-commit-only>
            <container-interceptors>
                <interceptor>
                    org.jboss.ejb.plugins.ProxyFactoryFinderInterceptor
                </interceptor>
                <interceptor>org.jboss.ejb.plugins.LogInterceptor</interceptor>
                <interceptor>org.jboss.ejb.plugins.SecurityInterceptor</interceptor>
                <interceptor>org.jboss.ejb.plugins.TxInterceptorCMT</interceptor>
                <interceptor metricsEnabled="true">
                    org.jboss.ejb.plugins.MetricsInterceptor
                </interceptor>
                <interceptor>org.jboss.ejb.plugins.EntityCreationInterceptor</interceptor>
                <interceptor>org.jboss.ejb.plugins.EntityLockInterceptor</interceptor>
                <interceptor>org.jboss.ejb.plugins.EntityInstanceInterceptor</interceptor>
                <interceptor>
                    org.jboss.ejb.plugins.EntityReentranceInterceptor
                </interceptor>
                <interceptor>
                        org.jboss.resource.connectionmanager.CachedConnectionInterceptor
                </interceptor>
                <interceptor>
                        org.jboss.ejb.plugins.EntitySynchronizationInterceptor
                </interceptor>
                <interceptor>
                        org.jboss.ejb.plugins.cmp.jdbc.JDBCRelationInterceptor
                </interceptor>
            </container-interceptors>
            <instance-pool>
                org.jboss.ejb.plugins.EntityInstancePool
            </instance-pool>
            <instance-cache>
                org.jboss.ejb.plugins.InvalidableEntityInstanceCache
            </instance-cache>
            <persistence-manager>
                org.jboss.ejb.plugins.cmp.jdbc.JDBCStoreManager
            </persistence-manager>
            <locking-policy>
                org.jboss.ejb.plugins.lock.QueuedPessimisticEJBLock
            </locking-policy>
            <container-cache-conf>
                <cache-policy>
                    org.jboss.ejb.plugins.LRUEnterpriseContextCachePolicy
                </cache-policy>
                <cache-policy-conf>
                    <min-capacity>50</min-capacity>
                    <max-capacity>1000000</max-capacity>
                    <overager-period>300</overager-period>
                    <max-bean-age>600</max-bean-age>
                    <resizer-period>400</resizer-period>
                    <max-cache-miss-period>60</max-cache-miss-period>
                    <min-cache-miss-period>1</min-cache-miss-period>
                    <cache-load-factor>0.75</cache-load-factor>
                </cache-policy-conf>
            </container-cache-conf>
            <container-pool-conf>
                <MaximumSize>100</MaximumSize>
            </container-pool-conf>
            <commit-option>B</commit-option>
        </container-configuration>
        <!-- ... -->
    </container-configurations>
</jboss>

두 예제를 통해 컨테이너 설정 옵션들이 어떻게 사용되는지를 보여주게 됩니다. 컨테이너 설정 정보는 두 레벨로 지정될 수 있습니다. 첫 번째는 환경설정 파일 세트 디렉터리에 들어있는 standardjboss.xml입니다. 두 번째는 EJB JAR 레벨입니다. EJB JAR의 META-INF 디렉터리내에 jboss.xml 파일을 위치시킴으로써, 여러분은 standardjboss.xml 파일에서 설정한 컨네이너 환경을 덮어쓰거나 완전히 새로운 이름을 갖는 컨테이너 환경을 지정할 수 있습니다. 이것은 컨테이너의 설정에 대해 매우 유연한 정책을 가능하게 합니다. 즉 모든 컨테이너의 환경설정 속성들이 외부로 노출되고 또한 쉽게 수정될 수 있기 때문입니다. 능숙한 개발자는 인스턴스 풀이나 캐쉬와 같은 특별한 컨테이너 컴포넌트들까지도 구현할 수 있으며, 이것을 표준 컨테이너 환경설정에 쉽게 통합시켜 특정한 어플리케이션이나 환경에 최적화된 동작을 꾀할 수 있습니다.

jboss.xml에서 EJB를 컨테이너 환경설정에 매핑시키는 요소들

그림 5.7. jboss.xml에서 EJB를 컨테이너 환경설정에 매핑시키는 요소들

EJB 배치가 자신의 컨테이너 환경설정을 어떻게 선택하는지는 명시적(explicit)이거나 암묵적인(implict) jboss/enterprise-beans/<type>/configuration-name에 기반을 둡니다. 그림 5.7, “jboss.xml에서 EJB를 컨테이너 환경설정에 매핑시키는 요소들”에서는 jboss.xml DTD가 어떤 컨테이너 설정에서 사용되어야만 EJB가 선언되는지를 보여주고 있습니다.

configuration-name 요소는 그림 5.6, “컨테이너 환경에 관련된 jboss_3_2 DTD 요소들.”에 있는 container-configurations/container-configuration 요소에 연결됩니다. 이것은 참조하는 EJB에 사용될 컨테이너 환경을 지정합니다. configuration-name 요소로부터 container-name 요소로 연결됩니다. 여러분은 EJB 정의내에 container-configuration 요소를 포함시킴으로써 EJB의 클래스당 컨테이너 환경설정을 지정할 수도 있습니다. 보통 이것이 지원되기는 하나 완전히 새로운 컨테이너 환경을 정의하지는 않습니다. jboss.xml 레벨에서의 container-configuration에 대한 일반적인 사용법은 standardjboss.xml 서술자에서 설정된 하나 이상의 container-configuration을 덮어쓰는 것입니다. 이를 위해서는 container-configuration/extends속성으로써 이미 존재하고 있는 standardjboss.xml container-configuration/container-name의 참조하는 이름인 conatiner-configuration을 지정해주는 것입니다. 예제 5.9, “ 안전한 액세스가 가능하도록 standardjboss.xml 컨테이너 비상태 빈 환경설정을 덮어쓰는 예.”에서는 Standard Stateless SessionBean이라는 이름을 갖는 표준 비상태 세션 환경설정의 확장된 환경설정인 하나의 새로운 Secured Stateless SessionBean 환경설정을 정의하는 예를 보여주고 있습니다.

예제 5.9. 안전한 액세스가 가능하도록 standardjboss.xml 컨테이너 비상태 빈 환경설정을 덮어쓰는 예.

<?xml version="1.0"?>
<jboss>
    <enterprise-beans>
        <session>
            <ejb-name>EchoBean</ejb-name>
            <configuration-name>Secured Stateless SessionBean</configuration-name>
            <!-- ... -->
        </session>
    </enterprise-beans>
    <container-configurations>
        <container-configuration extends="Standard Stateless SessionBean">
            <container-name>Secured Stateless SessionBean</container-name>
            <!-- Override the container security domain -->
            <security-domain>java:/jaas/my-security-domain</security-domain>
        </container-configuration>
    </container-configurations>
</jboss>

만약 EJB가 배치 유닛 EJB JAR내에 컨테이너 환경 사양을 제공하지 않을 경우, 컨테이너 팩토리는 EJB의 타입에 기반하여 standardjboss.xml 서술자로부터 컨테이너 환경설정을 선택합니다. 따라서 실제로는 EJB의 모든 타입에 대해 암묵적인 configuration-name 요소가 있으며 EJB 타입으로부터 디폴트 컨테이너 환경설정 이름이 다음과 같이 매핑됩니다:

만일 여러분이 빈 타입에 따라 디폴트로 매핑되는 값을 사용하고자 한다면 EJB의 컨테이너 환경설정을 지정할 필요가 없게 됩니다. configuration-name 요소를 포함하는 self-contained 서술자를 제공할 수도 있지만 이것은 여러분들의 스타일에 따르는 문제일 뿐입니다.

이제 여러분을은 EJ에서 사용하는 컨테이너 환경설정을 어떻게 지정하는지 알게 되었고, 따라서 여러분은 배치 유닛 레벨의 덮어쓰기를 정의할 수 있으므로 남은 문제는 container-configuration의 자식 요소들에 대한 것입니다. 이 자식요소들에 대한 설명은 다음 섹션에서 다루어지게 될 것입니다. 많은 요소들에서 다른 요소들에 의해 영향을 받는 환경설정을 갖는 인터페이스 클래스의 구현들을 지정하기때문에 환경설정 요소들에 대한 논의 이전에 여러분들은 org.jboss.metadata.XmlLoadable 인터페이스에 대한 이해가 필요합니다.

XmlLoadable 인터페이스는 단일 메쏘드로 구성된 간단한 인터페이스입니다. 인터페이스의 정의는 다음과 같습니다:

import org.w3c.dom.Element;
public interface XmlLoadable
{
    public void importXml(Element element) throws Exception;
}

클래스는 자신의 환경설정이 XML 문서의 조각(fragment)으로 지정될 수 있도록 하기위해 이 인터페이스를 구현합니다. 문서의 root 요소는 importXml 메쏘드로 넘겨지게 됩니다. 여러분은 다음 절에서 기술되는 컨테이너의 환경설정 요소들로써 이러한 몇몇 예제들을 보게 될 것입니다.

5.3.1.3.1. container-name 요소

container-name 요소는 주어진 환경에 대한 고유한 이름을 지정하는 요소입니다. EJB는 자신들의 configuration-name 요소를 컨터에너 환경설정을 위한 container-name의 값으로 설정하는 특정한 컨테이너 환경설정에 연결됩니다.

5.3.1.3.2. call-logging 요소

call-logging 요소에는 LogInterceptor가 컨테이너에 대한 log 메쏘드를 호출할지에 대한 사항을 가르키는 값으로써 부울린(true 또는 false) 값이 지정됩니다. 이것은 보다 정교하게 로깅 API를 지원하는 log4j로 변경됨으로써 더이상 지원되지 않는 경향이 있습니다.

5.3.1.3.3. invoker-proxy-binding-name 요소

invoker-proxy-binding-name 요소에는 사용할 디폴트 호출자의 이름을 지정합니다. 빈 레벨에서의 invoker-bindings 지정이 빠져있다면, invoker-proxy-binding-name 요소 값과 일치되는 이름을 갖는 invoker-proxy-binding이 home과 remote 프록시를 생성하는데 사용됩니다.

5.3.1.3.4. container-interceptors 요소

container-interceptors 요소에는 컨테이너를 위한 메쏘드 인터셉터 체인으로써 설정되어지는 하나 이상의 인터셉터 요소들을 지정합니다. 인터셉터 요소의 값은 org.jboss.ejb.Interceptor 인터페이스 구현의 완전한 형태를 갖는 클래스 이름입니다. 컨테이너 인터셉터는 EJB 메쏘드 호출을 넘겨주는 linked-list 구조로 구성됩니다. 체인의 첫 번째 인터셉터는 컨테이너에 MBeanServer가 메쏘드를 넘겨줄때 호출됩니다. 마지막 인터셉터는 빈에대한 비즈니스 메소드를 호출합니다. 우리는 컨터이너 플러그인 프레임워크에 대해 알아보는 이번 장의 후반부에서 Interceptor 인터페이스에 대해 논의하겠습니다. 일반적으로 보안, 트랜잭션, 영속성 및 인터셉터로부터 파생되는 쓰레드의 안정성등과 같은 기존의 표준 EJB 인터셉터 환경설정을 변경하고자 할때는 주의를 요해야 합니다.

5.3.1.3.5. instance-pool 과 container-pool-conf 요소들
instance-pool 과 container-pool-conf 요소들

그림 5.8. instance-pool 과 container-pool-conf 요소들

instance-pool 요소에는 컨테이너 InstancePool로써 사용하려는 org.jboss.ejb.InstancePool 인터페이스 구현의 완전한 형태를 갖는 클래스 이름을 지정합니다. InstancePool 인터페이스에 대한 자세한 사항은 컨테이너 플러그인 프레임워크에 대해 논의하는 이번장의 후반부에서 다루어지게 됩니다.

container-pool-confXmlLoadable 인터페이스를 구현하는 경우에는 instance-pool 요소에 의해 주어진 InstancePool 구현쪽으로 넘겨집니다. 현재 모든 JBoss의 InstancePool 구현들은 org.jboss.ejb.plugins.AbstractInstancePool 클래스로부터 파생되며, MinimumSize, MaximumSize, strictMaximumSizestrictTimeout container-pool-conf 자식 요소들에 대한 지원을 제공합니다. JBoss가 InstancePoolMinimumSize 값을 지정하지 않더라도 MinimumSize 요소에는 풀내에 유지할 최소한의 인스턴스 갯수를 지정합니다.

MaximumSize에는 풀내에 허용되는 인스턴스의 최대 갯수를 지정합니다. 여러분은 기본적으로 MaxiumSize를 사용하는 것을 원하지 않을 수 있습니다. 풀의 MaximumSize는 유지가능한 EJB 인스턴스의 최대 갯수이지만 동시 요청이 MaximumSize 값을 넘어설 경우 추가로 인스턴스들을 생성시킬 수 있습니다. 풀의 MaximumSize에 EJB의 최대 동시 요청수를 제한하고자 한다면, strictMaximumSize 요소를 true로 설정해야 합니다. strictMaximumSize가 true일 경우에는 오직 MaximumSize 만큼의 EJB 인스턴스만이 활성화되게 됩니다. 만약 MaximumSize 만큼의 인스턴스가 활성화되어있다면, 이후에 받게되는(subsequent) 요청들은 풀이 빠질때까지는 블록킹됩니다. strictMaximumSize의 디폴트 값은 false 입니다. 인스턴스 풀 객체가 요청에 대한 블록킹 대기시간을 얼마나 잡느냐는 strictTimeout 요소로 제어합니다. strictTimeout 요소는 인스턴스가 MaximumSize까지 차있을때 풀에서 인스턴스를 반환받을 수 있을때까지 대기하는 시간을 밀리세컨드 단위로 지정합니다. 이 값이 0인 경우, 기다리지 말라는 의미가 됩니다. 요청이 인스턴스를 대기하는 시간을 초과하게되면, java.rmi.ServerException이 발생하고 호출은 취소됩니다. 이것은 Long으로 파싱되므로 최대 가능한 대기시간은 9,223,372,036,854,775,807 혹은 대략 292,471,208년이 되며, 이것이 기본 값입니다.

5.3.1.3.6. instance-cache 와 container-cache-conf 요소들
instance-cache 와 container-cache-conf 그리고 이와 관련된 요소들

그림 5.9. instance-cache 와 container-cache-conf 그리고 이와 관련된 요소들

instance-cache 요소에는 org.jboss.ejb.InstanceCache 인스턴스 구현의 완전한 형태를 갖는 클래스 이름이 지정됩니다. 이 요소는 식별성(identity)과 관련된 EJB 타입들인 엔티티와 상태 세션 빈즈에만 의미가 있습니다. 컨테이너 플러그인 프레임워크에 대한 논의를 하는 이번 장의 후반부에서 InstanceCache 인터페이스의 상세한 내용을 알아보도록 하겠습니다.

container-cache-conf 요소는 InstanceCache 구현에서 XmlLoadable 인터페이스를 지원할 경우에만 넘겨집니다. 현재 모든 JBoss의 InstanceCache 구현들은 org.jboss.ejb.plugins.AbstractInstanceCache 클래스로부터 파생되며, XmlLoadable 인터페이스를 지원하도록 제공되고 인스턴스 캐쉬가 저장되는데 사용되는 org.jboss.util.CachePolicy 구현의 완전한 형태를 갖는 클래스 이름으로써 cache-policy 자식 요소를 사용합니다. cache-policy-conf 자식 요소는 CachePolicy 구현에서 XmlLoadable 인터페이스를 지원하는 경우에만 넘겨집니다. 만일 지원하지 않는다면, cache-policy-conf는 조용히 무시됩니다.

cache-policy-conf 자식 요소들의 현재 배열을 지원하는 standardjboss.xml 환경설정에 의해 사용되는 CachePolicy의 JBoss 구현에는 두 개가 있습니다. 이 클래스들은 org.jboss.ejb.plugins.LRUEnterpriseContextCachePolicyorg.jboss.ejb.plugins.LRUStatefulContextCachePolicy입니다. LRUEnterpriseContextCachePolicy는 엔티티 빈 컨테이너에서 사용되며, LRUStatefulContextCachePolicy는 상태 세션 빈 컨테이너에서 사용됩니다. 두 개 모두 다음과 같은 cache-policy-conf 자식 요소들을 지원합니다:

  • min-capacity: 캐쉬의 최소 용량을 지정합니다.
  • max-capacity: 캐쉬의 최대 용량을 지정합니다. 이때 이 값이 min-capacity보다 작아서는 않됩니다.
  • overager-period: 캐쉬 제한기간을 넘어서는(overager) 작업이 실행되는 간격을 초단위로 지정합니다. overager 작업의 목적은 캐쉬가 max-bean-age 요소의 값보다 큰 age를 갖는 빈즈를 포함하고 있는지를 알아보기 위해서입니다. 이 조건을 만나는 어떠한 빈즈라도 부동태(不動態-passivated)로 바뀝니다.
  • max-bean-age: overager 프로세스에 의해 부동태로 바뀌기전까지 빈의 비활성(inactivity) 최대 기간을 초단위로 지정합니다.
  • resizer-period: 크기 재설정 작업(resizer)이 실행되는 간격을 초단위로 지정합니다. 크기 재설정 작업은 다음과 방식으로 아래에 남아있는 3개의 요소 값에 기반하여 캐쉬의 용량을 줄이거나 늘리게 됩니다. 크기 재설정 작업이 실행되면, 현재의 cache-miss-period를 점검하여 그 주기(period)가 min-cache-miss-period 값보다 작은 경우에는 cache-load-factor를 사용하여 max-capacity 값까지 캐쉬를 늘려줍니다. 이와 반대로 max-cache-miss-period 값보다 큰 경우에는 cache-load-factor를 사용하여 캐쉬를 줄여주게 됩니다.
  • max-cache-miss-period: 캐쉬가 되지 않는 경우(cache miss) 가 발생하면 캐쉬 용량을 줄이라고 신호(signal)를 보내는 기간을 초단위로 지정합니다. 이 값은 캐쉬가 줄어들기전에 참게되는 최소 missrate와 똑같습니다.
  • min-cache-miss-period: 캐쉬가 되지 않는 경우(cache miss) 가 발생하면 캐쉬 용량을 늘리라고 신호(signal)를 보내는 기간을 초단위로 지정합니다. 이 값은 캐쉬가 줄어들기전에 참게되는 최대 missrate와 똑같습니다.
  • cache-load-factor: 캐쉬의 용량을 늘이거나 줄여주는 비율을 지정합니다. factor는 1보다 작아야 합니다. 캐쉬의 용량이 줄어들면 빈의 현재 비율도 cache-load-factor 값과 동일하게 됩니다. 캐쉬가 늘어나게될 때는 current-capacity *1/cache-load-factor만큼 새로운 용량이 추가됩니다. 실제 늘어나는 비율은 캐쉬가 되지 않은 갯수에 기초를 둔 내부 알고리즘을 통해 최대 2배까지도 갈 수 있습니다. 캐쉬가 되지 않는 비율이 증가될 수록 실제 늘어나는 비율 또한 2에 가깝게 됩니다.

또한 LRUStatefulContextCachePolicy 는 남아있는 자식 요소들을 지원합니다:

  • remover-period: 제거 작업(remover task)가 실행되는 사이의 초단위 주기를 지정합니다. 제거 작업에서는 max-bean-life에 지정된 초보다 길게 빈을 액세스하지 않는 부동태 빈즈를 제거합니다. 이 작업은 사용자들에 의해 수동태화된(passivation) 저장소(store)를 채움으로써 제거되지 않는 상태 셔션 빈즈가 생기는것을 를 방지하게 됩니다.
  • max-bean-life: 수동태화된 저장소에 존재하는 빈을 제거하기전까지의 비동작(inactivity) 상태를 초단위의 최대 기간으로 지정합니다.

또다른 캐쉬 정책의 구현은 그저 인스턴스들을 결코 수동태화시키지 않는 org.jboss.ejb.plugins.NoPassivationCachePolicy 클래스입니다. 이것은 명시적으로 제거하기전까지는 결코 인스턴스들을 버리지 않는 인-메모리 HashMap 구현을 사용합니다. 이 클래스는 어떠한 cache-policy-conf 환경설정 요소들도 지원하지 않습니다.

5.3.1.3.7. persistence-manager 요소

persistence-manager 요소의 값은 영속적인(persistence) 관리자 구현의 완전한 형태를 갖는 클래스 이름이 지정됩니다. 구현의 타입은 EJB의 타입에 영향을 받습니다. 상태 세션 빈의 경우, org.jboss.ejb.StatefulSessionPersistenceManager 인터페이스의 구현이어야만 합니다. CMP 엔티티 빈즈가 org.jboss.ejb.EntityPersistenceStore 인터페이스의 구현이 되어야만 하는 반면, BMP 엔티티 빈즈는 org.jboss.ejb.EntityPersistenceManager 인터페이스의 구현이어야만 합니다.

5.3.1.3.8. web-class-loader 요소

web-class-loader 요소에는 배치된 EAR들과 EJB JARs 및 WAR 파일로부터 리소스들과 클래스들의 동적인 로딩이 가능하도록 하는 WebService MBean과 연결되어 사용되는 org.jboss.web.WebClassLoader 서브클래스를 지정합니다. 하나의 WebClassLoader는 하나의 Container와 관계되며 자신의 부모로써 하나의 org.jboss.mx.loading.UnifiedClassLoader를 가져야만 합니다. 이것은 로컬 로딩을 위해 사용되는것보다는 원격 로딩을 위한 URL들의 다른 세트를 반환하는 getURLs() 메쏘드를 덮어씁니다.

WebClassLoader는 서브클래스에 의해 덮어씌여지는 의미를 갖는 두개의 메쏘드를 갖습니다: getKey() 그리고 getBytes(). 후자는 이 구현내에서는 동작되지 않고(no-op) IIOP 모듈에서 사용되는 classloader 처럼 바이트코드 생성이 가능한 서브클래스에 의해 덮어씌여집니다.

WebClassLoader의 서브클래스들은 WebClassLoader(ObjectName containerName, UnifiedClassLoader parent) 구성자(constructor)로써 동일한 서명(signature)을 갖는 구성자를 갖아야만 합니다.

5.3.1.3.9. locking-policy 요소

locking-policy 요소에는 EJB 락 구현에 사용되는 완전한 형태의 클래스 이름을 지정합니다. 이 클래스는 org.jboss.ejb.BeanLock 인터페이스를 반드시 구현해야 합니다. 현재의 JBoss 버전에 포함된 것은 다음과 같습니다:

  • org.jboss.ejb.plugins.lock.QueuedPessimisticEJBLock: 올바른 FIFO 큐내에서 공급되는 트랜잭션 락을 대기하는 쓰레드를 잡고있도록 하는 구현. 비-트랜잭션 쓰레드들 또한 이 대기 큐에 넣어지게 됩니다. 이 클래스는 큐에서 다음 대기중인 트랜잭션을 꺼집어내어 해당 트랜잭션에 관련된 대기중인 쓰레드들에만 통지합니다. 현재는 QueuedPessimisticEJBLock이 표준 환경설정에서 사용되는 디폴트입니다.
  • org.jboss.ejb.plugins.lock.SimpleReadWriteEJBLock: 이것은 락으로 하여금 동시에 여러개의 읽기 락이 허용되도록 합니다. 일단 writer가 락을 요청하면, 모든 writer가 완료되기전까지는 아직 읽기 락을 갖지 않고 있는 트랜잭션들의 읽기-락 요청의 발생에 대해 락을 블럭킹하게되고 그런 다음 모든 대기중인 reader들은 동시에 진행(재입력이 가능한 setting/methodLock에 따라)하게 됩니다. 쓰기 락에서의 첫 번째 크랙을 진행하려는 reader는 다른 대기중인 writer에 앞섭니다. 이미 진행되는 reader가 있다면, 불일치 읽기 예외가 발생합니다. 물론, writer들은 쓰기 락을 걸기전에 모든 읽기-락을 풀때까지 기다려야 합니다.
  • org.jboss.ejb.plugins.lock.NoLock: 트랜잭션 컨테이너 환경설정당 인스턴스를 사용하는 비-락킹 정책.

5.4 절, “엔티티 빈의 락킹과 데드락 검출”에서 락킹 정책의 사용법에 대해 자세히 논의하게 될 것입니다.

5.3.1.3.10. commit-option 과 optiond-refresh-rate 요소

commit-option 값에는 EJB 엔티티 빈의 영속적인 저장소 커밋 옵션을 지정합니다. 이 값에는 A,B,C 또는 D 중에 하나가 와야 합니다. 옵션들은 다음과 같은 의미를 갖습니다:

  • A: 컨테이너가 트랜잭션사이에서의 빈 상태를 캐쉬합니다. 이 옵션은 컨테이너가 오직 영속적인 저장소를 사용자만이 액세스한다는 가정을 합니다. 이러한 가정을 통해 컨테이너는 꼭 필요할때만 영속적인 저장소로부터 인-메모리 상태를 통기화하는 것을 가능케 합니다. 이것은 첫 번째 비즈니스 메쏘드가 발견된 빈에 대해 실행되기전이나 빈이 다른 비즈니스 메쏘드를 다루기 위해 수동화되고 비활성화된 후에 일어납니다. 이러한 동작(behavior)은 비즈니스 메쏘드가 트랜잭션의 컨텍스트 내에서 실행되는가와는 독립적입니다.
  • B: 컨테이너는 트랜잭션사이에서의 빈 상태를 캐쉬합니다만 A 옵션과 달리 컨테이너는 영속적인 저장소에 배타적인 액세스를 가정하고 있지 않습니다. 따라서, 컨테이너는 각각의 트랜잭션이 시작되기전에 인-메모리 상태를 동기화하게 됩니다. 결국 트랜잭션 컨텍스트내에서 실행되는 비즈니스 메쏘드는 빈을 캐슁하는 컨테이너로부터 많은 이득을 얻을 수 없으므로 트랜잭션 컨텍스트의 외부(트랜잭션 속섣을은 Never, NotSupported 혹은 Supports)에서 실행되는 비즈니스 메쏘드는 빈의 캐쉬된(그리고 잠재적으로는 올바르지 않을 수도 있는) 상태를 액세스합니다.
  • C: 컨테이너가 빈의 인스턴스들을 캐쉬하지 않습니다. 인-메모리 상태는 반드시 각각의 트랜잭션이 시작될때 동기화되어져야만 합니다. 트랜잭션의 외부에서 실행되는 비즈니스 메쏘드에 대해서는 여전히 동기화(synchronization)이 수행되지만, 호출자(caller)의 것과 동일한 트랜잭션 컨텍스트내에서 ejbLoad가 실행됩니다.
  • D: EJB 사양서에는 기술되어 있지 않은 JBoss에서만 제공되는 기능입니다. 빈의 상태가 A 옵션에서 처럼 트랜잭션사이에서 캐쉬되는 느린(lazy) 읽기 스키마이지만, 상태는 주기적으로 자산의 영속적인 저장소와 동기화됩니다. 재로딩되는 간격의 기본은 30초입니다만 optiond-refresh-rate 요소에서 이 값을 설정해줄 수 있습니다.
5.3.1.3.11. security-domain 요소

EJB org.jboss.ejb.Container 클래스 내부에서 security-domain 요소는 org.jboss.security.AuthenticationManagerorg.jboss.security.RealmMapping 인터페이스를구현하는 객체의 JNDI 이름을 지정합니다. 일반적으로 jboss root 요소아래에 전역적인 security-domain으로 지정되기 때문에 할당된 배치내의 모든 EJB들은 모두 보호됩니다. 보안 관리자 인터페이스들과 보안 레이어의 설정에 대한 세부사항은 8 장, JBoss에서의 보안에서 다루어지게 됩니다.

5.3.1.3.12. cluster-config
cluster-config 과 이에 관련된 요소들

그림 5.10. cluster-config 과 이에 관련된 요소들

cluster-config 요소는 컨테이너 환경을 사용하는 모든 EJB들의 클러스터에 관련된 설정을 지정하는 것이 가능하게 합니다. 클러스터 환경설정의 사양은 컨테이너 환경설정 레벨이나 개별적인 EJB 배치 레벨에서 이루어질 수 있습니다.

partition-name 요소에서는 클러스터링 정보를 교환하는 컨테이너에 의해 사용되는 org.jboss.ha.framework.interfaces.HAPartition 인터페이스를 찾을 수 있도록 지정합니다. 이것은 HAPartition이 연결된 하부의 완전한 JNDI 이름이 아닙니다. 이보다는 요구되는 클러스터를 관리하는 ClusterPartitionMBean 서비스의 PartitionName 속성에 대응됩니다. HAPartition에 바인딩되는 실제 JNDI 이름은 tt class="literal">partition-name값에 /HASessionState/를 붙여주는 형태가 됩니다. 기본 값은 DefaultPartition입니다.

home-load-balance-policy 요소는 home 프록시에 만들어지는 로드 밸런스 호출에 사용되는 자바 클래스 이름을 가르킵니다. 클래스는 반드시 org.jboss.ha.framework.interface.LoadBalancePolicy 인터페이스가 구현되어야만 합니다. 기본 정책은 org.jboss.ha.framework.interfaces.RoundRobin입니다.

bean-load-balance-policy 요소는 빈 프록시에서 로드 밸런스 호출에 사용되는 자바 클래스 이름을 가르킵니다. 클래스에서는 org.jboss.ha.framework.interface.LoadBalancePolicy 인터페이스가 반드시 구현되어야만 합니다. 엔티티 빈즈와 상태 세션 빈에 대한 기본값은 org.jboss.ha.framework.interfaces.FirstAvailavble입니다. 비상태 세션 빈의 경우는 org.jboss.ha.framework.interfaces.RoundRobin이 기본값입니다.

session-state-manager-jndi-name 요소에서는 클러스터내의 상태 세션 관리를 위한 백엔드인 컨테이너에서 사용되는 org.jboss.ha.framework.interfaces.HASessionState 이름을 가르킵니다. partition-name 요소와는 달리, 이것은 연결된 HASessionState 구현밑의 JNDI 이름입니다. 기본 위치로 /HASessionState/Default가 사용됩니다.

5.3.1.3.13. depends

depends 요소에서는 컨테이너나 EJB에 의존하는 서비스의 JMX ObjectName을 주게 됩니다. 다른 서비스들에 대한 명백한 의존성을 지정함으로써 필요한 서비스들이 구동된 이후에 배치되는 순서가 신뢰될 수 있도록 합니다.

5.3.2. 컨테이너 플러그인 프레임워크

JBoss EJB 컨테이너는 컨테이너 동작(behavior)의 다양한 면들에 대한 구현을 변경할 수 있도록 하기 위해 프레임워크 패턴을 사용합니다. 컨테이너 그 자체로는 다양한 동작을 갖는 컴포넌트들을 함께 연결하는 것말고는 다른 중요한 작업을 수행할 수 없습니다. 컨테이너의 환경설정을 변경시킴으로써 새로운 구현을 플러그인 시킬 수 있기때문에 동작되는 컴포넌트들의 구현은 플러그인으로 참조됩니다. 동작의 플러그인에 대한 예로, 여러분이 영속적인 관리, 객체 풀링, 객체 캐슁을 하는 컨테이너 호출자와 인터셉터를 원한다고 생각해보겠습니다. org.jboss.ejb.Container 클래스에는 네 개의 서브클래스가 있으며, 각각의 클래스는 특정한 빈 타입을 구현합니다:

  • org.jboss.ejb.EntityContainerjavax.ejb.EntityBean 타입들을 처리합니다.
  • org.jboss.ejb.StatelessSessionContainer는 비상태 javax.ejb.SessionBean 타입들을 처리합니다.
  • org.jboss.ejb.StatefulSessionContainer는 상태 javax.ejb.SessionBean 타입들을 처리합니다.
  • org.jboss.ejb.MessageDrivenContainerjavax.ejb.MessageDrivenBean 타입들을 처리합니다.

EJB 컨테이너들은 자신의 동작 대부분을 컨테이너 플러그인이라 알려진 컴포넌트들로 위임합니다. 컨테이너 플러그인을 구성하는 인터페이스들은 아래에 나열된 것을 가르킵니다:

  • org.jboss.ejb.ContainerPlugin
  • org.jboss.ejb.ContainerInvoker
  • org.jboss.ejb.Interceptor
  • org.jboss.ejb.InstancePool
  • org.jboss.ejb.InstanceCache
  • org.jboss.ejb.EntityPersistanceManager
  • org.jboss.ejb.EntityPersistanceStore
  • org.jboss.ejb.StatefulSessionPersistenceManager

컨테어너의 주요한 임무는 자신의 플러그인을 관리하는 것입니다. 즉 요구되는 기능성을 구현하기 위한 모든 플러그인 정보를 확실하게 하는 것입니다.

5.3.2.1. org.jboss.ejb.ContainerPlugin

ContainerPlugin 인터페이스는 모든 컨테이너 플러그인 인터페이스들의 부모 인터페이스입니다. 컨테이너가 자신의 플러그인 각각에 대해 컨테이너를 가르켜 플러그인이 대신 동작하도록 허용하는 콜백을 제공합니다. ContainerPlugin 인터페이스는 아래와 같이 주어집니다.

예제 5.10. org.jboss.ejb.ContainerPlugin 인터페이스

public interface ContainerPlugin
    extends org.jboss.system.Service
{
    /** 
     * 이 콜백은 컨테이너에 의해 설정되기 때문에 
     * 플러그인은 자신의 컨테이너를 액세스할 수 있습니다.
     *
     * @param con the container which owns the plugin
     */
    public void setContainer(Container con);
}   

5.3.2.2. org.jboss.ejb.Interceptor

Interceptor 인터페이스는 각각의 EJB 메쏘드 호출이 넘겨주어야만 하는 메쏘드 인터셉터들의 체인을 만들수 있도록 합니다. Interceptor 인터페이스는 다음과 같이 주어집니다.

예제 5.11. org.jboss.ejb.Interceptor 인터페이스

import org.jboss.invocation.Invocation;
                    
public interface Interceptor 
    extends ContainerPlugin
{
    public void setNext(Interceptor interceptor);
    public Interceptor getNext();
    public Object invokeHome(Invocation mi) throws Exception;
    public Object invoke(Invocation mi) throws Exception;
} 

컨테이너 환경설정내에서 정의된 모든 인터셉터들은 EJBDeployer에 의해 컨테이너 인터셉터 체인에 생성되고 추가됩니다. 마지막 인터셉터는 EJB 빈 구현과 상호 작용하는 인터셉트이므로 배치자(deployer)에 의해 추가되는 것이 아니라 컨테이너 자신에 의해 추가됩니다.

체인내의 인터셉터 순서는 중요합니다. 이러한순서에 대한 아이디어는 인터셉터들이 캐쉬및 풀과 함께 상호작용하기전에 특정한 EnterpriseContext 인스턴스가 자리를 잡도록 시도하지 않기 때문입니다.

Interceptor 인터페이스의 구현은 Invocation 객체가 넘겨지는 linked-list와 유사한 구조로 형성됩니다. 체인을 구성하는 첫 번째 인터셉터는 호출자가 JMX 버스를 통해 컨테이너에 Invocation을 넘겨줄 때 호출됩니다. 마지막 인터셉터는 빈의 비즈니스 메쏘드를 호출합니다. 여기에는 보통 빈의 타입과 컨테이너의 설정에 따라 체인내에서 다섯개의 인터셉터 순서로 이루어집니다. Interceptor 시맨틱은 간단한것에서부터 복잡한 것까지 다양한 범위를 갖습니다. 간단한 인터셉터의 예로는 LoggingInterceptor를 들 수 있으며, 복잡한 예로는 EntitySynchronizationInterceptor가 있습니다.

인터셉터 패턴이 갖는 가장 커다란 장점으로는 인터셉터의 배열에 대한 유연성입니다. 또 다른 장점으로는 서로 다른 인터셉터들간의 기능적인 구별이 명확하다는 것입니다. 즉, 트랜잭션의 로직과 보안성은 각각 TXInterceptorSecurityInterceptor로 명백하게 구별됩니다.

인터셉터들중 어느것 하나라도 실패할 경우, 호출은 그 시점에서 종료됩니다. 이것은 시맨틱의 fail-quickley 타입입니다. 즉, 보안 처리된 EJB가 절절한 권한없이 액세스된다면, 어떠한 트랜잭션의 시작이나 인스턴스들의 캐쉬 업데이트 이전에 호출은 SecurityInterceptor 으로 실패합니다.

5.3.2.3. org.jboss.ejb.InstancePool

InstancePool은 어떠한 식별성(identity)과도 관련되지 않은 EJB 인스턴스들을 관리하기 위해 사용됩니다. 실제로 풀은 관련되지 않은 빈 인스턴스들과 이에 연계된 데이터를 결합하는 org.jboss.ejb.EnterpriseContext 객체들의 서브클래스들을 관리합니다.

예제 5.12.  org.jboss.ejb.InstancePool 인터페이스

public interface InstancePool
    extends ContainerPlugin
{
    /** 
     * 식별성(identity)없이 인스턴스 얻기.
     * finders와 create-methods혹은 stateless beans에서 사용될 수 있음.
     *
     * @return Context /w instance
     * @exception RemoteException
     */
    public EnterpriseContext get() throws Exception;
    
    /** Return an anonymous instance after invocation.
     *
     * @param ctx
     */
    public void free(EnterpriseContext ctx);
    
    /**
     * 호출이후에 anonymous 인스턴스를 버림.
     * 호출로부터 예외가 발생되어 인스턴스가 거절될 때 호출됨.
     *
     * @param ctx
     */
    public void discard(EnterpriseContext ctx);
    
    /**
     * Return the size of the pool.
     *
     * @return the size of the pool.
     */
    public int getCurrentSize();
    
    /**
     * Get the maximum size of the pool.
     *
     * @return the size of the pool.
     */
    public int getMaxSize();
}

환경 설정에 따라 컨테이너는 재활용된 인스턴스를 포함하는 풀의 일정한 크기를 갖도록 선택하거나 요구에 따른 인스턴스의 구체화(instantiate)와 초기화(initialize)를 선택할 수 있습니다.

풀은 활성화(activation)를 위한 여유 인스턴스를 획득하는데 InstanceCache 구현에서 사용되며, Home 인터페이스 메쏘드(create와 finder 호출)를 위해 사용되는 인터페이스들을 획득하기 위해 인터셉터에서 사용됩니다.

5.3.2.4. org.jboss.ebj.InstanceCache

컨테이너의 InstanceCache 구현은 활성화된 상태에 있는 모든 EJB-인스턴스들을 처리합니다. 이것은 식별성을 갖는 빈 인스턴스가 첨부된다는 의미입니다. 엔티티와 상태 세션 빈들만이 메쏘드 호출사이에서 상태를 갖기 때문에 캐쉬됩니다. 엔티티 빈의 캐쉬 키는 빈의 프라이머리 키입니다. 상태 세션 빈의 캐쉬 키는 세션 id 입니다.

예제 5.13. org.jboss.ejb.InstanceCache 인터페이스

public interface InstanceCache 
    extends ContainerPlugin
{
    /**
     * 주어진 식별성(identity)에 따른 이 캐쉬로부터 빈 인스턴스를 획득함. 
     * 인스턴스가 캐쉬내에 없을 경우, 이 메쏘드는 활성화에 참여할 수 있습니다.
     * 구현은 O(1) 복잡성(complexity)을 갖아야만 합니다.
     * 이 메쏘드는 비상태 세션 빈즈에서는 결코 호출되지 않습니다.
     *
     * @param id the primary key of the bean
     * @return the EnterpriseContext related to the given id
     * @exception RemoteException in case of illegal calls
     * (concurrent / reentrant), NoSuchObjectException if
     * the bean cannot be found.
     * @see #release
     */
    public EnterpriseContext get(Object id)
	throws RemoteException, NoSuchObjectException;
    
    /**
     * 생성 또는 활성화후 활성화된 빈 인스턴스를 삽입합니다.
     * 구현은 반드시 적절한 락킹과 O(1) 복잡성으로 보장되어야만 합니다.
     *
     * @param ctx the EnterpriseContext to insert in the cache
     * @see #remove
     */
    public void insert(EnterpriseContext ctx);
    
    /**
     * 이 캐쉬로부터 주어진 빈 인스턴스를 릴리즈합니다.
     * 이 메쏘드는 빈을 캐쉬로부터 없애는데 수동적일 수 있습니다. 
     * 구현은 다른 쓰레드에서 실행되도록 하기위해
     * 수동태로 즉각 남아있도록 반환되어져야 합니다. 
     *
     * @param ctx the EnterpriseContext to release
     * @see #get
     */
    public void release(EnterpriseContext ctx);
    
    /**
     * 주어진 식별성을 갖는 이 캐쉬로부터 빈 인스턴스를 제거합니다.
     * 구현은 O(1) 복잡성을 갖고 적절한 락킹을 보장해야만 합니다.
     *
     * @param id the pimary key of the bean
     * @see #insert
     */
    public void remove(Object id);
    
    /**
     * 활성화된 특정한 id에 대응되는 인스턴스가 있는지를 점검
     *
     * @param id the pimary key of the bean
     * @see #insert
     */
    public boolean isActive(Object id);    
}

활성화된 인스턴스들의 목록을 관리하는것에 더해, InstanceCache는 인스턴스들의 활성화와 비활성화까지도 책임을 집니다. 주어진 식별성을 갖는 인스턴스가 요청되고 이것이 현재 활성화되어 있지 않은 경우라면, InstanceCache는 반드시 여유분의 인스턴스를 획득하기 위해 InstancePool을 사용해야 하며 그 다음에는 영속적인 관리자(persistence manager)가 이 인스턴스를 활성화시킵니다. 이와 비슷하게 InstanceCache가 활성화되어 있는 인스턴스를 비활성화시키는결정을 할 경우에는 이를 비활성화시키기 위한 영속적인 관리자를 호출해야만 하고 InstancePool쪽으로 인스턴스를 릴리즈 시킵니다.

5.3.2.5. org.jboss.ejb.EntityPersistenceManager

EntityPersistenceManager는 엔티티 빈의 영속성에 대한 책임을 집니다. 여기에는 다음과 같은 것들이 포함됩니다:

  • 저장소에 EJB 인스턴스를 생성
  • EJB 인스턴스쪽에 주어진 프라이머리 키를 갖는 상태를 로딩
  • 주어진 EJB 인스턴스의 상태를 저장
  • 저장소로부터 EJB 인스턴스의 제거
  • EJB 인스턴스의 상태 활성화
  • EJB 인스턴스의 상태 비활성화(Passivating)

예제 5.14.  org.jboss.ejb.EntityPersistenceManager 인터페이스

public interface EntityPersistenceManager 
    extends ContainerPlugin
{
    /**
     * 빈 클래스 또는 서브 클래스의 새로운 인스턴스를 반환.
     *
     * @return the new instance
     */
    Object createBeanClassInstance() throws Exception;
                    
    /**
     * 이 메쏘드는 엔티티가 만들어질때마다 호출됨.
     * 영속적 관리자는 인스턴스에 대한 ejbCreate 메쏘드를 호출할 책임이 있으며,
     * 영속적인 저장소에 그 결과를 올바르게 쓰는 처리를 담당.
     *
     * @param m the create method in the home interface that was
     * called
     * @param args any create parameters
     * @param instance the instance being used for this create call
     */
    void createEntity(Method m,
		      Object[] args,
		      EntityEnterpriseContext instance)
	throws Exception;
    
    /**
     * 이 메쏘드는 엔티티가 만들어질때마다 호출됨. 
     * 영속적 관리자는 인스턴스에 대한 ejbPostCreate 메쏘드를 호출해야할 책임이 있고, 
     * 그 결과를 올바르게 영속적인 저장소에 저장하는 처리를 담당.
     *
     * @param m the create method in the home interface that was
     * called
     * @param args any create parameters
     * @param instance the instance being used for this create call
     */
    void postCreateEntity(Method m,
			  Object[] args,
			  EntityEnterpriseContext instance)
	throws Exception;
    
    /**
     * 이 메쏘드는 단일 엔티티가 발견되었을 때 호출됨.
     * 영속적 관리자는 요구되는 인스턴스가 영속적인 저장소내에 사용가능한지를 반드시 찾아야만 하고, 
     * 찾은 경우에는 그 결과로 반환되어지는 인스턴스에 대한 EJBObject를 생성하는 
     * ContainerInvoker 플러그인을 사용할 수 있음.
     *
     * @param finderMethod the find method in the home interface that was
     * called
     * @param args any finder parameters
     * @param instance the instance to use for the finder call
     * @return an EJBObject representing the found entity
     */
    Object findEntity(Method finderMethod,
		      Object[] args,
		      EntityEnterpriseContext instance)
	throws Exception;
    
    /**
     * 이 메쏘드는 엔티티들의 컬렉션이 발견되었을 때 호출됨. 
     * 영속적 관리자는 영속적인 저장소내에서 요구되는 인스턴스들이 사용가능한지를 반드시 찾아야만 하고, 
     * 찾은 경우에는 그 결과로 반환되는 인스턴스를 위한 EJBObjects를 생성하는 
     * ContainerInvoker 플러그인을 사용할 수 있음.
     *
     * @param finderMethod the find method in the home interface that was
     * called
     * @param args any finder parameters
     * @param instance the instance to use for the finder call
     * @return an EJBObject collection representing the found
     * entities
     */
    Collection findEntities(Method finderMethod,
			    Object[] args,
			    EntityEnterpriseContext instance)
                     throws Exception;
    
    /**
     * 이 메쏘드는 엔티티가 활성화될 때 호출됨. 
     * 영속적 관리자는 인스턴스에 대해 ejbActivate 메쏘드를 호출해야만 함.
     *
     * @param instance the instance to use for the activation
     *
     * @throws RemoteException thrown if some system exception occurs
     */
    void activateEntity(EntityEnterpriseContext instance)
	throws RemoteException;
                    
    /**
     * 이 메쏘드는 저장소로부터 로드될 엔티티가 있을 때마다 호출됨.
     * 영속적 관리자는 저장소로부터 상태를 로드해야만 하고 
     * 공급되는 인스턴스에 대한 ejbLoad를 호출함.
     *
     * @param instance the instance to synchronize
     *
     * @throws RemoteException thrown if some system exception occurs
     */
    void loadEntity(EntityEnterpriseContext instance)
	throws RemoteException;
    
    /**
     * 이 메쏘드는 엔티티가 저장될지를 결정하는데 사용됨.
     *
     * @param instance the instance to check
     * @return true, if the entity has been modified
     * @throws Exception thrown if some system exception occurs
     */
    boolean isModified(EntityEnterpriseContext instance) throws Exception;
    
    /**
     * 이 메쏘드는 저장소에 엔티티를 저장할때마다 호출됩니다. 
     * 영속적 관리자는 반드시 제공되는 인스턴스에 대해 ejbStore를 호출하여 
     * 저장소내에 그 상태를 저장합니다.
     *
     * @param instance the instance to synchronize
     *
     * @throws RemoteException thrown if some system exception occurs
     */
    void storeEntity(EntityEnterpriseContext instance)
	throws RemoteException;
    
    /**
     * 이 메쏘드는 엔티티가 비활성화될 때 호출됨. 
     * 영속적 관리자는 반드시 인스턴스에 대한 ejbPassivate 메쏘드를 호출해야만 함.
     *
     * @param instance the instance to passivate
     *
     * @throws RemoteException thrown if some system exception occurs
     */
    void passivateEntity(EntityEnterpriseContext instance)
	throws RemoteException;
    
    /**
     * 이 메쏘드는 엔티티가 저장소에서 제거될 때 호출됨. 
     * 영속적 관리자는 반드시 인스턴스에 대한 ejbRemove를 호출해야만 하며, 
     * 그런 다음에는 주어진 저장소에서 자신의 상태를 제거함.
     *
     * @param instance the instance to remove
     *
     * @throws RemoteException thrown if some system exception occurs
     * @throws RemoveException thrown if the instance could not be removed
     */
    void removeEntity(EntityEnterpriseContext instance)
	throws RemoteException, RemoveException;
}

EJB 2.0 사양서에 따라, JBoss에서는 두 개의 영속적 시맨틱 엔티티 빈을 지원합니다: Container Managed Persistence (CMP) 와 Bean Managed Persistence (BMP). CMP 구현은 org.jboss.ejb.EntityPersistanceStore 인터페이스의 구현을 사용합니다. 기본적으로 이것은 CMP2 영속적 엔진을 위한 엔트리 포인트인 org.jboss.ejb.plugins.cmp.jdbc.JDBCStoreManager 입니다. EntityPersistanceStore 인터페이스를 아래에 제공하고 있습니다.

예제 5.15.  org.jboss.ejb.EntityPersistanceStore 인터페이스

public interface EntityPersistenceStore 
    extends ContainerPlugin
{
    /**
     * 빈 클래스나 빈 클래스의 서브클래스의 새로운 인스턴스 반환.
     *
     * @return the new instance
     *
     * @throws Exception
     */
    Object createBeanClassInstance() throws Exception;
    
    /**
     * 인스턴스 컨텍스트의 초기화.
     *
     * <p>이 메쏘드는 createEntity 전에 호출되고, 
     * 모든 cmpFields 값을 0 이나 null로 리셋시켜야 합니다.
     *
     * @param ctx
     *
     * @throws RemoteException
     */
    void initEntity(EntityEnterpriseContext ctx);
    
    /**
     * 이 메쏘드는 엔티티가 생성될때마다 호출됩니다. 
     * 영속적인 관리자는 영속적인 저장소에 결과를 올바르게 쓸 수 있도록 처리하는 책임을 갖습니다.
     *
     * @param m the create method in the home interface that was
     * called
     * @param args any create parameters
     * @param instance the instance being used for this create call
     * @return The primary key computed by CMP PM or null for BMP
     *
     * @throws Exception
     */
    Object createEntity(Method m,
			Object[] args,
			EntityEnterpriseContext instance)
	throws Exception;
    
    /**
     * 이 메쏘드는 단일 엔티티가 발견되어질 때 호출됩니다. 
     * 영속적인 관리자는 영속적인 저장소내에 사용가능한 요구되는 인스턴스가 있는지를 
     * 찾아내야만 하고 찾는 경우 객체의 프라이머리 키를 반환합니다.
     *
     * @param finderMethod the find method in the home interface that was
     * called
     * @param args any finder parameters
     * @param instance the instance to use for the finder call
     * @return a primary key representing the found entity
     *
     * @throws RemoteException thrown if some system exception occurs
     * @throws FinderException thrown if some heuristic problem occurs
     */
    Object findEntity(Method finderMethod,
		      Object[] args,
		      EntityEnterpriseContext instance)
	throws Exception;
    
    /**
     * 이 메쏘드는 엔티티들의 컬렉션이 발견되었을 때 호출됩니다. 
     * 영속적 관리자는 영속적 저장소내에서 요구되는 인스턴스들이 사용가능한지를 찾아야만 하고, 
     * 찾았을 때는 primaryKey의 컬렉션을 반환해야 합니다.
     *
     * @param finderMethod the find method in the home interface that was
     * called
     * @param args any finder parameters
     * @param instance the instance to use for the finder call
     * @return an primary key collection representing the found
     * entities
     *
     * @throws RemoteException thrown if some system exception occurs
     * @throws FinderException thrown if some heuristic problem occurs
     */
    Collection findEntities(Method finderMethod,
			    Object[] args,
			    EntityEnterpriseContext instance)
	throws Exception;
    
    /**
     * 이 메쏘드는 엔티티가 활성화되었을 때 호출됩니다.
     *
     * <p>PersistenceManager factorization에서 대부분의 EJB 호출들은 
     * 존재하지 않으나 이 호출은 우리에게 영속적인 저장소내에서 
     * 최적화를 도입할 수 있도록 허용합니다. 
     * 특히, PersistenceStore가 사용할 수 있는(JAWS는 smart updates에 사용)
     * 하나의 "PersistenceContext"를 갖는 컨텍스트와 이것은 이것을 구성하고 있는 
     * 것들은 그 자체로 콜백의 좋은 예가 됩니다. 
     * @param instance the instance to use for the activation
     *
     * @throws RemoteException thrown if some system exception occurs
     */
    void activateEntity(EntityEnterpriseContext instance)
	throws RemoteException;
    
    /**
     * 이 메쏘드는 엔티티가 저장소로부터 로드될 때마다 호출됩니다.
     * 영속적 관리자는 저장소로부터 상태를 로드해야만 하고, 
     * 그런 다음 제공되는 인스턴스에 대해 ejbLoad를 호출합니다. 
     *
     * @param instance the instance to synchronize
     *
     * @throws RemoteException thrown if some system exception occurs
     */
    void loadEntity(EntityEnterpriseContext instance)
	throws RemoteException;
    
    /**
     * 이 메쏘드는 엔티티가 저장될지를 결정하는데 사용합니다. 
     *
     * @param instance the instance to check
     * @return true, if the entity has been modified
     * @throws Exception thrown if some system exception occurs
     */
    boolean isModified(EntityEnterpriseContext instance) throws Exception;
    
    /**
     * 이 메쏘드는 엔티티가 저장소에 저장될때마다 호출됩니다. 
     * 영속적 관리자는 반드시 제공되는 인스턴스에 대해 ejbStore를 호출해야만 하고 
     * 그런 다음 저장소에 상태를 저장합니다.
     *
     * @param instance the instance to synchronize
     *
     * @throws RemoteException thrown if some system exception occurs
     */
    void storeEntity(EntityEnterpriseContext instance)
	throws RemoteException;
    
    /**
     * 이 메쏘드는 엔티티가 비활성화될 때 호출됩니다. 
     * 영속적 관리자는 인스턴스에 대해 ejbPassivate 메쏘드를 호출해야만 합니다.
     *
     * <p>저장소를 호출하기위한 EJB 콜백을 노출시키는 이유에 대한
     * 활성화 논의를 참고하십시오.
     *
     * @param instance the instance to passivate
     *
     * @throws RemoteException thrown if some system exception occurs
     */
    void passivateEntity(EntityEnterpriseContext instance)
	throws RemoteException;
    
    /**
     * 이 메쏘드는 엔티티가 저장소로부터 제거될 때 호출됩니다.
     * 영속적 관리자는 인스턴스에 대해 반드시 ejbRemove를 호출해야 하며, 
     * 저장소로부터 자신의 상태를 제거합니다. 
     *
     * @param instance the instance to remove
     *
     * @throws RemoteException thrown if some system exception occurs
     * @throws RemoveException thrown if the instance could not be removed
     */
    void removeEntity(EntityEnterpriseContext instance)
	throws RemoteException, RemoveException;
}

EntityPersistenceManager 인터페이스의 기본 BMP 구현은 org.jboss.ejb.plugins.BMPPersistenceManager 입니다. BMP 영속적 관리자는 모든 영속적인 로직이 엔티티 빈 자신이기 때문에 굉장히 간단합니다. 영속적 관리자가 갖는 유일한 임무는 컨테이너 콜백을 수행하는 것입니다.

5.3.2.6. org.jboss.ejb.StatefulSessionPersistenceManager

StatefulSessionPersistenceManager는 상태 SessionBeans의 영속성에 대한 책임을 갖습니다. 여기에는 다음과 같은 사항들이 포함됩니다:

  • 저장소에 상태 세션을 생성
  • 저장소로부터 상태 세션들을 활성화
  • 저장소로부터 상태 세션들을 비활성화
  • 저장소로부터 상태 세션들을 제거

StatefulSessionPersistenceManager 인터페이스는 아래에 제시되어 있습니다.

예제 5.16. org.jboss.ejb.StatefulSessionPersistenceManager 인터페이스

public interface StatefulSessionPersistenceManager 
    extends ContainerPlugin
{
    public void createSession(Method m, Object[] args,
			      StatefulSessionEnterpriseContext ctx)
	throws Exception;
    
    public void activateSession(StatefulSessionEnterpriseContext ctx)
	throws RemoteException;
    
    public void passivateSession(StatefulSessionEnterpriseContext ctx)
	throws RemoteException;
    
    public void removeSession(StatefulSessionEnterpriseContext ctx)
	throws RemoteException, RemoveException;
    
    public void removePassivated(Object key);
}

StatefulSessionPersistenceManager 인터페이스의 기본 구현은 org.jboss.ejb.plugins.StatefulSessionFilePersistenceManager 입니다. 이것의 이름이 보여주듯이 StatefulSessionFilePersistenceManager는 파일 시스템을 이용하여 영속적인 상태 세션 빈을 가능케 합니다. 보다 구체적으로 이야기하자면, 영속적인 관리자는 빈의 이름과 세션 id에 .ser 확장자로 구성된 이름을 갖는 일반적인 파일내에 빈즈를 직렬화시킵니다. 영속적 관리자는 활성화되는 동안 빈즈의 상태를 다시 저장하고 빈의 .ser 파일로부터 비활성화시키는 동안 자신의 상태를 각각 저장합니다.

5.4. 엔티티 빈의 락킹과 데드락 검출

이번 섹션에서는 엔티티 빈 락킹이 무엇인가와 엔티티 빈즈들이 어떻게 액세스되고 JBoss내에서 락이 걸리는지를 살펴보도록 하겠습니다. 또한 여러분이 시스템내에 엔티티 빈즈를 사용하면서 겪을 수 있는 문제들에 대해 살펴보면서 어떻게 이 문제를 해결할지도 논의해보도록 하겠습니다. 데드락킹이란 정식적으로 정의되고 검증되었습니다. 그리고 최종적으로 우리는 엔티티 빈 락킹 용어를 사용하여 여러분의 시스템에서 어떻게 문제를 해결하는지도 살펴보겠습니다.

5.4.1. 왜 JBoss는 락킹을 필요로 하는가?

락킹(Locking)은 여러분의 데이터 정합성을 보장하기 위한 것입니다. 때때로 여러분은 한번에 오직 한 사람만 중요한 데이터의 갱신이 가능하도록 하는 작업이 보장될 수 있도록 할 필요가 생깁니다. 때로는 시스템내의 예민한 객체들에 액세스할 때 직렬화가 되도록하여 데이터가 동시에 읽고 쓰는 것에 의해 잘못되지 않도록 할 필요도 생깁니다. 데이터베이스는 전통적으로 이러한 기능을 트랜잭션과 관련된 범주에서 테이블과 로우의 락킹 기능을 이용하여 제공하고 있습니다.

엔티티 빈즈는 관계형 데이터에 객체-지향적인 인터페이스를 제공하는 매우 좋은 방법입니다. 더 나아가서 엔티티 빈즈를 통해 캐슁과 꼭 필요할 때까지 갱신의 지연을 하여 데이터베이스에 걸리는 부하를 줄여줌으로써 데이터베이스를 효과적으로 최대 효율을 낼 수 있도록 하여 성능의 향상또한 얻을 수 있습니다. 하지만 캐슁을 하게 되면 데이터의 정합성이 문제가 되며 어플리케이션 서버 레벨의 락킹으로 구현되는 일부에서는 관계형 데이터베이스를 사용하는 엔티티 빈들이 필요하게 됩니다.

5.4.2. 엔티티 빈의 생명주기(Lifecycle)

JBoss의 기본 설정환경에서는 한번에 메모리내에는 하나의 엔티티 빈의 활성화된 인스턴스만이 존재하게 됩니다. 이것은 모든 캐쉬 설정과 commit-option의 각 타입들에 대해 적용됩니다. 이 인스턴스의 생명주기는 모든 각각의 commit-option에 대해 다릅니다.

  • commit-option A, 이 인스턴스는 캐쉬되고 트랜잭션들사이에서 사용됩니다.
  • commit-option B, 이 인스턴스는 캐쉬되고 트랜잭션들 사이에서 사용되나 트랜잭션의 끝에서 dirty로 표시됩니다. 이것은 새로운 트랜잭션이 시작될 때 ejbLoad가 반드시 호출되어야 됩니다.
  • commit-option C, 이 인스턴스는 dirty로 표시되어 캐쉬로부터 릴리즈되고 트랜잭션 끝에서 비활성화로 표시됩니다.
  • commit-option D, 캐쉬내에서 안정된 빈즈에서 주기적으로 ejbLoad를 호출하는 쓰레드를 백그라운드에서 갱신합니다. 다른 상황에서는 A와 동일한 방식으로 동작합니다.

빈이 비활성으로 표시된다면 비활성 큐(passivation queue)에 담기게 됩니다. 각각의 엔티티 빈 컨테이너들은 정기적으로 빈즈를 비활성시켜 비활성 큐내에 위치시키는 비활성 쓰레드를 갖습니다. 동일한 프라이머리 키를 갖는 빈을 어플리케이션 요청들이 액세스할때는 비활성 큐에서 이 빈즈가 꺼내어져 재사용됩니다.

예외가 발생하거나 트랜잭션이 취소(rollback)될 때는 엔티티 빈 인스턴스가 전체 캐쉬로부터 없어집니다. 비활성 큐에 넣어지거나 인스턴스 풀에 의해 거절되는 것이 아닙니다. 비활성 큐를 제외하고는 엔티티 빈 인스턴스 풀링은 없습니다.

5.4.3. 기본 락킹 동작

엔티티 빈 락킹은 엔티티 빈 인스턴스로부터 완전히 분리되어 집니다. 락킹에 대한 로직은 별도의 락 객체내에서 완전히 분리되고 관리되어집니다. 한번에 활성화된 엔티티 빈의 허용된 인스턴스는 오직 하나이기때문에 JBoss는 데이터의 정합성을 보장하고 EJB 스펙에 부합하기 위해 두개의 락 타입을 채용하였습니다.

  • Method Lock: 한번에 오직 하나의 쓰레드만이 실행되도록 하기위한 Method Lock은 주어진 엔티티 빈에서 호출될 수 있습니다. 이것은 EJB 스펙에서 필요합니다.
  • Transaction Lock: 트랜잭션락은 한번에 오직 하나의 트랜잭션만이 주어진 엔티티 빈에 액세스할 수 있도록 보장합니다. 이러한 보장을 통해 어플리케이션 서버 레벨에서 트랜잭션의 ACID 속성을 보장합니다. 주어진 엔티티 빈의 활성화된 인스턴스는 한번에 오직 하나만 존재하는 것이 기본이기 때문에 JBoss는 dirty reads와 dirty writes로부터 이 인스턴스를 반드시 보호해야 합니다. 따라서 기본 엔티니 빈 락킹 동작은 하나의 트랜잭션이 완전히 종료될때까지 트랜잭션내의 엔티티 빈에 락을 걸게 됩니다. 이것이 의미하는 것은 어떠한 메쏘드가 결국 하나의 트랜잭션내에서 하나의 엔티티 빈을 호출한다면, 다른 트랜잭션들은 이 트랜잭션이 성공(commit)하거나 취소(rollback)되기전까지는 해당 빈에 액세스를 할 수 없다는 것입니다.

5.4.4. 플러그인이 가능한 인터셉터와 락킹 정책

우리는 기본 엔티티 빈의 생명주기와 동작(behavior)가 standardjboss.xml 서술자에서의 컨테이너 환경설정 정의부분에서 정의되는 것을 본적이 있습니다. 다음에 보여지는 컨테이너의 환경설정에서는 Standard CMP 2.x EntityBean 환경설정에 대한 container-interceptors 정의가 나옵니다.

예제 5.17. Standard CMP 2.x EntityBean 인터셉터 정의

<container-configuration>
    <container-name>Standard CMP 2.x EntityBean</container-name>
    <!-- ... -->
    <container-interceptors>
        <interceptor>org.jboss.ejb.plugins.LogInterceptor</interceptor>
        <interceptor>org.jboss.ejb.plugins.SecurityInterceptor</interceptor>
        <interceptor>org.jboss.ejb.plugins.TxInterceptorCMT</interceptor>
        <interceptor>org.jboss.ejb.plugins.MetricsInterceptor</interceptor>
        <interceptor>org.jboss.ejb.plugins.EntityCreationInterceptor</interceptor>
        <interceptor>org.jboss.ejb.plugins.EntityLockInterceptor</interceptor>
        <interceptor>org.jboss.ejb.plugins.EntityInstanceInterceptor</interceptor>
        <interceptor>org.jboss.resource.connectionmanager.CachedConnectionInterceptor</interceptor>
        <interceptor>org.jboss.ejb.plugins.EntitySynchronizationInterceptor</interceptor>
        <interceptor>org.jboss.ejb.plugins.cmp.jdbc.JDBCRelationInterceptor</interceptor>
    </container-interceptors>
</container-configuration> 

위에서 보여지는 인터셉터의 엔티티 빈 대부분의 동작을 정의하고 있습니다. 다음에 오는 인터셉터의 설명은 이번 절과 관련된 것들입니다.

  • EntityLockInterceptor: 이 인터셉터의 역할은 호출의 처리가 허용되기전에 획득되어져야만 하는 락의 스케쥴링입니다. 이 인터셉터는 매우 경량(lightweight)이며 모든 락킹 동작을 플러그인이 가능한 락킹 정책에 위임합니다.
  • EntityInstanceInterceptor: 이 인터셉터의 업무는 캐쉬내에 엔티티 빈을 찾거나 새로운 것을 생성시키는 것입니다. 또한 이 인터셉터는 메모리내에서 한번에 오직 하나의 활성화된 인스턴스만 존재하도록 보장합니다.
  • EntitySynchronizationInterceptor: 이 인터셉터의 역할은 저장소내의 캐쉬 상태를 동기화시키는 것입니다. 이것은 EJB 시방서의 ejbLoadejbStore 시만틱을 갖고 행해지게 됩니다. 트랜잭션이 존재하는 경우 이 인터셉터는 트랜잭션 경계에 의해 트리거되어집니다. 이것은 JTA 인터페이스를 통해 트랜잭션 모니터와함께 콜백을 등록합니다. 만약 트랜잭션이 없다면, 정책은 호출로부터 얻어지는 상태를 저장합니다. 동기화는 여기서 고려되는 시방서를 따르는 A, B 그리고 C에서 뿐만 아니라 JBoss에서만 지원하는 commit-option D 에서도 준수되고 있습니다.

5.4.5. 데드락(Deadlock)

데드락 문제를 찾아내고 해결하는 것이 이번 섹션의 주제입니다. 우리는 데드락킹 MBean이 무엇인지를 설명하고 이를 여러분의 어플리케이션에서 어떻게 검출하며 어떻게 데드락을 해결하는지에 대해 알아보도록 하겠습니다. 데드락은 두개 이상의 쓰레드가 공유된 리소스들에 대해 락을 걸때 발생할 수 있습니다. 그림 5.11, “데드락킹 정의 예제”에서는 간단한 데드락 시나리오를 보여주고 있습니다. 여기서 Thread 1Bean A에 대한 락을 갖으며, Thread 2Bean B에 대한 락을 갖고 있습니다. 나중에 Thread 1에서 Bean B에 락을 걸려고 하면 Thread 2가 이를 소유하고 있기 때문에 블럭킹됩니다. 이와 비슷하게 Thread 2Bean A에 대해 락을 걸려고 하면 Thread 1에서 락을 갖고 있기 때문에 역시 블럭킹됩니다. 이러한 과점에서 보면 두개의 쓰레드 모두 다른 쓰레드에 의해 이미 락이 걸려있는 리소스에 액세스하기 위해 데드락킹된 대기상태가 됩니다.

데드락킹 정의 예제

그림 5.11. 데드락킹 정의 예제

JBoss의 기본 락킹 정책은 트랜잭션이 종료되기전까지 하나의 트랜잭션 컨텍스트내에서 호출이 발생하는 경우 엔티티 빈에 락을 거는 것입니다. 이러한 이유로, 많은 엔티티 빈즈를 액세스하는 장기적으로 동작되는 트랜잭션을 갖고 있을 경우나 이들 엔티티 빈에 액세스하는 순서에 주의를 기울이지 않을 경우 데드락에 쉽게 걸려들게 됩니다. 다양한 테크닉들과 향상된 환경설정을 통해 이러한 데드락킹 문제를 빗겨나갈 수 있습니다. 이러한 방법들은 이번 섹션의 후반부에서 논의하도록 하겠습니다.

5.4.5.1. 데드락 검출

다행스럽게도 JBoss는 데드락 검출이 가능합니다. JBoss는 대기중인 트랜잭션들의 전역적인 내부 그래프를 유지하면서 트랜잭션에서 어떤것들을 블락킹하고 있는지 알고 있습니다. 쓰레드가 엔티티 빈 락을 획득하지 못한다는 결정을 하게되면, JBoss는 어떤 트랜잭션이 현재 해당 빈에 대한 락을 갖고 있는지 찾아내고 여기에 블락킹된 트랜잭션 그래프를 추가합니다. 그래프의 어떤 모습인지에 대한 예가 표 5.1, “블럭킹된 트랜잭션 테이블 예제”에 제공됩니다.

표 5.1. 블럭킹된 트랜잭션 테이블 예제

Blocking TX필요한 락을 갖는 Tx
Tx1Tx2
Tx3Tx4
Tx4Tx1

쓰레드가 실제로 블락되기전에 데드락 문제가 어디에 있는지 검출하는 시도가 일어납니다. 이러한 시도는 블락 트랜잭션 그래프를 검토함으로써 이루어집니다. 그래프를 검토하면서 어떤 트랜잭션이 블럭킹되었는지를 추적합니다. 만약 그래프내에서 한번 이상의 블럭된 노드를 보게되면, 데드락이 존재하는 것을 알고 ApplicationDeadlockException을 발생시킵니다. 데드락 검출을 위한 알고리즘은 BeanLockSupport deadlockDetection 메쏘드에서 찾을 수 있습니다. 다음에 보여주는 코드에서 이 메소드를 볼 수 있습니다.

예제 5.18. org.jboss.ejb.plugins.lock.BeanLockSupport deadlockDetection 메쏘드

// 다음은 데드락 검출을 위한 코드입니다.
protected static HashMap waiting = new HashMap();

public void deadlockDetection(Transaction miTx) throws Exception
{
    HashSet set = new HashSet();
    set.add(miTx);
    
    Object checkTx = this.tx;
    synchronized(waiting) {
	while (checkTx != null) {
	    Object waitingFor = waiting.get(checkTx);
	    if (waitingFor != null) {
		if (set.contains(waitingFor)) {
		    log.error("Application deadlock detected: " + miTx +
			      " has deadlock conditions");
		    throw new ApplicationDeadlockException("application deadlock detected");
		}
		set.add(waitingFor);
	    }
	    checkTx = waitingFor;
	}
    }
}   

5.4.5.2. ApplicationDeadlockException 캐쉬하기

JBoss가 어플리케이션 데드락을 검출할 수 있기 때문에 여러분은 ApplicationDeadlockException으로 인해 호출이 실패할 경우 트랜잭션을 재시도할 수 있도록 어플리케이션을 만들어주어야만 합니다. 불행하게도 이 예외 처리는 RemoteException내에 깊숙히 내장될 수 있기 때문에 여러분은 블럭을 찾아내는 검색을 해주어야만 합니다. 다음은 그 예를 보여주고 있습니다 :

try {
    // ...
} catch (RemoteException ex) {
    Throwable cause = null;
    RemoteException rex = ex;
    while (rex.detail != null) {
	cause = rex.detail;
	if (cause instanceof ApplicationDeadlockException) {
	    // ... 데드락이 존재하여 트랜잭션의 재시도를 강제적으로 수행
	    break;
	}
	if (cause instanceof RemoteException) {
	    rex = (RemoteException)cause;
	}
    }
}

5.4.5.3. 락 정보 보기

EntityLockMonitor MBean 서비스는 트랜잭션 락킹 테이블의 상태를 출력할 뿐만 아니라 기본 락킹 통계를 볼 수 있게 합니다. 이런 모니터링을 가능하게 하려면 conf/jboss-service.xml내에서 이것에 대한 설정부분의 주석을 제거하십시오:

<mbean code="org.jboss.monitor.EntityLockMonitor"
       name="jboss.monitor:name=EntityLockMonitor"/>

EntityLockMonitor는 환경설정가능한 속성들을 갖고 있지 않습니다. 이것은 다음과 같은 읽기-전용의 속성들만을 갖습니다:

  • MedianWaitTime: 락을 획득하는데까지 대기했던 쓰레드의 모든 시간에 대한 중앙값(median value).
  • AverageContenders: 경쟁의 총 수에 대한 락을 대기했던 모든 쓰레드들의 합에 대한 비율.
  • TotalContentions: 트랜잭션 락을 획득하기위해 대기했던 쓰레드들의 총 수. 이것은 쓰레드가 다른 트랜잭션에 관련된 락을 획득하도록 시도될 때 발생합니다.
  • MaxContenders: 트랜잭션 락을 얻기위해 대기했었던 쓰레드들의 최대 갯수.

이것은 또한 다음과 같은 오퍼레이션들도 갖습니다:

  • clearMonitor: 이 오퍼레이션은 모든 카운터들을 0으로 한 락 모니터 상태로 리셋시킵니다.
  • printLockMonitor: 이 오퍼레이션은 빈의 ejbName과 락을 대기하는 데 소요한 총 시간, 대기중이었던 락의 횟수에 대한 카운트와 락에 대한 대기시간 타임아웃이 생긴 트랜잭션의 갯수를 나열하는 모든 EJB 락에 대한 테이블 출력합니다.

5.4.6. 고급 환경설정과 최적화

엔티티 빈즈의 기본 락킹 동작은 데드락을 유발할 수 있습니다. 엔티티 빈으로 액세스함으로써 트랜잭션쪽에 빈을 락킹하기 때문에 이것은 여러분의 어플리케이션에 대한 퍼포먼스/처리량에 막대한 문제를 만들어낼 수도 있습니다. 이번 섹션에서는 최적화된 퍼포먼스와 데드락 가능성을 줄여주기 위해 사용할 수 있는 다양한 테크닉들과 환경설정들을 살펴보겠습니다.

5.4.6.1. 단명하는 트랜잭션(Short-lived Transactions)

여러분의 트랜잭션을 가능한 짧게 하고 세부적으로 만듭니다. 트랜잭션이 짧아질수록 동시에 액세스함으로써 발생될 수 있는 충돌의 여지를 줄여주고 어플리케이션의 처리량이 증가합니다.

5.4.6.2. 순차적인 액세스

여러분의 엔티티 빈즈를 순차적으로 액세스하게 함으로써 데드락에 대한 가능성을 줄여줄 수 있습니다. 즉, 여러분 시스템내의 엔티티 빈즈가 항상 동일한 순서대로 액세스될 수 있도록 하라는 의미입니다. 대부분의 경우에 있어서 사용자의 어플리케이션은 이러한 접근 방식을 사용하기에는 너무나 복잡하여 보다 고급 환경 설정이 필요합니다.

5.4.6.3. 읽기-전용 빈즈

엔티티 빈즈는 읽기-전용으로 표시될 수 있습니다. 읽기-전용으로 표시된 빈에 대해서는 트랜잭션에서 이를 취할 수 없습니다. 즉 읽기-전용 엔티티 빈즈는 결코 트랜잭셔널한 락이 걸리지 않습니다. 이 옵션과 함께 commit-option D를 사용하면 여러분의 읽기-전용 빈즈의 데이터가 종종 외부 소스에 의해 갱신될 때 매우 유용합니다.

빈을 읽기-전용으로 표시하기 위해서는 jboss.xml 배치 서술자내에서 read-only 플래그를 사용합니다.

예제 5.19. jboss.xml을 사용하여 엔티티 빈을 읽기-전용으로 표시하기

<jboss>
    <enterprise-beans>
        <entity>
            <ejb-name>MyEntityBean</ejb-name>
            <jndi-name>MyEntityHomeRemote</jndi-name>
            <read-only>True</read-only>
        </entity>
    </enterprise-beans>
</jboss>

5.4.6.4. 명시적으로 Read-Only 메쏘드를 정의하기

엔티티 빈즈의 기본 락킹 동작에 대해 읽고 이해한 후라면, 여러분은 아마도 "데이터를 수정하지 않더라도 빈을 왜 락킹해야하는가?"라는 의문을 갖게 될 것입니다. JBoss에서는 여러분이 오직 이러한 타입의 메쏘드들만이 호출되는 경우 트랜잭션내에 빈을 락킹하지 않는 읽기-가능한 여러분의 엔티티 빈에 대한 메쏘드가 무엇인지를 정의할 수 있도록 합니다. 여러분은 jboss.xml 배치 서술자내에서 이러한 읽기-가능 메쏘드들을 정의할 수 있습니다. 와일드카드가 메쏘드의 이름에 사용이 가능합니다. 다음에 오는 예제에서는 읽기-가능으로 모든 get으로 시작하는 메쏘드들과 anotherReadOnlyMethod를 정의하고 있습니다.

예제 5.20. 읽기-가능한 엔티티 빈 메쏘드 정의하기

<jboss>
    <enterprise-beans>
        <entity>
            <ejb-name>nextgen.EnterpriseEntity</ejb-name>
            <jndi-name>nextgen.EnterpriseEntity</jndi-name>
            <method-attributes>
                <method>
                    <method-name>get*</method-name>
                    <read-only>true</read-only>
                </method>
                <method>
                    <method-name>anotherReadOnlyMethod</method-name>
                    <read-only>true</read-only>
                </method>
            </method-attributes>
        </entity>
    </enterprise-beans>
</jboss> 

5.4.6.5. 트랜잭션 정책당 인스턴스

트랜잭션 정책당 인스턴스란 데드락과 JBoss의 기본 락킹 정책으로 인한 처리량 문제를 완전히 없앨 수 있는 고급 환경설정입니다. 기본 엔티티 빈 락킹 정책은 하나의 빈에 대한 활성화된 인스턴스를 하나만 허용하도록 하는 것입니다. 트랜잭션 정책당 인스턴스는 트랜잭션당 하나의 빈에 대한 새로운 인스턴스를 할당시킴으로써 이러한 요구조건을 깨고 트랜잭션이 종료되는 시점에서 이 인스턴스를 드롭합니다. 각각의 트랜잭션이 빈에 대한 자신만의 사본을 갖기 때문에 락킹에 기반을 둔 트랜잭션을 필요로 하지 않습니다.

이 옵션은 일견 매우 그럴듯하지만 곧바로 몇가지 결점들을 갖고 있다는 것을 알 수 있습니다. 먼저, 이 옵션의 트랜잭셔널 격리(isolation) 동작이 READ_COMMITTED와 동일합니다. 이것은 이들이 원치않을 때에도 반복적으로 생성될 수 있습니다. 다른 말로 표현하자면, 트랜잭션이 실효가 없는(stale) 빈의 사본을 갖을 수 있다는 것입니다. 두번째로는 현재 이 환경설정 옵션이 트랜잭션의 시작될때 반드시 ejbLoad가 발생하기 때문에 퍼포먼스에 문제가 생길 수 있는 commit-option B 또는 C를 필요로 한다는 것입니다. 하지만, 만약 현재 여러분의 어플리케이션이 어쨌든간에 commit-option BC를 필요로 한다고 했을때는 이러한 방식으로 된다는 것입니다. JBoss 개발자들은 현재 commit-option A(이 옵션을 위한 캐쉬의 사용이 가능하도록 하는)도 가능할 수 있는 방법을 모색하고 있습니다.

JBoss는 Instance Per Transaction CMP 2.x EntityBean과 이런 이 락킹 정책을 수행하는 standardjboss.xml내에 정의된 Instance Per Transaction BMP EntityBean이라는 이름을 갖는 컨테이너 환경설정을 갖고 있습니다. 이 설정을 사용하기 위해서는 아래에 보여지는 것처럼 jboss.xml 배치 서술자내에 여러분의 빈과 함께 사용하는 컨테이너 설정의 이름을 참조할 수 있도록만 해주면 됩니다.

예제 5.21. 트랜잭션 정책당 인스턴스를 사용하는 에제.

<jboss>
    <enterprise-beans>
        <entity>
            <ejb-name>MyCMP2Bean</ejb-name>
            <jndi-name>MyCMP2</jndi-name>
            <configuration-name>
                Instance Per Transaction CMP 2.x EntityBean
            </configuration-name>
        </entity>
        <entity>
            <ejb-name>MyBMPBean</ejb-name>
            <jndi-name>MyBMP</jndi-name>
            <configuration-name>
                Instance Per Transaction BMP EntityBean
            </configuration-name>
        </entity>
    </enterprise-beans>
</jboss>

5.4.7. 클러스터내에서 구동시키기

현재까지로는 클러스터내에서 엔티티 빈즈를 위한 분산된 락킹 기능이 존재하지 않습니다. 이런 기능은 데이터베이스로 위임되어졌으며 어플리케이션 개발자에 의해서 지원되여져야 합니다. 클러스터링된 엔티티 빈즈에서는 로우 락킹 메커니즘과 함께 조합한 commit-option B 또는 C를 사용하도록 제안하고 있습니다. CMP에 대해서는 로우-락킹 설정 옵션이 존재합니다. 이 옵션은 데이터베이스에서 빈을 로드할 때 SQL select for update 를 사용합니다. commit-option BC와 함께 이것은 클러스터에 걸쳐 사용될 수 있는 트랜잭셔널 락을 구현합니다. BMP에서는 BMP의 ejbLoad 메쏘드내에서 select for update 호출을 명시적으로 구현해야 합니다.

5.4.8. 문제해결

이번 섹션에서는 대표적인 락킹 문제들과 이들의 해법을 논의해보도록 하겠습니다.

5.4.8.1. 락킹 동작이 일어나지 않는 문제

JBoss 사용자 전자메일 리스트에서 락킹이 일어나지 않고 동시에 빈즈에 액세스하는 경우가 나타나 결국 dirty read가 발생한다는 문제점에 관한 사항을 많이 찾아볼 수 있습니다. 이러한 현상이 발생하는 일반적인 원인은 다음과 같습니다:

  • 여러분이 커스텀 container-configurations을 사용할 경우, 이 설정들이 갱신되었는지 확인하십시오.
  • custom/complex 프라이머리 키 클래스로부터 올바르게 equalshashCode가 구현되었는지 꼭 확인하십시오.
  • 여러분의 custom/complex 프라이머리 키 클래스가 올바르게 직렬화(serialize)되었는지 꼭 확인하십시오. 일반적으로 저지르는 실수중에 하나는 멤버 변수의 초기화가 프라이머리 키가 언마샬되어졌을 때 실행되어진다는 가정입니다.

5.4.8.2. IllegalStateException

"removing bean lock and it has tx set!" 이라는 메시지를 갖는 IllegalStateException이 일반적으로 의미하는 것은 여러분이 자신의 custom/complex 프라이머리 키 클래스를 위해 올바르게 equals 과/또는 hashCode를 구현하지 않았기 때문이거나 여러분의 프라이머리 키 클래스가 직렬화를 위해 올바르게 구현되지 않았기 때문입니다.

5.4.8.3. 지연(Hangs)과 트랜잭션 타임아웃

JBoss에서 오래전부터 해결되지 않는 버그는 트랜잭션이 실제로는 롤백되지는 않았지만 롤백으로 표시되기만 한 경우 트랜잭션 타임아웃이 발생되는 것입니다. . 이것에 대한 책임은 호출 쓰레드로 위임됩니다. 이러한 현상은 엔티티 빈 락이 절대로 릴리즈되지 않을 때처럼 무한정 호출 쓰레드가 지연될 경우 커다란 문제가 됩니다. 이러한 문제를 해결하기 위한 좋은 방법은 없습니다. 여러분은 오직 무한정 지연되는 트랜잭션내의 원인을 제거하는 것만이 필요할 따름입니다. 일반적으로 저지를 수 있는 실수중에 하나는 인터넷을 통해 연결을 하거나 트랜잭션내에 web-crawler(검색 로봇)을 동작시키는 것입니다.