JMX 애플리케이션 개발

본 장에서는 JEUS에서 제공하는 MBean에 접근하기 위한 클라이언트 애플리케이션 개발에 대한 내용을 다룬다.

1. JMX 클라이언트 애플리케이션

본 절에서는 일반적인 JMX 클라이언트 애플리케이션의 동작에 대해 간략하게 설명한다.

  1. JEUS MBean 서버에 연결하기 위해서는 아래 2가지 방법 중 하나를 사용할 수 있다.

  2. 사용하고자 하는 기능을 제공하는 MBean에 접근하여 속성값을 얻어오거나, 작업을 수행시킨다.

    • MBean에 접근하기 위해서는 해당 MBean을 가리키는 고유 이름이라고 할 수 있는 ObjectName을 알고 있어야 한다. JEUS에서 제공하는 MBean들이 갖는 ObjectName 형식에 대해서는 MBean Object Names를 참고한다.

    • 보안을 위해 각 MBean들이 제공하는 속성값이나 연산들은 수행 시 권한 체크를 수행하는 경우가 있다. 이러한 MBean을 사용하기 위해서는 적절한 권한을 갖는 사용자로 인증을 한 후 접근을 해야 한다. JMX 연결할 때 보안 설정에 관한 내용은 Security 설정을 참고한다.

  3. 얻어온 속성값이나 수행시킨 작업의 결과를 받아와 처리한다.

  4. 여러 작업을 수행하는 경우 2, 3의 과정을 반복한다.

본 장에서 설명하는 내용을 이해하기 위해서 JMX Remote API 1.0과 Jakarta EE Management 스펙에 대한 기본 지식이 필요하다. JMX Remote API에 대한 자세한 정보는 Oracle에서 제공하는 JMX Remote API 1.0 스펙과 JMX Remote API를 참고한다.

2. MBean 서버 연결

본 절에서는 MBean 서버로 접속하기 위한 방법에 대해서 설명한다.

2.1. JNDI 사용

본 절에서는 JNDI를 사용해서 JEUS를 모니터링하는 JMX 애플리케이션에 대해서 설명한다.

각 서버는 기동할 때 JMX 연결을 맺기 위해 필요한 정보를 담고 있는 javax.naming.Reference 객체를 JNDI에 등록한다. 등록할 때 사용하는 이름은 "mgmt/rmbs/adminServer"와 같은 형식을 가진다. 사용자는 JNDI에 등록되어 있는 Reference를 통해 MBean 서버와 연결을 맺을 수 있다.

다음은 JNDI를 사용하는 클라이언트 예제이다.

package jmxclient;

import java.util.Set;
import java.util.Iterator;
import java.util.Hashtable;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.naming.Context;
import javax.naming.InitialContext;

/**
 * JMX Client which uses JNDI lookup.
 */
public class JMXClientUsingJndi {

    public static void main(String args[]) throws Exception {
        if(args.length < 4) {
            System.out.println("Required arguments: "
                + "hostname username password target-name");
            return;
        }

        // Step 1. Setting Environments
        String hostname = args[0];
        String username = args[1];
        String password = args[2];

        // targetName could be server name,
        // for example, "adminServer", "server1"
        String targetName = args[3];

        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "jeus.jndi.JEUSContextFactory");
        env.put(Context.PROVIDER_URL, hostname);
        env.put(Context.SECURITY_PRINCIPAL, username);
        env.put(Context.SECURITY_CREDENTIALS, password);

        // Step 2. Getting MBeanServerConnection
        InitialContext ctx = new InitialContext(env);
        JMXConnector connector = (JMXConnector)ctx.lookup("mgmt/rmbs/" + targetName);
        MBeanServerConnection mbeanServer = connector.getMBeanServerConnection();

        // Step 3. Query
        ObjectName jeusScope = new ObjectName("JEUS:*");
        Set objectNames = mbeanServer.queryNames(jeusScope, null);

