Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

[Spring 레퍼런스] 23장 JMX #2

이 문서는 개인적인 목적이나 배포하기 위해서 복사할 수 있다. 출력물이든 디지털 문서든 각 복사본에 어떤 비용도 청구할 수 없고 모든 복사본에는 이 카피라이트 문구가 있어야 한다.



23.4 빈의 ObjectName 제어

내부적으로 MBeanExporter는 ObjectNamingStrategy 구현체에 등록하는 각 빈의 ObjectName을 가져오는 전략을 위임한다. 기본 구현체 KeyNamingStrategy는 beans Map의 키를 ObjectName로 사용한다. 그리고 KeyNamingStrategy는 beans Map의 키를 Properties의 항목에 매핑해서 ObjectName을 처리할 수 있다. KeyNamingStrategy외에도 스프링은 두 가지 ObjectNamingStrategy 구현체를 더 제공하고 있다. IdentityNamingStrategy는 빈의 JVM 아이디(identity)에 기반해서 ObjectName을 만들고 MetadataNamingStrategy은 소스수준의 메타데이터를 사용해서 ObjectName를 얻는다.

23.4.1 Properties에서 ObjectName 읽어오기

자신만의 KeyNamingStrategy 인스턴스를 설정해서 빈의 키를 사용하는 대신 Properties 인스턴스에서 ObjectName을 읽어오도록 구성할 수 있다. KeyNamingStrategy는 빈 키에 대응되는 키를 가진 Properties의 요소를 찾으려고 할 것이다. 일치하는 항목이 없거나 Properties 인스턴스가 null이라면 빈 키 자체를 사용한다.

아래 코드는 KeyNamingStrategy의 설정 예시를 보여준다.

<beans>
  <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
      <map>
        <entry key="testBean" value-ref="testBean"/>
      </map>
    </property>
    <property name="namingStrategy" ref="namingStrategy"/>
  </bean>

  <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
    <property name="name" value="TEST"/>
    <property name="age" value="100"/>
  </bean>

  <bean id="namingStrategy" class="org.springframework.jmx.export.naming.KeyNamingStrategy">
    <property name="mappings">
      <props>
        <prop key="testBean">bean:name=testBean1</prop>
      </props>
    </property>
    <property name="mappingLocations">
      <value>names1.properties,names2.properties</value>
    </property>
  </bean>
</beans>

여기서 KeyNamingStrategy 인스턴스는 매핑 프로퍼티와 매핑 프로퍼티에 정의된 경로에 있는 프로퍼티 파일로 정의된 Properties 인스턴스와 합쳐진 Properties 인스턴스로 구성된다. 이 구성에서 testBean 빈은 ObjectName bean:name=testBean1을 받을 것이다. 이는 이 값이 Properties 인스턴스에서 빈 키에 대응되는 키를 가진 항목이기 때문이다.

Properties 인스턴스에 항목이 없다면 빈 키의 이름을 ObjectName으로 사용한다.

23.4.2 MetadataNamingStrategy의 사용

MetadataNamingStrategy는 ObjectName을 사용하기 위해 각 빈의 ManagedResource속성의 objectName 프로퍼티를 사용한다. 아래 코드는 MetadataNamingStrategy의 설정 예시를 보여준다.

<beans>
  <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
      <map>
        <entry key="testBean" value-ref="testBean"/>
      </map>
    </property>
    <property name="namingStrategy" ref="namingStrategy"/>
  </bean>

  <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
    <property name="name" value="TEST"/>
    <property name="age" value="100"/>
  </bean>

  <bean id="namingStrategy" class="org.springframework.jmx.export.naming.MetadataNamingStrategy">
    <property name="attributeSource" ref="attributeSource"/>
  </bean>

  <bean id="attributeSource" class="org.springframework.jmx.export.metadata.AttributesJmxAttributeSource"/>
</beans>

ManagedResource 속성에 제공된 objectName이 없다면 ObjectName은 [fully-qualified-package-name]:type=[short-classname],name=[bean-name]의 형식으로 생성될 것이다. 예를 들어 다음 빈에 생성된 ObjectName은 com.foo:type=MyClass,name=myBean가 될 것이다.

<bean id="myBean" class="com.foo.MyClass"/>


23.4.3 <context:mbean-export/> 요소

Java 5 이상을 사용함다면 MBeanExporter의 편리한 하위 클래스 AnnotationMBeanExporter를 사용할 수 있다. 이 하위 클래스의 인스턴스를 정의하면 표준 자바 어노테이션 기반의 메타데이터를 항상 사용할 것이므로 (자동탐지도 항상 활성화된다) namingStrategy, assembler, attributeSource 설정이 더이상 필요없다. 게다가 스프링의 'context' 네임스페이스가 지원하는 더 간단한 분법도 가능하다. MBeanExporter 빈을 정의하는 대신 다음의 요소를 지정하면 된다.

<context:mbean-export/>

필요하다면 특정 MBean 서버에 대한 참조를 제공할 수도 있고 defaultDomain 속성 (AnnotationMBeanExporter의 프로퍼티)은 생성된 Mbean ObjectNames의 도메인의 대체값을 받는다. 이전 MetadataNamingStrategy 섹션에서 설명한대로 이는 정규화된 패키지명에 사용한다.

<context:mbean-export server="myMBeanServer" default-domain="myDomain"/>
Note
빈(bean) 클래스에서 JMX 어노테이션의 자동탐지와 함께 인터페이스 기반의 AOP 프록시는 사용하지 말아라. 인터페이스 기반의 프록시는 대상 클래스를 '숨기고' JMX가 관리하는 리소스 어노테이션도 숨긴다. 그러므로 이러한 경우에는 대상클래스(target-class) 프록시를 사용해야 한다. 등의 'proxy-target-class' 플래그를 설정하더라도 JMX 빈은 구동시 이를 경고없이 무시할 것이다.


23.5 JSR-160 커넥터

리모트 접근을 위해서 스프링 JMX 모듈은 서버측과 클라이언트측 커넥터를 생성하는 org.springframework.jmx.support 패키지에서 FactoryBean의 두가지 구현체를 제공한다.

23.5.1 서버측 커넥터

스프링 JMX가 JSR-160 JMXConnectorServer를 생성해서 구동하고 노출하게 하려면 다음 설정을 사용해라.

<bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBean"/>

기본적으로 ConnectorServerFactoryBean는 JMXConnectorServer을 "service:jmx:jmxmp://localhost:9875"에 바인딩해서 생성한다. 그러므로 serverConnector 빈은 MBeanServer를 localhost의 JMXMP 프로토콜과 9875 포트로 노출한다. JMXMP 프로토콜은 JSR-160 명세에 선택사항으로 나와 있다. 현재로써는 주요 오픈소스 JMX 구현체인 MX4J와 J2SE 5.0이 제공하는 구현체가 JMXMP를 지원하지 않는다.

다른 URL을 지정해서 JMXConnectorServer 자체를 MBeanServer에 등록하려면 serviceUrl과 ObjectName 프로퍼티를 각각 사용해라.

<bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBean">
  <property name="objectName" value="connector:name=rmi"/>
  <property name="serviceUrl" value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/myconnector"/>
</bean>

ObjectName 프로퍼티를 설정하면 스프링이 해당 ObjectName의 MBeanServer에 커넥터를 자동으로 등록할 것이다. 다음 예제는 JMXConnector 생성시 ConnectorServerFactoryBean에 전달할 수 있는 전체 파라미터를 보여주고 있다.

<bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBean">
  <property name="objectName" value="connector:name=iiop"/>
  <property name="serviceUrl" value="service:jmx:iiop://localhost/jndi/iiop://localhost:900/myconnector"/>
  <property name="threaded" value="true"/>
  <property name="daemon" value="true"/>
  <property name="environment">
    <map>
      <entry key="someKey" value="someValue"/>
    </map>
  </property>
</bean>

RMI에 기반한 커넥터를 사용한다면 이름 등록과정이 완료된 후 시작되도록 검색 서비스(tnameserv나 rmiregistry)가 필요하다. RMI로 원격 서비스를 내보내도록 스프링을 사용하고 있다면 스프링은 이미 RMI 레지스트리를 생성했을 것이다. RIM로 원격서비스를 내보내도록 하고 있지 않다면 다음의 설정을 사용해서 간단하게 레지스트리를 시작할 수 있다.

<bean id="registry" class="org.springframework.remoting.rmi.RmiRegistryFactoryBean">
  <property name="port" value="1099"/>
</bean>


23.5.2 클라이언트측 커넥터

원격 JSR-160를 사용하는 MBeanServer에 MBeanServerConnection를 생성하려면 다음과 같이 MBeanServerConnectionFactoryBean를 사용해라.

<bean id="clientConnector" class="org.springframework.jmx.support.MBeanServerConnectionFactoryBean">
  <property name="serviceUrl" value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxrmi"/>
</bean>


23.5.3 Burlap/Hessian/SOAP에서의 JMX

JSR-160은 클라이언트와 서버간의 통신을 수행하는 방법에 확장포인트를 허용하고 있다. 앞의 예제는 JSR-160 명세에 명시된 의무적인 RMI 기반의 구현체(IIOP와 JRMP)와 (선택적인) JMXMP를 사용하고 있다. 다른 프로바이더나 JMX 구현체 (MX4J와 같은)를 사용해서 HTTP나 SSL등을 사용하는 SOAP, Hessian, Burlap같은 프로토콜의 장점을 취할 수 있다.

<bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBean">
  <property name="objectName" value="connector:name=burlap"/>
  <property name="serviceUrl" value="service:jmx:burlap://localhost:9874"/>
</bean>

앞의 예제에서는 MX4J 3.0.0을 사용했다. 자세한 내용은 MX4J 공식 문서를 참고해라.

23.6 프록시를 통한 MBean 접근

스프링 JMX에서는 로컬이나 원격 MBeanServer에 등록된 MBean 호출을 리라우팅하는 프록시를 생성할 수 있다. 이러한 프록시는 MBean과 상호작용할 수 있는 표준 자바 인터페이스를 제공한다. 다음 코드는 로컬 MBeanServer에서 동작 중인 MBean에 대한 프록시를 구성하는 방법을 보여준다.

<bean id="proxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean">
  <property name="objectName" value="bean:name=testBean"/>
  <property name="proxyInterface" value="org.springframework.jmx.IJmxTestBean"/>
</bean>

ObjectName: bean:name=testBean으로 등록된 MBean에 프록시가 생성된 것을 볼 수 있다. 프록시가 구현하는 인터페이스들은 proxyInterfaces 프로퍼티로 제어하고 이 인터페이스들의 메서드와 프로퍼티를 MBean의 동작과 속성으로 매핑하는 규칙은 InterfaceBasedMBeanInfoAssembler에서 사용한 규칙과 동일하다.

MBeanProxyFactoryBean은 MBeanServerConnection로 접근할 수 있는 모든 MBean에 대해서 프록시를 생성할 수 있다. 기본적으로 로컬 MBeanServer를 사용하지만 이를 오버라이드해서 원격 MBean을 가리키는 프록시에 대한 원격 MBeanServer를 가리키는 MBeanServerConnection를 제공할 수 있다.

<bean id="clientConnector" class="org.springframework.jmx.support.MBeanServerConnectionFactoryBean">
  <property name="serviceUrl" value="service:jmx:rmi://remotehost:9875"/>
</bean>

<bean id="proxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean">
  <property name="objectName" value="bean:name=testBean"/>
  <property name="proxyInterface" value="org.springframework.jmx.IJmxTestBean"/>
  <property name="server" ref="clientConnector"/>
</bean>

위 예제에서 MBeanServerConnectionFactoryBean를 사용하는 원격 머신을 가리키는 MBeanServerConnection를 생성한 것을 볼 수 있다. server 프로퍼티로 이 MBeanServerConnection을 MBeanProxyFactoryBean에 전달한다. 생성한 프록시는 이 MBeanServerConnection를 사용해서 모든 호출을 MBeanServer로 보낼 것이다.

23.7 알림(Notification)

스프링의 JMX는 JMX 알림도 광범위하게 지원하고 있다.

23.7.1 알림에 대한 리스너 등록

스프링의 JMX는 다수의 MBean(이는 스프링의 MBeanExporter가 내보낸 MBean과 다른 메카니즘으로 등록된 MBean을 포함한다.)을 가진 다수의 NotificationListeners을 아주 쉽게 등록할 수 있도록 지원한다. 예제를 통해서 대상 MBean의 속성이 변경될 때마다 알림을 받는(Notification으로) 시나리오를 생각해 보자.

package com.example;

import javax.management.AttributeChangeNotification;
import javax.management.Notification;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;

public class ConsoleLoggingNotificationListener implements NotificationListener, NotificationFilter {

  public void handleNotification(Notification notification, Object handback) {
    System.out.println(notification);
    System.out.println(handback);
  }

  public boolean isNotificationEnabled(Notification notification) {
    return AttributeChangeNotification.class.isAssignableFrom(notification.getClass());
  }
}
<beans>
  <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
      <map>
        <entry key="bean:name=testBean1" value-ref="testBean"/>
      </map>
    </property>
    <property name="notificationListenerMappings">
      <map>
        <entry key="bean:name=testBean1">
          <bean class="com.example.ConsoleLoggingNotificationListener"/>
        </entry>
      </map>
    </property>
  </bean>

  <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
    <property name="name" value="TEST"/>
    <property name="age" value="100"/>
  </bean>
</beans>

위 설정에서 대상 MBean(bean:name=testBean1)이 JMX Notification를 브로드캐스트할 때마다 notificationListenerMappings 프로퍼티에 리스너로 등록한 ConsoleLoggingNotificationListener 빈이 알림을 받을 것이다. ConsoleLoggingNotificationListener 빈은 Notification의 응답으로 적절한 모든 액션을 받을 수 있다.

내보낸 빈과 리스너간의 연결로 빈 이름을 직접 사용할 수도 있다.

<beans>
  <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
      <map>
        <entry key="bean:name=testBean1" value-ref="testBean"/>
      </map>
    </property>
    <property name="notificationListenerMappings">
      <map>
        <entry key="testBean">
          <bean class="com.example.ConsoleLoggingNotificationListener"/>
        </entry>
      </map>
    </property>
  </bean>

  <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
    <property name="name" value="TEST"/>
    <property name="age" value="100"/>
  </bean>
</beans>

MBeanExporter가 내보내는 모든 빈에 하나의 NotificationListener 인스턴스를 등록하고 싶다면 notificationListenerMappings 프로퍼티 맵의 엔트리에 키로 '*' (따옴표없이)를 와일드카드로 사용할 수 있다. 예를 들면 다음과 같다.

<property name="notificationListenerMappings">
  <map>
    <entry key="*">
      <bean class="com.example.ConsoleLoggingNotificationListener"/>
    </entry>
  </map>
</property>

반대로 해야 한다면(즉, MBean에 다수의 리스너를 등록한다면) notificationListeners 리스트 프로퍼티(그리고 notificationListenerMappings 프로퍼티에 대한 설정에)를 대신 사용해야 한다. 이 때 단일 MBean의 NotificationListener를 설정하는 대신 NotificationListenerBean 인스턴스를 설정해라. NotificationListenerBean은 NotificationListener와 ObjectName(또는 ObjectNames)을 은닉화하고 이는 MBeanServer에 등록된다. NotificationListenerBean은 NotificationFilter와 고급 JMX 알림 시나리오에서 사용할 수 있는 임의의 handback 객체같은 다수의 프로퍼티들도 은닉화한다.

NotificationListenerBean 인스턴스를 사용할 때의 설정은 이전에 본 설정과 크게 다르지 않다.

<beans>
  <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
      <map>
        <entry key="bean:name=testBean1" value-ref="testBean"/>
      </map>
    </property>
    <property name="notificationListeners">
      <list>
        <bean class="org.springframework.jmx.export.NotificationListenerBean">
          <constructor-arg>
            <bean class="com.example.ConsoleLoggingNotificationListener"/>
          </constructor-arg>
          <property name="mappedObjectNames">
            <list>
              <value>bean:name=testBean1</value>
            </list>
          </property>
        </bean>
      </list>
    </property>
  </bean>

  <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
    <property name="name" value="TEST"/>
    <property name="age" value="100"/>
  </bean>
</beans>

위의 예제는 첫 알림 예제와 같다. Notification이 발생할 때마다 handback 객체가 주어지고 해당 NotificationFilter로 관련없는 Notifications을 필터링하기를 원한다고 해보자. (handback 객체가 무엇이고 NotificationFilter가 무엇인지에 대해서는 JMX 명세서(1.2)의 'The JMX Notification Model' 부분을 참고하기 바란다.)

<beans>
  <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
      <map>
        <entry key="bean:name=testBean1" value-ref="testBean1"/>
        <entry key="bean:name=testBean2" value-ref="testBean2"/>
      </map>
    </property>
    <property name="notificationListeners">
      <list>
        <bean class="org.springframework.jmx.export.NotificationListenerBean">
          <constructor-arg ref="customerNotificationListener"/>
          <property name="mappedObjectNames">
            <list>
              <!-- 두 별개의 MBean에서 받은 알림을 처리한다 -->
              <value>bean:name=testBean1</value>
              <value>bean:name=testBean2</value>
            </list>
          </property>
          <property name="handback">
            <bean class="java.lang.String">
              <constructor-arg value="This could be anything..."/>
            </bean>
          </property>
          <property name="notificationFilter" ref="customerNotificationListener"/>
        </bean>
      </list>
    </property>
  </bean>

  
  <bean id="customerNotificationListener" class="com.example.ConsoleLoggingNotificationListener"/>

  <bean id="testBean1" class="org.springframework.jmx.JmxTestBean">
    <property name="name" value="TEST"/>
    <property name="age" value="100"/>
  </bean>

  <bean id="testBean2" class="org.springframework.jmx.JmxTestBean">
    <property name="name" value="ANOTHER TEST"/>
    <property name="age" value="200"/>
  </bean>
</beans>


23.7.2 알림 발행하기

스프링은 Notifications을 받기 위한 등록뿐 아니라 Notifications을 보내기위한 등록도 지원한다.

Note
이 부분은 MBeanExporter를 통해 MBean으로 노출된 스프링이 관리하는 빈에 대해서만 관련된 부분이다. 이미 존재하거나 사용자가 정의한 모든 MBean은 표준 JMX API를 사용해서 알림을 발행해야 한다.


스프링의 JMX 알림 발행 지원에서 핵심 인터페이스는 NotificationPublisher 인터페이스다. (org.springframework.jmx.export.notification 패키지에 정의되어 있다.) MBeanExporter 인스턴스를 통해서 MBean으로 내보내질 모든 빈은 NotificationPublisher 인스턴스에 접근할 수 있도록 NotificationPublisherAware 인터페이스를 구현할 수 있다. NotificationPublisherAware 인터페이스는 해당 빈이 Notifications을 발행하는데 사용할 수 있는 간단한 setter 메서드로 구현한 빈에 NotificationPublisher 인스턴스를 제공한다.

NotificationPublisher 클래스의 Javadoc에 나온대로 NotificationPublisher 메카니즘으로 이벤트를 발행하면서 관리되고 있는 빈은 어떤 알림 리스너 등에 대해서도 상태관리를 책임지지 않는다. 스프링의 JMX 지원은 JMX 인프라스트럭처와 관련된 모든 이슈의 처리를 담당한다. 애플리케이션 개발자가 해야하는 일은 NotificationPublisherAware 인터페이스를 구현하고 제공된 NotificationPublisher 인스턴스를 사용해서 이벤트를 발행하는 것 뿐이다. 관리되는 빈이 MBeanServer에 등록된 후에 NotificationPublisher가 설정된다는 점에 주의해라.

NotificationPublisher 인스턴스의 사용은 아주 직관적이다. 그냥 Notification 인스턴스(또는 Notification의 적절한 하위클래스의 인스턴스)를 만들고 발행할 이벤트와 관련된 데이터로 알림을 만든 후 NotificationPublisher 인스턴스의 sendNotification(Notification)를 호출해서 Notification을 전달하면 된다.

아래의 간단한 예제를 보자. 이 시나리오에서 내보내진 JmxTestBean 인스턴스는 add(int, int)가 호출될 때마다 NotificationEvent를 발행할 것이다.

package org.springframework.jmx;

import org.springframework.jmx.export.notification.NotificationPublisherAware;
import org.springframework.jmx.export.notification.NotificationPublisher;
import javax.management.Notification;

public class JmxTestBean implements IJmxTestBean, NotificationPublisherAware {

  private String name;
  private int age;
  private boolean isSuperman;
  private NotificationPublisher publisher;

  // 다른 getter와 setter는 생략했다

  public int add(int x, int y) {
    int answer = x + y;
    this.publisher.sendNotification(new Notification("add", this, 0));
    return answer;
  }

  public void dontExposeMe() {
    throw new RuntimeException();
  }

  public void setNotificationPublisher(NotificationPublisher notificationPublisher) {
    this.publisher = notificationPublisher;
  }
}

NotificationPublisher 인터페이스와 관련된 부분이 모두 잘 동작한다는 점이 스프링의 JMX 지원을 좋은 점중 하나다. 하지만 클래스가 스프링과 JMX에 모두 의존성을 갖게 된다는 단점이 있다. 언제나 그렇듯이 이에 대한 조언은 현실적인 조언 뿐이다. NotificationPublisher이 제공하는 기능이 필요하고 스프링과 JMX에 대한 의존성을 받아들일 수 있다면 사용해도 좋다.

23.8 추가 자료

이 섹션에서는 JMX에 대한 추가자료와 관련된 링크를 제공한다.

2014/02/04 23:55 2014/02/04 23:55