JMX MBean registration using Spring on a standalone JVM

12,839

Solution 1

In addition to defining an MBeanServerFactory bean (as Nicholas noted in their answer) using ...

<bean class="org.springframework.jmx.support.MBeanServerFactoryBean">
    <property name="locateExistingServerIfPossible" value="true" />
</bean>

... you need to tell the MBeanExporter what to manage:

If a bean implements one of the JMX management interfaces, MBeanExporter can simply register the MBean with the server through its autodetection process.

If a bean does not implement one of the JMX management interfaces, MBeanExporter will create the management information using the supplied MBeanInfoAssembler.

Assuming your abc.def.ghi.DH class does not implement any JMX interface, try defining your MBeanExporter as:

<bean class="org.springframework.jmx.export.MBeanExporter" lazy-init="false">
    <property name="assembler">
        <bean
            class="org.springframework.jmx.export.assembler.MethodNameBasedMBeanInfoAssembler"
        >
            <property name="managedMethods">
                <list>
                    <value>getNum</value>
                </list>
            </property>
        </bean>
    </property>
    <property name="beans">
        <map>
            <entry key="bean:name=dH1" value-ref="dH"/>
        </map>
    </property>
</bean>

Looking at the OpenJDK 7, update 2, build 21 DefaultMBeanServerInterceptor.java source, line 898 creates a DynamicMBean for regular objects:

DynamicMBean mbean = Introspector.makeDynamicMBean(object);

I haven't debugged it, but I bet mbeanServer.registerMBean(dh, new ObjectName("bean:name=dH1")) eventually calls DefaultMBeanServerInterceptor.registerObject(), which creates a DynamicMBean for you and properly registers your standard JavaBean properties' setters and getters.


Here are some test files that work using Spring Framework 3.0.5 and Oracle HotSpot Java 1.6.0_24. After setting your CLASSPATH environment variable, just run javac *.java and java Main and use VisualVM (or similar application) to connect to the running java application to see the registered MBeans.

ac.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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd"
    default-lazy-init="true"
>
    <bean id="test" class="Test" />
    <bean class="org.springframework.jmx.support.MBeanServerFactoryBean">
        <property name="locateExistingServerIfPossible" value="true" />
    </bean>
    <bean class="org.springframework.jmx.export.MBeanExporter" lazy-init="false">
        <property name="assembler">
            <bean
                class="org.springframework.jmx.export.assembler.MethodNameBasedMBeanInfoAssembler"
            >
                <property name="managedMethods">
                    <list>
                        <value>getVal</value>
                        <value>setVal</value>
                    </list>
                </property>
            </bean>
        </property>
        <property name="beans">
            <map>
                <entry key="bean:name=Test" value-ref="test"/>
            </map>
        </property>
    </bean>
</beans>

Test.java:

public class Test {
    private String val = "";
    public String getVal() {
        return val;
    }
    public void setVal(String v) {
        val = v;
    }
}

Main.java:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
    public static void main(final String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("ac.xml");
        try {
            Thread.sleep(1000 * 60 * 5);
        } catch (final Throwable t) {}
    }
}

Solution 2

The issue is with the MBeanServerFactoryBean.

From the javadoc:

By default, MBeanServerFactoryBean will always create a new MBeanServer even if one is already running. To have the MBeanServerFactoryBean attempt to locate a running MBeanServer first, set the value of the "locateExistingServerIfPossible" property to "true".

Try this config:

<bean class="org.springframework.jmx.support.MBeanServerFactoryBean">
    <property name="locateExistingServerIfPossible" value="true" />
</bean>

=================================================

Try specifying the MBeanServer in the exporter bean:

<bean class="org.springframework.jmx.export.MBeanExporter" lazy-init="false">
    <property name="beans">
        <map>
                <entry key="bean:name=dH1" value-ref="dH" />
            </map>
        </property>
        <property name="server" ref="MBeanServer" />
</bean>
<bean id="MBeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean">
    <property name="locateExistingServerIfPossible" value="true" />
</bean>

========================================================================

Ok, let's take the brute force approach and acquire the platform MBeanServer directly:

<bean class="org.springframework.jmx.export.MBeanExporter" lazy-init="false">
    <property name="beans">
        <map>
                <entry key="bean:name=dH1" value-ref="dH" />
            </map>
        </property>
        <property name="server">
            <bean id="MBeanServer" class="java.lang.management.ManagementFactory" factory-method="getPlatformMBeanServer"/>
        </property>
</bean>
Share:
12,839

Related videos on Youtube

Monis Iqbal
Author by

Monis Iqbal

software enthusiast

Updated on June 04, 2022

Comments

  • Monis Iqbal
    Monis Iqbal almost 2 years

    Following various example configurations from Spring documentation as well as some forums on the Internet, my application context file looks like:

    <beans>
        <bean id="dH" class="abc.def.ghi.DH">
            <constructor-arg>
                <value>0</value>
            </constructor-arg>
            <property name="num" value="100"/>
        </bean>
        <bean class="org.springframework.jmx.export.MBeanExporter" lazy-init="false">
                <property name="beans">
                  <map>
                        <entry key="bean:name=dH1" value-ref="dH"/>
                  </map>
                </property>
        </bean>
        <bean class="org.springframework.jmx.support.MBeanServerFactoryBean"/>
    </beans>
    

    I'm running this without any container and on plain JVM. I'm able to connect to my process via JConsole but the MBean doesn't show up. However registering the bean programmatically exposes it successfully.

    MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
    DH dh = new DH(0);
    mbeanServer.registerMBean(dh, new ObjectName("bean:name=dH1"));
    

    I've tried playing with the Spring configuration without success. I think the bean is not registering to the already running MBean server that was accessible from ManagementFactory.getPlatformMBeanServer(). Any ideas on the issue?

  • Monis Iqbal
    Monis Iqbal about 12 years
    Thanks for the other suggestions @Nicholas. Though trying them both didn't register the MBeans as well.
  • Monis Iqbal
    Monis Iqbal about 12 years
    Thanks for digging this out @Dan. When Spring context is loaded or even the dh is created using the context the Introspector line is not even executed. However registering the bean manually does call the same line. I don't think Spring is trying to register the bean with these configurations. Something must be missing...
  • Go Dan
    Go Dan about 12 years
    This works for me using Spring 3.0.5 and a simple JavaBean with one property getter/setter; I can connect to my simple test app with VisualVM and see the test MBean that was registered using the MBeanExporter bean definition I provided. Try breaking your scenario down to a simple form, get that to work, then build it back up to what you currently have and see where your issue lies.
  • Monis Iqbal
    Monis Iqbal about 12 years
    Using Spring 3.1.0 I tried with your provided XML with and without -Dcom.sun.management.jmxremote as program option, still no luck. I'm loading the context in the following manner: BeanFactory factory = new XmlBeanFactory(new ClassPathResource("/application-context.xml")); and then loading the intended MBean as: factory.getBean(DH.class); I know this sounds naive but is there a way we can match our complete XMLs and simple program structure?
  • Monis Iqbal
    Monis Iqbal about 12 years
    Thanks a bunch @Dan. It was the way the beans were getting initialized :|. I was using BeanFactory whereas using the ApplicationContext did the trick.