        // Step 4. Handling the Query Result
        for(Iterator i = objectNames.iterator(); i.hasNext();) {
            System.out.println("[MBean] " + i.next());
        }
    }
}

위 예제를 작성한 후 컴파일하여 실행하면 JEUS 서버에 접속한 후 "JEUS:*"에 해당하는 MBean들의 목록을 출력한다. 예제 프로그램은 인자를 4개 받는데, 첫 번째는 서버의 hostname, 두 번째는 JEUS 사용자 이름, 세 번째는 비밀번호, 마지막은 서버 이름이다.

$ java -classpath .:${JEUS_HOME}/lib/client/jclient.jar jmxclient.JMXClientUsingJndi 127.0.0.1:9736 jeus jeus adminServer

[2016.05.28 15:20:24][2] [t-1] [NET-0002] Beginning to listen to NonBlockingChannelAcceptor: /127.0.0.1:9756.
[MBean] JEUS:j2eeType=JeusService,jeusType=ThreadPool,JMXManager=adminServer,
J2EEServer=adminServer,name=threadpool.System
[MBean] JEUS:j2eeType=JeusService,jeusType=JEUSMPConnector,JMXManager=adminServer,
J2EEServer=adminServer,name=adminServer
[MBean] JEUS:j2eeType=JeusService,jeusType=JMSDestinationResource,
JMXManager=adminServer,J2EEServer=adminServer,JMSResource=adminServer_jms,
name=ExamplesQueue
[MBean] JEUS:j2eeType=JeusService,jeusType=JeusLogService,JMXManager=adminServer,
J2EEServer=adminServer,name=adminServer
[MBean] JEUS:j2eeType=JeusService,jeusType=ThreadPool_WEBC,JMXManager=adminServer,
WebEngine=adminServer_servlet,J2EEServer=adminServer,WebListener=http1,name=http1
[MBean] JEUS:j2eeType=JeusService,jeusType=SecurityDomain,JMXManager=adminServer,
J2EEDomain=domain1,SecurityService=SecurityService,name=SYSTEM_DOMAIN
[MBean] JEUS:j2eeType=JeusService,jeusType=DeploymentPlanManagementService,
JMXManager=adminServer,J2EEDomain=domain1,name=adminServer
[MBean] JEUS:j2eeType=JeusService,jeusType=JMSConnectionFactoryResource,
JMXManager=adminServer,J2EEServer=adminServer,JMSResource=adminServer_jms,
name=ConnectionFactory
[MBean] JEUS:j2eeType=JeusService,jeusType=JMSEngine,JMXManager=adminServer,
J2EEServer=adminServer,name=adminServer_jms
[MBean] JEUS:j2eeType=JeusService,jeusType=ServerDeploymentService,
JMXManager=adminServer,J2EEServer=adminServer,name=adminServer
...
  1. 예제 프로그램은 jclient.jar가 있어야 실행할 수 있다. 기본적으로 jclient.jar는 JEUS_HOME/lib/client 아래에 위치한다.

  2. JNDI의 자세한 정보에 대해서는 JEUS Server 안내서의 JNDI Naming Server를 참고한다. 만약에 JMX 애플리케이션이 서블릿 또는 EJB에서 실행된다면 JNDI 파라미터에 대한 설정은 필요하지 않다.

2.2. JMX Remote API 사용

본 절에서는 JMX Remote API를 사용해서 JEUS를 모니터링하는 JMX 애플리케이션에 대해서 가장 일반적으로 사용되는 방식으로 설명한다.

연결을 맺을 대상은 JMX Service URL을 통해 지정한다. JEUS MBean 서버에 연결하기 위한 URL은 아래와 같은 형태를 갖는다.

  • service:jmx:jmxmp://0.0.0.0:9736/JeusMBeanServer

  • service:jmx:jmxmp://0.0.0.0:9736/JEUSMP_<adminServer>(adminServer는 접근할 서버 이름에 따라 달라진다.)

다음은 JMX Remote API를 사용하는 클라이언트 예제이다.

package jmxclient;

import java.util.Hashtable;
import java.util.Iterator;
import java.util.Set;

import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import javax.naming.Context;

/**
 * JMX Client which uses JMX Remote API
 */
public class JMXClientUsingJmxUrl {
    private static final String URL_PATH = "/JeusMBeanServer";

    public static void main(String args[]) throws Exception {
        if(args.length < 4) {
            System.out.println("Required arguments: "
                + "hostname port username password");
            return;
        }

        // Step 1. Setting Environments
        String address = args[0];
        int port = Integer.parseInt(args[1]);
        String username = args[2];
        String password = args[3];

        Hashtable env = new Hashtable();
        env.put(JMXConnectorFactory.PROTOCOL_PROVIDER_PACKAGES, "jeus.management.remote.protocol");
        env.put(Context.SECURITY_PRINCIPAL, username);
        env.put(Context.SECURITY_CREDENTIALS, password);

        // Step 2. Getting MBeanServerConnection
        JMXServiceURL serviceURL = new JMXServiceURL("jmxmp", address, port, URL_PATH);
        JMXConnector connector = JMXConnectorFactory.connect(serviceURL, env);
        MBeanServerConnection mbeanServer = connector.getMBeanServerConnection();

        // Step 3. Query
        ObjectName jeusScope = new ObjectName("JEUS:*");
        Set objectNames = mbeanServer.queryNames(jeusScope, null);

        // Step 4. Handling the Query Result
        for(Iterator i = objectNames.iterator(); i.hasNext();) {
            System.out.println("[MBean] " + i.next());
        }
    }
}

위 예제를 작성한 후 컴파일하여 실행하면 JEUS 서버에 접속한 후 "JEUS:*"에 해당하는 MBean들의 목록을 출력한다. 인자는 순서대로 서버 주소, 포트, 사용자 이름, 비밀 번호이다.

$ java -classpath .:${JEUS_HOME}/lib/client/jclient.jar jmxclient.JMXClientUsingJmxUrl 127.0.0.1 9736 jeus jeus

[2016.05.28 15:21:29][2] [t-1] [NET-0002] Beginning to listen to NonBlockingChannelAcceptor: /127.0.0.1:9756.
[MBean] JEUS:j2eeType=JeusService,jeusType=ThreadPool,JMXManager=adminServer,
J2EEServer=adminServer,name=threadpool.System
[MBean] JEUS:j2eeType=JeusService,jeusType=JEUSMPConnector,JMXManager=adminServer,
J2EEServer=adminServer,name=adminServer
[MBean] JEUS:j2eeType=JeusService,jeusType=JMSDestinationResource,
JMXManager=adminServer,J2EEServer=adminServer,JMSResource=adminServer_jms,
name=ExamplesQueue
[MBean] JEUS:j2eeType=JeusService,jeusType=JeusLogService,JMXManager=adminServer,
J2EEServer=adminServer,name=adminServer
[MBean] JEUS:j2eeType=JeusService,jeusType=ThreadPool_WEBC,JMXManager=adminServer,
WebEngine=adminServer_servlet,J2EEServer=adminServer,WebListener=http1,name=http1
[MBean] JEUS:j2eeType=JeusService,jeusType=SecurityDomain,JMXManager=adminServer,
J2EEDomain=domain1,SecurityService=SecurityService,name=SYSTEM_DOMAIN
[MBean] JEUS:j2eeType=JeusService,jeusType=DeploymentPlanManagementService,
JMXManager=adminServer,J2EEDomain=domain1,name=adminServer
[MBean] JEUS:j2eeType=JeusService,jeusType=JMSConnectionFactoryResource,
JMXManager=adminServer,J2EEServer=adminServer,JMSResource=adminServer_jms,
name=ConnectionFactory
[MBean] JEUS:j2eeType=JeusService,jeusType=JMSEngine,JMXManager=adminServer,
J2EEServer=adminServer,name=adminServer_jms
[MBean] JEUS:j2eeType=JeusService,jeusType=ServerDeploymentService,
JMXManager=adminServer,J2EEServer=adminServer,name=adminServer
...

