javaspringjmxlazy-initializationspring-jmx

Exporting a lazily initialized bean (which implements SelfNaming and is annotated with ManagedResource annotation) gives IllegalStateException


I'm having a bean that is

I'm exporting it using spring's AnnotationMBeanExporter.

All this works good when I'm using spring version 4.3.16.RELEASE, but when I upgraded my spring version to 5.0.5.RELEASE or 5.1.3.RELEASE this code started giving me IllegalStateException.

My Bean definition and the spring's context.xml looks like this:

SampleBean.java:

package com.jmx.trial.dummybeans;

import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.jmx.export.naming.SelfNaming;

import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;

@ManagedResource
public class SampleBean implements SelfNaming {
    @Override
    public ObjectName getObjectName() throws MalformedObjectNameException {
        return new ObjectName("com.jmx.trial:name=sampleBean");
    }
}

application-context.xml

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

    <bean id="server" class="org.springframework.jmx.support.MBeanServerFactoryBean"/>

    <bean id="exporter" class="org.springframework.jmx.export.annotation.AnnotationMBeanExporter">
        <property name="server" ref="server"/>
    </bean>

    <bean id="sampleBean" class="com.jmx.trial.dummybeans.SampleBean" lazy-init="true"/>

</beans>

I understand that an extra validation was added here, and this is causing IllegalStateException but I'm not completely sure why that was added.

The stack-trace looks like this:

org.springframework.jmx.export.UnableToRegisterMBeanException: Unable to register MBean [sampleBean] with key 'sampleBean'; nested exception is java.lang.IllegalStateException: Not initialized

    at org.springframework.jmx.export.MBeanExporter.registerBeanNameOrInstance(MBeanExporter.java:625)
    at org.springframework.jmx.export.MBeanExporter.lambda$registerBeans$2(MBeanExporter.java:551)
    at java.base/java.util.HashMap.forEach(HashMap.java:1336)
    at org.springframework.jmx.export.MBeanExporter.registerBeans(MBeanExporter.java:551)
    at org.springframework.jmx.export.MBeanExporter.afterSingletonsInstantiated(MBeanExporter.java:434)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:863)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:863)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:546)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:144)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:85)
    at com.jmx.trial.MBeanExporterTest.testBeanExportedWithXml(MBeanExporterTest.java:79)
    at com.jmx.trial.MBeanExporterTest.testForLazyAutoDetectWithSelfNaming(MBeanExporterTest.java:44)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: java.lang.IllegalStateException: Not initialized
    at org.springframework.util.Assert.state(Assert.java:73)
    at org.springframework.jmx.export.MBeanExporter$NotificationPublisherAwareLazyTargetSource.postProcessTargetObject(MBeanExporter.java:1115)
    at org.springframework.aop.target.LazyInitTargetSource.getTarget(LazyInitTargetSource.java:72)
    at org.springframework.jmx.export.MBeanExporter$NotificationPublisherAwareLazyTargetSource.getTarget(MBeanExporter.java:1103)
    at org.springframework.aop.framework.CglibAopProxy$DynamicUnadvisedInterceptor.intercept(CglibAopProxy.java:475)
    at com.jmx.trial.dummybeans.SampleBean$$EnhancerBySpringCGLIB$$9cd1c95b.getObjectName(<generated>)
    at org.springframework.jmx.export.MBeanExporter.getObjectName(MBeanExporter.java:752)
    at org.springframework.jmx.export.MBeanExporter.registerLazyInit(MBeanExporter.java:726)
    at org.springframework.jmx.export.MBeanExporter.registerBeanNameOrInstance(MBeanExporter.java:596)
    ... 33 more

My experiments:

Though not very sure, but I think implementing SelfNaming interface is not a very good idea as the javadocs of SelfNaming interface says that:

This interface is mainly intended for internal usage.

I'm not sure if it is the problem with using SelfNaming interface or I'm doing something fundamentally wrong. Can you please explain this behavior and point me to the fundamentals that I'm missing.

PS: some of my findings: (might be unrelated) From here I found this caution point:

Do not use interface-based AOP proxies in combination with autodetection of JMX annotations in your bean classes.

I don't completely understand this, but is this the rule that I'm violating?


Solution

  • It's possibly a bug; injectNotificationPublisherIfNecessary only takes any action if your class also implements NotificationPublisherAware.

    This would be invalid since we don't have an objectName, yet, because the bean is lazy and this would violate the contract for ModelMBeanNotificationPublisher.

    I don't know if postProcessTargetObject can ignore the assert if the resource doesn't implement the publisher aware interface, or whether the publisher injection needs to be made lazily too. I don't know the internals well enough.

    I suggest you open an SPR JIRA issue so the Spring Team can take a look and give you a definitive answer.