I have more than 5 spring web application and all of them are utilizing another common library. This common library has its own MBeans. Because of mandatory unique objectName constraint, my applications could not be deployed on same server.
The way I am using MBeans are like this:
@ManagedResource(objectName = "com.org.city:name=City", description = "City related operations")
I would like to use same MBean class with different objectNames for all applications. What is the correct way to utilize it without duplicating my MBeans.
Thanks
I ran into the same issue, and built off of Cemo's solution. Here is a sample implementation.
context.xml
<!-- Set up jmx bean auto scanning -->
<!-- Note: we're not using <context:mbean-export /> because we need to provide our own naming strategy -->
<bean id="mbeanExporter" class="org.springframework.jmx.export.annotation.AnnotationMBeanExporter">
<property name="namingStrategy">
<bean class="com.foo.MultiAppMetadataNamingStrategy">
<property name="applicationName" value="${application.name}" />
</bean>
</property>
</bean>
MultiAppMetadataNamingStrategy.java
public class MultiAppMetadataNamingStrategy implements ObjectNamingStrategy, InitializingBean {
private String applicationName;
public MultiAppMetadataNamingStrategy() {
}
public MultiAppMetadataNamingStrategy(String applicationName) {
this.applicationName = Preconditions.checkNotNull(applicationName, "applicationName must not be null");
}
public void setApplicationName(String applicationName) {
this.applicationName = Preconditions.checkNotNull(applicationName, "applicationName must not be null");
}
@Override
public void afterPropertiesSet() throws Exception {
if (applicationName == null) {
throw new IllegalArgumentException("Property 'applicationName' is required");
}
}
@Override
public ObjectName getObjectName(Object managedBean, String beanKey) throws MalformedObjectNameException {
Class managedClass = AopUtils.getTargetClass(managedBean);
String domain = ClassUtils.getPackageName(managedClass);
Hashtable<String, String> properties = new Hashtable<>();
properties.put("type", ClassUtils.getShortName(managedClass));
properties.put("name", beanKey);
// ensure the application name is included as a property in the object name
properties.put("app", applicationName);
return ObjectNameManager.getInstance(domain, properties);
}
}
This allows setting up an mbean like:
package com.foo;
@ManagedResource(description = "Bean description")
public class MyBean {
...
}
which will register an mbean with object name com.foo:name=myBean,type=MyBean,app=CustomAppName
I have implemented ObjectNamingStrategy for custom behaviour.
@Override
public ObjectName getObjectName(Object managedBean, String beanKey) throws MalformedObjectNameException {
Class managedClass = AopUtils.getTargetClass(managedBean);
Hashtable<String, String> properties = new Hashtable<String, String>();
properties.put("type",ClassUtils.getPackageName(managedClass).concat(".").concat(ClassUtils.getShortName(managedClass)));
properties.put("name", beanKey);
return ObjectNameManager.getInstance(domain, properties);
}
You need to change the registering behavoiur of the mbean exporter:
<property name="registrationBehaviorName" value="REGISTRATION_REPLACE_EXISTING"/>
But this will still mean only one application registers the bean. And you can't logically have more than one mbean with the same name from multiple applications. How will be determined which application to invoke? Use the application name as a prefix to the mbean name.
You can define a simple naming strategy based on properties using place holder(s).
Every war will have it;s own copy of the app.properties
E.g.
With a properties file :app.properties
appName=MyApp1 #Every app will have it own value e.g,MyApp2,MyApp3,MyApp4,MyApp5
and a PropertiesPlaceHolder
<bean id="placeholderConfig" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="placeholderPrefix" value="$app{" />
<property name="location" value="classpath:app.properties"/>
</bean>
Defining objectName
@ManagedResource(objectName=com.mycompany:name=$app{appName}-MyBean")
public class MyBean {}
Your bean will be named
com.mycompany
+MyApp1-MyBean
+MyApp2-MyBean
+MyApp3-MyBean
+MyApp4-MyBean
+MyApp5-MyBean
You can use more the one property place holder.
Works with Spring 4.0.2
These answers helped point me in the right direction, but there were a few pieces missing for an Annotation based setup (I'm not using Spring Boot however)
The Spring Docs on this subject say:
If you prefer using the annotation based approach to define your management interfaces, then a convenience subclass of MBeanExporter is available: AnnotationMBeanExporter. When defining an instance of this subclass, the namingStrategy, assembler, and attributeSource configuration is no longer needed, since it will always use standard Java annotation-based metadata (autodetection is always enabled as well). In fact, rather than defining an MBeanExporter bean, an even simpler syntax is supported by the @EnableMBeanExport @Configuration annotation.
But using @EnableMBeanExport
prevents you from having the ability of defining your own NamingStrategy
So Instead of just setup a @Bean
method that returns my MBeanExporter
with a custom naming strategy that uses the context path.
@Configuration
public class JmxUtil {
@Value("#{servletContext.contextPath}")
private String contextPath;
private String domain = "foo.bar";
@Bean
public MBeanExporter mbeanExporter() {
AnnotationMBeanExporter exporter = new AnnotationMBeanExporter();
exporter.setNamingStrategy((managedBean, beanKey) -> {
return ObjectNameManager.getInstance(domain, new Hashtable<>(ImmutableMap.of(
"name", beanKey,
"instance", contextPath
)));
});
exporter.setDefaultDomain(domain);
return exporter;
}
}
来源:https://stackoverflow.com/questions/7075007/same-jmx-mbean-class-for-many-application-at-same-server