예제 프로그램은 jclient.jar가 있어야 실행할 수 있다. 기본적으로 jclient.jar는 JEUS_HOME/lib/client 아래에 위치한다.

3. Security 설정

JMX를 사용하여 JEUS 서버에 등록되어 있는 여러 MBean들이 제공하는 속성을 읽거나 작업할 경우 JEUS는 해당 연결을 맺은 사용자가 속성을 읽을 권한이 있는지 작업을 수행할 권한이 있는지를 검사한다. 각각 MBean을 사용할 때 필요한 권한에 대한 정보는 JEUS API 문서를 참고한다.

API 문서는 다음 위치에서 찾을 수 있다.

JEUS_HOME/docs/api

API 문서는 MBean을 사용할 때 필요한 권한(Permission Name)뿐만 아니라 ObjectNamePattern, 속성(Attribute), 작업(Operation) 등에 대한 정보도 제공하고 있다.

JMX 애플리케이션에서 사용자 이름이나 비밀번호와 같은 정보는 서버에 접속해서 MBeanServerConnection을 생성할 때 제공한다. 일반적으로 MBeanServerConnection을 생성할 때는 다음과 같은 코드를 사용하는데, 코드를 살펴보면 Hashtable로 전달하는 환경설정 정보에 사용자 이름과 비밀번호가 들어가는 것을 확인할 수 있다.

...

JMXServiceURL serviceURL =
        new JMXServiceURL("service:jmx:jmxmp://127.0.0.1:9736/adminServer");

Map<String, Object> env = new HashMap<String, Object>();
env.put(Context.SECURITY_PRINCIPAL, id);
env.put(Context.SECURITY_CREDENTIALS, password);
env.put(Context.INITIAL_CONTEXT_FACTORY, "jeus.jndi.JEUSContextFactory");
env.put("jmx.remote.x.request.timeout", "10");

JMXConnector connector = JMXConnectorFactory.connect(serviceURL, env);
MBeanServerConnection connection = connector.getMBeanServerConnection();

...

이때 사용하는 사용자 이름과 비밀번호, 권한 설정은 JEUS Security를 이용해서 설정한다. JEUS Security 설정 방법에 대한 자세한 내용은 JEUS Security 안내서의 보안 시스템 사용자 정보 설정보안 시스템 정책 설정을 참고한다.

4. MBean Object Names

ObjectName은 MBean 객체의 기본 JMX 객체 이름이다. MBean 서버에 접근하여 MBean과 상호작용하기 위해서는 대상 MBean의 이름을 알고 있어야 한다. 만약 이름을 정확하게 알지 못하는 경우에는 알고 있는 부분을 이용하여 MBean 서버에 질의를 보낸 후 결과값을 받아 맞는 이름을 찾아볼 수 있다.

JEUS에서 제공하는 MBean에 대한 ObjectName은 아래와 같은 형태를 갖는다.

<domain_name>: j2eeType=<j2eeType_value>, name=<name_value>,
    [<parent-j2eeType_value>], [jeusType = <jeusType_value>],
    [isTargetable = <isTargetable_value>],
    JMXManager = <JMXManager_value> [,*]

또는

<domain_name>: *

ObjectName은 <domain_name>으로 시작해야 하고, 각 이름과 값의 짝이 순차적으로 규정되지 않는다.

예를 들면 다음은 둘 다 JEUS 도메인 MBean의 objectname을 얻어온다.

JEUS:j2eeType=J2EEDomain,JMXManager=adminServer, *
JEUS:JMXManager=adminServer, j2eeType=J2EEDomain, *

다음은 각 항목에 대한 설명이다.

  • <domain_name>

    • ObjectName의 도메인 이름으로 값은 'JEUS’이다.

  • j2eeType

    • MBean은 J2EE 타입이며, J2EE Management 스펙에 의해 기술된다.

    • 다음 값들 중 하나를 설정한다.

      AppClientModule

      EJBModule

      EntityBean

      J2EEApplication

      J2EEDomain

      J2EEServer

      JAXRResource

      JCAConnectionFactory

      JCAManagedConnectionFactory

      JCAResource

      JDBCDataSource

      JDBCDriver

      JDBCResource

      JMSResource

      JNDIResource

      JTAResource

      JVM

      JavaMailResource

      JeusService

      MessageDrivenBean

      ResourceAdaptor

      ResourceAdapterModule

      Servlet

      StatefulSessionBean

      StatelessSessionBean

      URLResource

      WebModule

  • name

    • MBean의 이름으로 각각의 MBean Object에는 유일한 값이 있다. 예를 들면 "adminServer"라는 서버가 실행하는 JVM의 이름은 'adminServer’이다.

  • parent-j2eeType

    • MBean의 상위 J2EE 타입으로 각 MBean들에 계층이 규정되어 있다. 예를 들면 "JDBCDriver"의 상위 J2EE 타입은 'JDBCDataSource’이다.

  • jeusType

    • JEUS JMX에서 정의된 MBean들의 타입이다. "JeusService" J2EE 타입만 몇 가지 jeusType을 가질 수 있다.

    • 다음 값들 중 하나를 설정한다.

      EJBEngine

      JMSClientResource

      JMSConnectionFactoryResource

      JMSDestinationResource

      JMSDurableSubscriberResource

      JMSEngine

      JMSPersistenceStoreManager

      JMSServiceChannel

      SecurityDomain

      SecurityPolicy

      SecurityService

      SecuritySubject

      SessionContainer

      SessionContainerCentral

      SessionContainerP2P

      ThreadPool

      ThreadPool_WEBC

      WebEngine

      WebListener

      WebServices

  • isTargetable

    • 설정값은 blooean 타입으로 사용자 AP(EJB, 서블릿, JSP)가 deploy되어 isTargetable 동작하는 MBean에서는 반드시 true로 설정되어야 한다.

  • JMXManager

    • MBean 서비스를 제공하는 JMXManager의 이름이다. 일반적으로 JMXManager 항목의 값은 해당 JMXManager가 속한 서버 이름과 동일하다.

5. Spring JMX Support

JEUS 8 버전부터 Spring JMX를 사용할 때 JEUS 서버가 제공하는 MBean 서버를 사용할 수 있도록 라이브러리를 제공한다. 아래 경로를 기준으로, 지원하고 있는 스프링 버전 별로 라이브러리를 각각 제공하고 있다.

JEUS_HOME/lib/shared/spring-support/

JEUS에서 제공하는 라이브러리를 사용하지 않더라도 Spring JMX는 JEUS 상에서 동작한다. JEUS라이브러리를 사용하려면 애플리케이션에 라이브러리를 패키징하고, 추가적인 설정이 필요하므로 JEUS 서버가 사용하는 MBean 서버를 사용해야 할 경우에만 사용하는 것을 권장한다.

JEUS Spring JMX 지원 라이브러리를 사용하기 위해서는 먼저 라이브러리를 WEB-INF/lib 아래에 넣은 후 Spring 설정 파일에 아래와 같이 설정하여 Spring JMX가 JEUS MBean 서버를 찾아서 사용할 수 있도록 한다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

  ...

  <bean id="mBeanServer" class="jeus.spring.jmx.JeusMBeanServerFactoryBean"/>

  <!-- this bean must not be lazily initialized if the exporting is to happen -->
  <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter" lazy-init="false">
    <property name="server" ref="mBeanServer"/>
    <property name="beans">
      <map>
        <entry key="bean:name=testBean1" value-ref="testBean"/>
      </map>
    </property>
  </bean>

  ...

</beans>

Spring 프레임워크 및 Spring JMX 에 대한 자세한 사항은 Spring Framework 공식 문서를 참고한다.