Tuesday, July 22, 2014

Remote JMX Notification Listener Using Spring Framework

July 22, 2014


We did it easily with pure out-of-box solution in Java. But we want to use Spring since everything else are supported by Spring.

It's simply no working examples on the Internet. Our Senior Java developer said.

I googled and couldn't find meaningful solution either. all the examples are just local listeners that did not help us on our situation.

I eventually decided to invent my own way and fortunately the solution was found within two hours. Thanks for meaningful names used in the framework and my limited but fundamental knowledge on JMX. I guessed I could give it a try when I saw name like NotificationListenerRegistrar. Now lack of example of using Spring to remotely receive JMX notification became a history.

The key is to use org.springframework.jmx.access.NotificationListenerRegistrar. I had no time to verify if its mappedObjectNames is configured properly, but the configuration I used seemed working.

The steps that leads to success is

1. configure a client connector
2. configure a notification listener registrar that picks notification from client connector
3. register my listener in the registrar

Below is the spring context I used in the client side.


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

<bean id="amaListener" class="ama.test.ConsoleLoggingNotificationListener">
</bean>
<bean id="clientConnector"
class="org.springframework.jmx.support.MBeanServerConnectionFactoryBean">
<property name="serviceUrl"
value="service:jmx:rmi://myIP/jndi/rmi://myIP:1999/jmxrmi" />
</bean>
<--proxy here is not related to notification listener and can be removed --> <bean id="serverProxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean">
<property name="objectName" value="bean:name=amaBean" />
<property name="proxyInterface" value="ama.test.ITestBean" />
<property name="server" ref="clientConnector" />
</bean>
<bean id="notificationListener" class="org.springframework.jmx.access.NotificationListenerRegistrar">
<property name="mappedObjectNames">
<list>
<value>bean:name=amaBean</value>
</list>
</property>
<property name="NotificationListener" ref="amaListener" />
<property name="server" ref="clientConnector" />
</bean>


</beans>



Below are the whole examples including both MBean and client side listener. The source code of MBean was copied from web example so that it must be familiar to you if you already searched on this topic. BTW, the Spring I used is version 4. And one last piece, the VM parameters passed in while starting the MBean application (SpringMain.java, not for TestClient.java) are like this:

-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
-Djava.rmi.server.hostname=myIP
BTW, if port is specified, for instance, -Dcom.sun.management.jmxremote.port=2014, then you defined a standard way to access JMX server, and you can connect to JMX server via both 1999 and 2014 in this example.

ITestBean.java The interface

package ama.test;

public interface ITestBean {
public int add(int x, int y);
}

JmxTestBean.java

package ama.test;

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

public class JmxTestBean implements NotificationPublisherAware, ITestBean {

    private String name;
    public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

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

//Its add method can be invoked via jconsole and it sends out notification.

    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;
    }
}

SpringMain.java
This is the application that has an M Bean defined.

package ama.test;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringMain {

  public static void main(String[] args) throws Exception{

       new ClassPathXmlApplicationContext("application-context.xml");
       System.in.read();
    }
}

TestClient.java
--This client is used to demonstrate remove notification listener by using Spring framework.
package ama.test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestClient {

 public static void main(String[] args) throws Exception{
 
      new ClassPathXmlApplicationContext("client-app-context.xml");
       System.in.read();
   }

}

application-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx.xsd">
    
   
<bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean">
<property name="locateExistingServerIfPossible" value="true" />
</bean>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="bean:name=amaBean" value-ref="testBean" />
</map>
</property>
<property name="server" ref="mbeanServer"/>
<!--
<property name="notificationListenerMappings">
<map>
<entry key="bean:name=amaBean">
<bean class="ama.test.ConsoleLoggingNotificationListener" />
</entry>
</map>
</property>
-->
</bean>

  <bean id="testBean" class="ama.test.JmxTestBean">
    <property name="name" value="TEST"/>
    <property name="age" value="100"/>
  </bean>

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

   <bean id="serverConnector"
         class="org.springframework.jmx.support.ConnectorServerFactoryBean"
         depends-on="registry">
      <property name="objectName" value="connector:name=rmi"/>
      <property name="serviceUrl" 
                value="service:jmx:rmi://myIP/jndi/rmi://myIP:1999/jmxrmi"/>
   </bean>
</beans>


client-app-context.xml
  <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <bean id="amaListener" class="ama.test.ConsoleLoggingNotificationListener"> </bean> <bean id="clientConnector" class="org.springframework.jmx.support.MBeanServerConnectionFactoryBean"> <property name="serviceUrl" value="service:jmx:rmi://10.2.39.108/jndi/rmi://10.2.39.108:1999/jmxrmi" /> </bean> <bean id="serverProxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean"> <property name="objectName" value="bean:name=amaBean" /> <property name="proxyInterface" value="ama.test.ITestBean" /> <property name="server" ref="clientConnector" /> </bean> <bean id="notificationListener" class="org.springframework.jmx.access.NotificationListenerRegistrar"> <property name="mappedObjectNames"> <list> <value>bean:name=amaBean</value> </list> </property> <property name="NotificationListener" ref="amaListener" /> <property name="server" ref="clientConnector" /> </bean> </beans>


This along with this post finished my day perfectly. :)

No comments: