/*
 * Copyright 2002-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.jmx.export;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.management.DynamicMBean;
import javax.management.JMException;
import javax.management.MBeanException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import javax.management.StandardMBean;
import javax.management.modelmbean.ModelMBean;
import javax.management.modelmbean.ModelMBeanInfo;
import javax.management.modelmbean.RequiredModelMBean;

import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.scope.ScopedProxyUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.aop.target.LazyInitTargetSource;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.CannotLoadBeanClassException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.Constants;
import org.springframework.jmx.export.assembler.AutodetectCapableMBeanInfoAssembler;
import org.springframework.jmx.export.assembler.MBeanInfoAssembler;
import org.springframework.jmx.export.assembler.SimpleReflectiveMBeanInfoAssembler;
import org.springframework.jmx.export.naming.KeyNamingStrategy;
import org.springframework.jmx.export.naming.ObjectNamingStrategy;
import org.springframework.jmx.export.naming.SelfNaming;
import org.springframework.jmx.export.notification.ModelMBeanNotificationPublisher;
import org.springframework.jmx.export.notification.NotificationPublisherAware;
import org.springframework.jmx.support.JmxUtils;
import org.springframework.jmx.support.MBeanRegistrationSupport;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

JMX exporter that allows for exposing any Spring-managed bean to a JMX MBeanServer, without the need to define any JMX-specific information in the bean classes.

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.

A list of MBeanExporterListeners can be registered via the listeners property, allowing application code to be notified of MBean registration and unregistration events.

This exporter is compatible with MBeans as well as MXBeans.

Author:Rob Harrop, Juergen Hoeller, Rick Evans, Mark Fisher, Stephane Nicoll
See Also:
Since:1.2
/** * JMX exporter that allows for exposing any <i>Spring-managed bean</i> to a * JMX {@link javax.management.MBeanServer}, without the need to define any * JMX-specific information in the bean classes. * * <p>If a bean implements one of the JMX management interfaces, MBeanExporter can * simply register the MBean with the server through its autodetection process. * * <p>If a bean does not implement one of the JMX management interfaces, MBeanExporter * will create the management information using the supplied {@link MBeanInfoAssembler}. * * <p>A list of {@link MBeanExporterListener MBeanExporterListeners} can be registered * via the {@link #setListeners(MBeanExporterListener[]) listeners} property, allowing * application code to be notified of MBean registration and unregistration events. * * <p>This exporter is compatible with MBeans as well as MXBeans. * * @author Rob Harrop * @author Juergen Hoeller * @author Rick Evans * @author Mark Fisher * @author Stephane Nicoll * @since 1.2 * @see #setBeans * @see #setAutodetect * @see #setAssembler * @see #setListeners * @see org.springframework.jmx.export.assembler.MBeanInfoAssembler * @see MBeanExporterListener */
public class MBeanExporter extends MBeanRegistrationSupport implements MBeanExportOperations, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, SmartInitializingSingleton, DisposableBean {
Autodetection mode indicating that no autodetection should be used.
/** * Autodetection mode indicating that no autodetection should be used. */
public static final int AUTODETECT_NONE = 0;
Autodetection mode indicating that only valid MBeans should be autodetected.
/** * Autodetection mode indicating that only valid MBeans should be autodetected. */
public static final int AUTODETECT_MBEAN = 1;
Autodetection mode indicating that only the MBeanInfoAssembler should be able to autodetect beans.
/** * Autodetection mode indicating that only the {@link MBeanInfoAssembler} should be able * to autodetect beans. */
public static final int AUTODETECT_ASSEMBLER = 2;
Autodetection mode indicating that all autodetection mechanisms should be used.
/** * Autodetection mode indicating that all autodetection mechanisms should be used. */
public static final int AUTODETECT_ALL = AUTODETECT_MBEAN | AUTODETECT_ASSEMBLER;
Wildcard used to map a NotificationListener to all MBeans registered by the MBeanExporter.
/** * Wildcard used to map a {@link javax.management.NotificationListener} * to all MBeans registered by the {@code MBeanExporter}. */
private static final String WILDCARD = "*";
Constant for the JMX mr_type "ObjectReference".
/** Constant for the JMX {@code mr_type} "ObjectReference". */
private static final String MR_TYPE_OBJECT_REFERENCE = "ObjectReference";
Prefix for the autodetect constants defined in this class.
/** Prefix for the autodetect constants defined in this class. */
private static final String CONSTANT_PREFIX_AUTODETECT = "AUTODETECT_";
Constants instance for this class.
/** Constants instance for this class. */
private static final Constants constants = new Constants(MBeanExporter.class);
The beans to be exposed as JMX managed resources, with JMX names as keys.
/** The beans to be exposed as JMX managed resources, with JMX names as keys. */
@Nullable private Map<String, Object> beans;
The autodetect mode to use for this MBeanExporter.
/** The autodetect mode to use for this MBeanExporter. */
@Nullable private Integer autodetectMode;
Whether to eagerly initialize candidate beans when autodetecting MBeans.
/** Whether to eagerly initialize candidate beans when autodetecting MBeans. */
private boolean allowEagerInit = false;
Stores the MBeanInfoAssembler to use for this exporter.
/** Stores the MBeanInfoAssembler to use for this exporter. */
private MBeanInfoAssembler assembler = new SimpleReflectiveMBeanInfoAssembler();
The strategy to use for creating ObjectNames for an object.
/** The strategy to use for creating ObjectNames for an object. */
private ObjectNamingStrategy namingStrategy = new KeyNamingStrategy();
Indicates whether Spring should modify generated ObjectNames.
/** Indicates whether Spring should modify generated ObjectNames. */
private boolean ensureUniqueRuntimeObjectNames = true;
Indicates whether Spring should expose the managed resource ClassLoader in the MBean.
/** Indicates whether Spring should expose the managed resource ClassLoader in the MBean. */
private boolean exposeManagedResourceClassLoader = true;
A set of bean names that should be excluded from autodetection.
/** A set of bean names that should be excluded from autodetection. */
private Set<String> excludedBeans = new HashSet<>();
The MBeanExporterListeners registered with this exporter.
/** The MBeanExporterListeners registered with this exporter. */
@Nullable private MBeanExporterListener[] listeners;
The NotificationListeners to register for the MBeans registered by this exporter.
/** The NotificationListeners to register for the MBeans registered by this exporter. */
@Nullable private NotificationListenerBean[] notificationListeners;
Map of actually registered NotificationListeners.
/** Map of actually registered NotificationListeners. */
private final Map<NotificationListenerBean, ObjectName[]> registeredNotificationListeners = new LinkedHashMap<>();
Stores the ClassLoader to use for generating lazy-init proxies.
/** Stores the ClassLoader to use for generating lazy-init proxies. */
@Nullable private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
Stores the BeanFactory for use in autodetection process.
/** Stores the BeanFactory for use in autodetection process. */
@Nullable private ListableBeanFactory beanFactory;
Supply a Map of beans to be registered with the JMX MBeanServer.

The String keys are the basis for the creation of JMX object names. By default, a JMX ObjectName will be created straight from the given key. This can be customized through specifying a custom NamingStrategy.

Both bean instances and bean names are allowed as values. Bean instances are typically linked in through bean references. Bean names will be resolved as beans in the current factory, respecting lazy-init markers (that is, not triggering initialization of such beans).

Params:
  • beans – a Map with JMX names as keys and bean instances or bean names as values
See Also:
/** * Supply a {@code Map} of beans to be registered with the JMX * {@code MBeanServer}. * <p>The String keys are the basis for the creation of JMX object names. * By default, a JMX {@code ObjectName} will be created straight * from the given key. This can be customized through specifying a * custom {@code NamingStrategy}. * <p>Both bean instances and bean names are allowed as values. * Bean instances are typically linked in through bean references. * Bean names will be resolved as beans in the current factory, respecting * lazy-init markers (that is, not triggering initialization of such beans). * @param beans a Map with JMX names as keys and bean instances or bean names * as values * @see #setNamingStrategy * @see org.springframework.jmx.export.naming.KeyNamingStrategy * @see javax.management.ObjectName#ObjectName(String) */
public void setBeans(Map<String, Object> beans) { this.beans = beans; }
Set whether to autodetect MBeans in the bean factory that this exporter runs in. Will also ask an AutodetectCapableMBeanInfoAssembler if available.

This feature is turned off by default. Explicitly specify true here to enable autodetection.

See Also:
/** * Set whether to autodetect MBeans in the bean factory that this exporter * runs in. Will also ask an {@code AutodetectCapableMBeanInfoAssembler} * if available. * <p>This feature is turned off by default. Explicitly specify * {@code true} here to enable autodetection. * @see #setAssembler * @see AutodetectCapableMBeanInfoAssembler * @see #isMBean */
public void setAutodetect(boolean autodetect) { this.autodetectMode = (autodetect ? AUTODETECT_ALL : AUTODETECT_NONE); }
Set the autodetection mode to use.
Throws:
  • IllegalArgumentException – if the supplied value is not one of the AUTODETECT_ constants
See Also:
/** * Set the autodetection mode to use. * @throws IllegalArgumentException if the supplied value is not * one of the {@code AUTODETECT_} constants * @see #setAutodetectModeName(String) * @see #AUTODETECT_ALL * @see #AUTODETECT_ASSEMBLER * @see #AUTODETECT_MBEAN * @see #AUTODETECT_NONE */
public void setAutodetectMode(int autodetectMode) { if (!constants.getValues(CONSTANT_PREFIX_AUTODETECT).contains(autodetectMode)) { throw new IllegalArgumentException("Only values of autodetect constants allowed"); } this.autodetectMode = autodetectMode; }
Set the autodetection mode to use by name.
Throws:
  • IllegalArgumentException – if the supplied value is not resolvable to one of the AUTODETECT_ constants or is null
See Also:
/** * Set the autodetection mode to use by name. * @throws IllegalArgumentException if the supplied value is not resolvable * to one of the {@code AUTODETECT_} constants or is {@code null} * @see #setAutodetectMode(int) * @see #AUTODETECT_ALL * @see #AUTODETECT_ASSEMBLER * @see #AUTODETECT_MBEAN * @see #AUTODETECT_NONE */
public void setAutodetectModeName(String constantName) { if (!constantName.startsWith(CONSTANT_PREFIX_AUTODETECT)) { throw new IllegalArgumentException("Only autodetect constants allowed"); } this.autodetectMode = (Integer) constants.asNumber(constantName); }
Specify whether to allow eager initialization of candidate beans when autodetecting MBeans in the Spring application context.

Default is "false", respecting lazy-init flags on bean definitions. Switch this to "true" in order to search lazy-init beans as well, including FactoryBean-produced objects that haven't been initialized yet.

/** * Specify whether to allow eager initialization of candidate beans * when autodetecting MBeans in the Spring application context. * <p>Default is "false", respecting lazy-init flags on bean definitions. * Switch this to "true" in order to search lazy-init beans as well, * including FactoryBean-produced objects that haven't been initialized yet. */
public void setAllowEagerInit(boolean allowEagerInit) { this.allowEagerInit = allowEagerInit; }
Set the implementation of the MBeanInfoAssembler interface to use for this exporter. Default is a SimpleReflectiveMBeanInfoAssembler.

The passed-in assembler can optionally implement the AutodetectCapableMBeanInfoAssembler interface, which enables it to participate in the exporter's MBean autodetection process.

See Also:
/** * Set the implementation of the {@code MBeanInfoAssembler} interface to use * for this exporter. Default is a {@code SimpleReflectiveMBeanInfoAssembler}. * <p>The passed-in assembler can optionally implement the * {@code AutodetectCapableMBeanInfoAssembler} interface, which enables it * to participate in the exporter's MBean autodetection process. * @see org.springframework.jmx.export.assembler.SimpleReflectiveMBeanInfoAssembler * @see org.springframework.jmx.export.assembler.AutodetectCapableMBeanInfoAssembler * @see org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler * @see #setAutodetect */
public void setAssembler(MBeanInfoAssembler assembler) { this.assembler = assembler; }
Set the implementation of the ObjectNamingStrategy interface to use for this exporter. Default is a KeyNamingStrategy.
See Also:
/** * Set the implementation of the {@code ObjectNamingStrategy} interface * to use for this exporter. Default is a {@code KeyNamingStrategy}. * @see org.springframework.jmx.export.naming.KeyNamingStrategy * @see org.springframework.jmx.export.naming.MetadataNamingStrategy */
public void setNamingStrategy(ObjectNamingStrategy namingStrategy) { this.namingStrategy = namingStrategy; }
Indicates whether Spring should ensure that ObjectNames generated by the configured ObjectNamingStrategy for runtime-registered MBeans (registerManagedResource) should get modified: to ensure uniqueness for every instance of a managed Class.

The default value is true.

See Also:
/** * Indicates whether Spring should ensure that {@link ObjectName ObjectNames} * generated by the configured {@link ObjectNamingStrategy} for * runtime-registered MBeans ({@link #registerManagedResource}) should get * modified: to ensure uniqueness for every instance of a managed {@code Class}. * <p>The default value is {@code true}. * @see #registerManagedResource * @see JmxUtils#appendIdentityToObjectName(javax.management.ObjectName, Object) */
public void setEnsureUniqueRuntimeObjectNames(boolean ensureUniqueRuntimeObjectNames) { this.ensureUniqueRuntimeObjectNames = ensureUniqueRuntimeObjectNames; }
Indicates whether or not the managed resource should be exposed on the thread context ClassLoader before allowing any invocations on the MBean to occur.

The default value is true, exposing a SpringModelMBean which performs thread context ClassLoader management. Switch this flag off to expose a standard JMX RequiredModelMBean.

/** * Indicates whether or not the managed resource should be exposed on the * {@link Thread#getContextClassLoader() thread context ClassLoader} before * allowing any invocations on the MBean to occur. * <p>The default value is {@code true}, exposing a {@link SpringModelMBean} * which performs thread context ClassLoader management. Switch this flag off to * expose a standard JMX {@link javax.management.modelmbean.RequiredModelMBean}. */
public void setExposeManagedResourceClassLoader(boolean exposeManagedResourceClassLoader) { this.exposeManagedResourceClassLoader = exposeManagedResourceClassLoader; }
Set the list of names for beans that should be excluded from autodetection.
/** * Set the list of names for beans that should be excluded from autodetection. */
public void setExcludedBeans(String... excludedBeans) { this.excludedBeans.clear(); Collections.addAll(this.excludedBeans, excludedBeans); }
Add the name of bean that should be excluded from autodetection.
/** * Add the name of bean that should be excluded from autodetection. */
public void addExcludedBean(String excludedBean) { Assert.notNull(excludedBean, "ExcludedBean must not be null"); this.excludedBeans.add(excludedBean); }
Set the MBeanExporterListeners that should be notified of MBean registration and unregistration events.
See Also:
/** * Set the {@code MBeanExporterListener}s that should be notified * of MBean registration and unregistration events. * @see MBeanExporterListener */
public void setListeners(MBeanExporterListener... listeners) { this.listeners = listeners; }
Set the NotificationListenerBeans containing the NotificationListeners that will be registered with the MBeanServer.
See Also:
/** * Set the {@link NotificationListenerBean NotificationListenerBeans} * containing the * {@link javax.management.NotificationListener NotificationListeners} * that will be registered with the {@link MBeanServer}. * @see #setNotificationListenerMappings(java.util.Map) * @see NotificationListenerBean */
public void setNotificationListeners(NotificationListenerBean... notificationListeners) { this.notificationListeners = notificationListeners; }
Set the NotificationListeners to register with the MBeanServer.

The key of each entry in the Map is a String representation of the ObjectName or the bean name of the MBean the listener should be registered for. Specifying an asterisk (*) for a key will cause the listener to be associated with all MBeans registered by this class at startup time.

The value of each entry is the NotificationListener to register. For more advanced options such as registering NotificationFilters and handback objects see setNotificationListeners(NotificationListenerBean[]).

/** * Set the {@link NotificationListener NotificationListeners} to register * with the {@link javax.management.MBeanServer}. * <P>The key of each entry in the {@code Map} is a {@link String} * representation of the {@link javax.management.ObjectName} or the bean * name of the MBean the listener should be registered for. Specifying an * asterisk ({@code *}) for a key will cause the listener to be * associated with all MBeans registered by this class at startup time. * <p>The value of each entry is the * {@link javax.management.NotificationListener} to register. For more * advanced options such as registering * {@link javax.management.NotificationFilter NotificationFilters} and * handback objects see {@link #setNotificationListeners(NotificationListenerBean[])}. */
public void setNotificationListenerMappings(Map<?, ? extends NotificationListener> listeners) { Assert.notNull(listeners, "'listeners' must not be null"); List<NotificationListenerBean> notificationListeners = new ArrayList<>(listeners.size()); listeners.forEach((key, listener) -> { // Get the listener from the map value. NotificationListenerBean bean = new NotificationListenerBean(listener); // Get the ObjectName from the map key. if (key != null && !WILDCARD.equals(key)) { // This listener is mapped to a specific ObjectName. bean.setMappedObjectName(key); } notificationListeners.add(bean); }); this.notificationListeners = notificationListeners.toArray(new NotificationListenerBean[0]); } @Override public void setBeanClassLoader(ClassLoader classLoader) { this.beanClassLoader = classLoader; }
This callback is only required for resolution of bean names in the "beans" Map and for autodetection of MBeans (in the latter case, a ListableBeanFactory is required).
See Also:
/** * This callback is only required for resolution of bean names in the * {@link #setBeans(java.util.Map) "beans"} {@link Map} and for * autodetection of MBeans (in the latter case, a * {@code ListableBeanFactory} is required). * @see #setBeans * @see #setAutodetect */
@Override public void setBeanFactory(BeanFactory beanFactory) { if (beanFactory instanceof ListableBeanFactory) { this.beanFactory = (ListableBeanFactory) beanFactory; } else { logger.debug("MBeanExporter not running in a ListableBeanFactory: autodetection of MBeans not available."); } } //--------------------------------------------------------------------- // Lifecycle in bean factory: automatically register/unregister beans //--------------------------------------------------------------------- @Override public void afterPropertiesSet() { // If no server was provided then try to find one. This is useful in an environment // where there is already an MBeanServer loaded. if (this.server == null) { this.server = JmxUtils.locateMBeanServer(); } }
Kick off bean registration automatically after the regular singleton instantiation phase.
See Also:
  • registerBeans()
/** * Kick off bean registration automatically after the regular singleton instantiation phase. * @see #registerBeans() */
@Override public void afterSingletonsInstantiated() { try { logger.debug("Registering beans for JMX exposure on startup"); registerBeans(); registerNotificationListeners(); } catch (RuntimeException ex) { // Unregister beans already registered by this exporter. unregisterNotificationListeners(); unregisterBeans(); throw ex; } }
Unregisters all beans that this exported has exposed via JMX when the enclosing ApplicationContext is destroyed.
/** * Unregisters all beans that this exported has exposed via JMX * when the enclosing {@code ApplicationContext} is destroyed. */
@Override public void destroy() { logger.debug("Unregistering JMX-exposed beans on shutdown"); unregisterNotificationListeners(); unregisterBeans(); } //--------------------------------------------------------------------- // Implementation of MBeanExportOperations interface //--------------------------------------------------------------------- @Override public ObjectName registerManagedResource(Object managedResource) throws MBeanExportException { Assert.notNull(managedResource, "Managed resource must not be null"); ObjectName objectName; try { objectName = getObjectName(managedResource, null); if (this.ensureUniqueRuntimeObjectNames) { objectName = JmxUtils.appendIdentityToObjectName(objectName, managedResource); } } catch (Throwable ex) { throw new MBeanExportException("Unable to generate ObjectName for MBean [" + managedResource + "]", ex); } registerManagedResource(managedResource, objectName); return objectName; } @Override public void registerManagedResource(Object managedResource, ObjectName objectName) throws MBeanExportException { Assert.notNull(managedResource, "Managed resource must not be null"); Assert.notNull(objectName, "ObjectName must not be null"); try { if (isMBean(managedResource.getClass())) { doRegister(managedResource, objectName); } else { ModelMBean mbean = createAndConfigureMBean(managedResource, managedResource.getClass().getName()); doRegister(mbean, objectName); injectNotificationPublisherIfNecessary(managedResource, mbean, objectName); } } catch (JMException ex) { throw new UnableToRegisterMBeanException( "Unable to register MBean [" + managedResource + "] with object name [" + objectName + "]", ex); } } @Override public void unregisterManagedResource(ObjectName objectName) { Assert.notNull(objectName, "ObjectName must not be null"); doUnregister(objectName); } //--------------------------------------------------------------------- // Exporter implementation //---------------------------------------------------------------------
Register the defined beans with the MBeanServer.

Each bean is exposed to the MBeanServer via a ModelMBean. The actual implementation of the ModelMBean interface used depends on the implementation of the ModelMBeanProvider interface that is configured. By default the RequiredModelMBean class that is supplied with all JMX implementations is used.

The management interface produced for each bean is dependent on the MBeanInfoAssembler implementation being used. The ObjectName given to each bean is dependent on the implementation of the ObjectNamingStrategy interface being used.

/** * Register the defined beans with the {@link MBeanServer}. * <p>Each bean is exposed to the {@code MBeanServer} via a * {@code ModelMBean}. The actual implementation of the * {@code ModelMBean} interface used depends on the implementation of * the {@code ModelMBeanProvider} interface that is configured. By * default the {@code RequiredModelMBean} class that is supplied with * all JMX implementations is used. * <p>The management interface produced for each bean is dependent on the * {@code MBeanInfoAssembler} implementation being used. The * {@code ObjectName} given to each bean is dependent on the * implementation of the {@code ObjectNamingStrategy} interface being used. */
protected void registerBeans() { // The beans property may be null, for example if we are relying solely on autodetection. if (this.beans == null) { this.beans = new HashMap<>(); // Use AUTODETECT_ALL as default in no beans specified explicitly. if (this.autodetectMode == null) { this.autodetectMode = AUTODETECT_ALL; } } // Perform autodetection, if desired. int mode = (this.autodetectMode != null ? this.autodetectMode : AUTODETECT_NONE); if (mode != AUTODETECT_NONE) { if (this.beanFactory == null) { throw new MBeanExportException("Cannot autodetect MBeans if not running in a BeanFactory"); } if (mode == AUTODETECT_MBEAN || mode == AUTODETECT_ALL) { // Autodetect any beans that are already MBeans. logger.debug("Autodetecting user-defined JMX MBeans"); autodetect(this.beans, (beanClass, beanName) -> isMBean(beanClass)); } // Allow the assembler a chance to vote for bean inclusion. if ((mode == AUTODETECT_ASSEMBLER || mode == AUTODETECT_ALL) && this.assembler instanceof AutodetectCapableMBeanInfoAssembler) { autodetect(this.beans, ((AutodetectCapableMBeanInfoAssembler) this.assembler)::includeBean); } } if (!this.beans.isEmpty()) { this.beans.forEach((beanName, instance) -> registerBeanNameOrInstance(instance, beanName)); } }
Return whether the specified bean definition should be considered as lazy-init.
Params:
  • beanFactory – the bean factory that is supposed to contain the bean definition
  • beanName – the name of the bean to check
See Also:
/** * Return whether the specified bean definition should be considered as lazy-init. * @param beanFactory the bean factory that is supposed to contain the bean definition * @param beanName the name of the bean to check * @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory#getBeanDefinition * @see org.springframework.beans.factory.config.BeanDefinition#isLazyInit */
protected boolean isBeanDefinitionLazyInit(ListableBeanFactory beanFactory, String beanName) { return (beanFactory instanceof ConfigurableListableBeanFactory && beanFactory.containsBeanDefinition(beanName) && ((ConfigurableListableBeanFactory) beanFactory).getBeanDefinition(beanName).isLazyInit()); }
Register an individual bean with the MBeanServer.

This method is responsible for deciding how a bean should be exposed to the MBeanServer. Specifically, if the supplied mapValue is the name of a bean that is configured for lazy initialization, then a proxy to the resource is registered with the MBeanServer so that the lazy load behavior is honored. If the bean is already an MBean then it will be registered directly with the MBeanServer without any intervention. For all other beans or bean names, the resource itself is registered with the MBeanServer directly.

Params:
  • mapValue – the value configured for this bean in the beans map; may be either the String name of a bean, or the bean itself
  • beanKey – the key associated with this bean in the beans map
Throws:
See Also:
Returns:the ObjectName under which the resource was registered
/** * Register an individual bean with the {@link #setServer MBeanServer}. * <p>This method is responsible for deciding <strong>how</strong> a bean * should be exposed to the {@code MBeanServer}. Specifically, if the * supplied {@code mapValue} is the name of a bean that is configured * for lazy initialization, then a proxy to the resource is registered with * the {@code MBeanServer} so that the lazy load behavior is * honored. If the bean is already an MBean then it will be registered * directly with the {@code MBeanServer} without any intervention. For * all other beans or bean names, the resource itself is registered with * the {@code MBeanServer} directly. * @param mapValue the value configured for this bean in the beans map; * may be either the {@code String} name of a bean, or the bean itself * @param beanKey the key associated with this bean in the beans map * @return the {@code ObjectName} under which the resource was registered * @throws MBeanExportException if the export failed * @see #setBeans * @see #registerBeanInstance * @see #registerLazyInit */
protected ObjectName registerBeanNameOrInstance(Object mapValue, String beanKey) throws MBeanExportException { try { if (mapValue instanceof String) { // Bean name pointing to a potentially lazy-init bean in the factory. if (this.beanFactory == null) { throw new MBeanExportException("Cannot resolve bean names if not running in a BeanFactory"); } String beanName = (String) mapValue; if (isBeanDefinitionLazyInit(this.beanFactory, beanName)) { ObjectName objectName = registerLazyInit(beanName, beanKey); replaceNotificationListenerBeanNameKeysIfNecessary(beanName, objectName); return objectName; } else { Object bean = this.beanFactory.getBean(beanName); ObjectName objectName = registerBeanInstance(bean, beanKey); replaceNotificationListenerBeanNameKeysIfNecessary(beanName, objectName); return objectName; } } else { // Plain bean instance -> register it directly. if (this.beanFactory != null) { Map<String, ?> beansOfSameType = this.beanFactory.getBeansOfType(mapValue.getClass(), false, this.allowEagerInit); for (Map.Entry<String, ?> entry : beansOfSameType.entrySet()) { if (entry.getValue() == mapValue) { String beanName = entry.getKey(); ObjectName objectName = registerBeanInstance(mapValue, beanKey); replaceNotificationListenerBeanNameKeysIfNecessary(beanName, objectName); return objectName; } } } return registerBeanInstance(mapValue, beanKey); } } catch (Throwable ex) { throw new UnableToRegisterMBeanException( "Unable to register MBean [" + mapValue + "] with key '" + beanKey + "'", ex); } }
Replace any bean names used as keys in the NotificationListener mappings with their corresponding ObjectName values.
Params:
  • beanName – the name of the bean to be registered
  • objectName – the ObjectName under which the bean will be registered with the MBeanServer
/** * Replace any bean names used as keys in the {@code NotificationListener} * mappings with their corresponding {@code ObjectName} values. * @param beanName the name of the bean to be registered * @param objectName the {@code ObjectName} under which the bean will be registered * with the {@code MBeanServer} */
private void replaceNotificationListenerBeanNameKeysIfNecessary(String beanName, ObjectName objectName) { if (this.notificationListeners != null) { for (NotificationListenerBean notificationListener : this.notificationListeners) { notificationListener.replaceObjectName(beanName, objectName); } } }
Registers an existing MBean or an MBean adapter for a plain bean with the MBeanServer.
Params:
  • bean – the bean to register, either an MBean or a plain bean
  • beanKey – the key associated with this bean in the beans map
Returns:the ObjectName under which the bean was registered with the MBeanServer
/** * Registers an existing MBean or an MBean adapter for a plain bean * with the {@code MBeanServer}. * @param bean the bean to register, either an MBean or a plain bean * @param beanKey the key associated with this bean in the beans map * @return the {@code ObjectName} under which the bean was registered * with the {@code MBeanServer} */
private ObjectName registerBeanInstance(Object bean, String beanKey) throws JMException { ObjectName objectName = getObjectName(bean, beanKey); Object mbeanToExpose = null; if (isMBean(bean.getClass())) { mbeanToExpose = bean; } else { DynamicMBean adaptedBean = adaptMBeanIfPossible(bean); if (adaptedBean != null) { mbeanToExpose = adaptedBean; } } if (mbeanToExpose != null) { if (logger.isDebugEnabled()) { logger.debug("Located MBean '" + beanKey + "': registering with JMX server as MBean [" + objectName + "]"); } doRegister(mbeanToExpose, objectName); } else { if (logger.isDebugEnabled()) { logger.debug("Located managed bean '" + beanKey + "': registering with JMX server as MBean [" + objectName + "]"); } ModelMBean mbean = createAndConfigureMBean(bean, beanKey); doRegister(mbean, objectName); injectNotificationPublisherIfNecessary(bean, mbean, objectName); } return objectName; }
Register beans that are configured for lazy initialization with the MBeanServer indirectly through a proxy.
Params:
  • beanName – the name of the bean in the BeanFactory
  • beanKey – the key associated with this bean in the beans map
Returns:the ObjectName under which the bean was registered with the MBeanServer
/** * Register beans that are configured for lazy initialization with the * {@code MBeanServer} indirectly through a proxy. * @param beanName the name of the bean in the {@code BeanFactory} * @param beanKey the key associated with this bean in the beans map * @return the {@code ObjectName} under which the bean was registered * with the {@code MBeanServer} */
private ObjectName registerLazyInit(String beanName, String beanKey) throws JMException { Assert.state(this.beanFactory != null, "No BeanFactory set"); ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setProxyTargetClass(true); proxyFactory.setFrozen(true); if (isMBean(this.beanFactory.getType(beanName))) { // A straight MBean... Let's create a simple lazy-init CGLIB proxy for it. LazyInitTargetSource targetSource = new LazyInitTargetSource(); targetSource.setTargetBeanName(beanName); targetSource.setBeanFactory(this.beanFactory); proxyFactory.setTargetSource(targetSource); Object proxy = proxyFactory.getProxy(this.beanClassLoader); ObjectName objectName = getObjectName(proxy, beanKey); if (logger.isDebugEnabled()) { logger.debug("Located MBean '" + beanKey + "': registering with JMX server as lazy-init MBean [" + objectName + "]"); } doRegister(proxy, objectName); return objectName; } else { // A simple bean... Let's create a lazy-init ModelMBean proxy with notification support. NotificationPublisherAwareLazyTargetSource targetSource = new NotificationPublisherAwareLazyTargetSource(); targetSource.setTargetBeanName(beanName); targetSource.setBeanFactory(this.beanFactory); proxyFactory.setTargetSource(targetSource); Object proxy = proxyFactory.getProxy(this.beanClassLoader); ObjectName objectName = getObjectName(proxy, beanKey); if (logger.isDebugEnabled()) { logger.debug("Located simple bean '" + beanKey + "': registering with JMX server as lazy-init MBean [" + objectName + "]"); } ModelMBean mbean = createAndConfigureMBean(proxy, beanKey); targetSource.setModelMBean(mbean); targetSource.setObjectName(objectName); doRegister(mbean, objectName); return objectName; } }
Retrieve the ObjectName for a bean.

If the bean implements the SelfNaming interface, then the ObjectName will be retrieved using SelfNaming.getObjectName(). Otherwise, the configured ObjectNamingStrategy is used.

Params:
  • bean – the name of the bean in the BeanFactory
  • beanKey – the key associated with the bean in the beans map
Throws:
Returns:the ObjectName for the supplied bean
/** * Retrieve the {@code ObjectName} for a bean. * <p>If the bean implements the {@code SelfNaming} interface, then the * {@code ObjectName} will be retrieved using {@code SelfNaming.getObjectName()}. * Otherwise, the configured {@code ObjectNamingStrategy} is used. * @param bean the name of the bean in the {@code BeanFactory} * @param beanKey the key associated with the bean in the beans map * @return the {@code ObjectName} for the supplied bean * @throws javax.management.MalformedObjectNameException * if the retrieved {@code ObjectName} is malformed */
protected ObjectName getObjectName(Object bean, @Nullable String beanKey) throws MalformedObjectNameException { if (bean instanceof SelfNaming) { return ((SelfNaming) bean).getObjectName(); } else { return this.namingStrategy.getObjectName(bean, beanKey); } }
Determine whether the given bean class qualifies as an MBean as-is.

The default implementation delegates to JmxUtils.isMBean, which checks for DynamicMBean classes as well as classes with corresponding "*MBean" interface (Standard MBeans) or corresponding "*MXBean" interface (Java 6 MXBeans).

Params:
  • beanClass – the bean class to analyze
See Also:
Returns:whether the class qualifies as an MBean
/** * Determine whether the given bean class qualifies as an MBean as-is. * <p>The default implementation delegates to {@link JmxUtils#isMBean}, * which checks for {@link javax.management.DynamicMBean} classes as well * as classes with corresponding "*MBean" interface (Standard MBeans) * or corresponding "*MXBean" interface (Java 6 MXBeans). * @param beanClass the bean class to analyze * @return whether the class qualifies as an MBean * @see org.springframework.jmx.support.JmxUtils#isMBean(Class) */
protected boolean isMBean(@Nullable Class<?> beanClass) { return JmxUtils.isMBean(beanClass); }
Build an adapted MBean for the given bean instance, if possible.

The default implementation builds a JMX 1.2 StandardMBean for the target's MBean/MXBean interface in case of an AOP proxy, delegating the interface's management operations to the proxy.

Params:
  • bean – the original bean instance
Returns:the adapted MBean, or null if not possible
/** * Build an adapted MBean for the given bean instance, if possible. * <p>The default implementation builds a JMX 1.2 StandardMBean * for the target's MBean/MXBean interface in case of an AOP proxy, * delegating the interface's management operations to the proxy. * @param bean the original bean instance * @return the adapted MBean, or {@code null} if not possible */
@SuppressWarnings("unchecked") @Nullable protected DynamicMBean adaptMBeanIfPossible(Object bean) throws JMException { Class<?> targetClass = AopUtils.getTargetClass(bean); if (targetClass != bean.getClass()) { Class<?> ifc = JmxUtils.getMXBeanInterface(targetClass); if (ifc != null) { if (!ifc.isInstance(bean)) { throw new NotCompliantMBeanException("Managed bean [" + bean + "] has a target class with an MXBean interface but does not expose it in the proxy"); } return new StandardMBean(bean, ((Class<Object>) ifc), true); } else { ifc = JmxUtils.getMBeanInterface(targetClass); if (ifc != null) { if (!ifc.isInstance(bean)) { throw new NotCompliantMBeanException("Managed bean [" + bean + "] has a target class with an MBean interface but does not expose it in the proxy"); } return new StandardMBean(bean, ((Class<Object>) ifc)); } } } return null; }
Creates an MBean that is configured with the appropriate management interface for the supplied managed resource.
Params:
  • managedResource – the resource that is to be exported as an MBean
  • beanKey – the key associated with the managed bean
See Also:
/** * Creates an MBean that is configured with the appropriate management * interface for the supplied managed resource. * @param managedResource the resource that is to be exported as an MBean * @param beanKey the key associated with the managed bean * @see #createModelMBean() * @see #getMBeanInfo(Object, String) */
protected ModelMBean createAndConfigureMBean(Object managedResource, String beanKey) throws MBeanExportException { try { ModelMBean mbean = createModelMBean(); mbean.setModelMBeanInfo(getMBeanInfo(managedResource, beanKey)); mbean.setManagedResource(managedResource, MR_TYPE_OBJECT_REFERENCE); return mbean; } catch (Throwable ex) { throw new MBeanExportException("Could not create ModelMBean for managed resource [" + managedResource + "] with key '" + beanKey + "'", ex); } }
Create an instance of a class that implements ModelMBean.

This method is called to obtain a ModelMBean instance to use when registering a bean. This method is called once per bean during the registration phase and must return a new instance of ModelMBean

Throws:
Returns:a new instance of a class that implements ModelMBean
/** * Create an instance of a class that implements {@code ModelMBean}. * <p>This method is called to obtain a {@code ModelMBean} instance to * use when registering a bean. This method is called once per bean during the * registration phase and must return a new instance of {@code ModelMBean} * @return a new instance of a class that implements {@code ModelMBean} * @throws javax.management.MBeanException if creation of the ModelMBean failed */
protected ModelMBean createModelMBean() throws MBeanException { return (this.exposeManagedResourceClassLoader ? new SpringModelMBean() : new RequiredModelMBean()); }
Gets the ModelMBeanInfo for the bean with the supplied key and of the supplied type.
/** * Gets the {@code ModelMBeanInfo} for the bean with the supplied key * and of the supplied type. */
private ModelMBeanInfo getMBeanInfo(Object managedBean, String beanKey) throws JMException { ModelMBeanInfo info = this.assembler.getMBeanInfo(managedBean, beanKey); if (logger.isInfoEnabled() && ObjectUtils.isEmpty(info.getAttributes()) && ObjectUtils.isEmpty(info.getOperations())) { logger.info("Bean with key '" + beanKey + "' has been registered as an MBean but has no exposed attributes or operations"); } return info; } //--------------------------------------------------------------------- // Autodetection process //---------------------------------------------------------------------
Performs the actual autodetection process, delegating to an AutodetectCallback instance to vote on the inclusion of a given bean.
Params:
  • callback – the AutodetectCallback to use when deciding whether to include a bean or not
/** * Performs the actual autodetection process, delegating to an * {@code AutodetectCallback} instance to vote on the inclusion of a * given bean. * @param callback the {@code AutodetectCallback} to use when deciding * whether to include a bean or not */
private void autodetect(Map<String, Object> beans, AutodetectCallback callback) { Assert.state(this.beanFactory != null, "No BeanFactory set"); Set<String> beanNames = new LinkedHashSet<>(this.beanFactory.getBeanDefinitionCount()); Collections.addAll(beanNames, this.beanFactory.getBeanDefinitionNames()); if (this.beanFactory instanceof ConfigurableBeanFactory) { Collections.addAll(beanNames, ((ConfigurableBeanFactory) this.beanFactory).getSingletonNames()); } for (String beanName : beanNames) { if (!isExcluded(beanName) && !isBeanDefinitionAbstract(this.beanFactory, beanName)) { try { Class<?> beanClass = this.beanFactory.getType(beanName); if (beanClass != null && callback.include(beanClass, beanName)) { boolean lazyInit = isBeanDefinitionLazyInit(this.beanFactory, beanName); Object beanInstance = null; if (!lazyInit) { beanInstance = this.beanFactory.getBean(beanName); if (!beanClass.isInstance(beanInstance)) { continue; } } if (!ScopedProxyUtils.isScopedTarget(beanName) && !beans.containsValue(beanName) && (beanInstance == null || !CollectionUtils.containsInstance(beans.values(), beanInstance))) { // Not already registered for JMX exposure. beans.put(beanName, (beanInstance != null ? beanInstance : beanName)); if (logger.isDebugEnabled()) { logger.debug("Bean with name '" + beanName + "' has been autodetected for JMX exposure"); } } else { if (logger.isTraceEnabled()) { logger.trace("Bean with name '" + beanName + "' is already registered for JMX exposure"); } } } } catch (CannotLoadBeanClassException ex) { if (this.allowEagerInit) { throw ex; } // otherwise ignore beans where the class is not resolvable } } } }
Indicates whether or not a particular bean name is present in the excluded beans list.
/** * Indicates whether or not a particular bean name is present in the excluded beans list. */
private boolean isExcluded(String beanName) { return (this.excludedBeans.contains(beanName) || (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX) && this.excludedBeans.contains(beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length())))); }
Return whether the specified bean definition should be considered as abstract.
/** * Return whether the specified bean definition should be considered as abstract. */
private boolean isBeanDefinitionAbstract(ListableBeanFactory beanFactory, String beanName) { return (beanFactory instanceof ConfigurableListableBeanFactory && beanFactory.containsBeanDefinition(beanName) && ((ConfigurableListableBeanFactory) beanFactory).getBeanDefinition(beanName).isAbstract()); } //--------------------------------------------------------------------- // Notification and listener management //---------------------------------------------------------------------
If the supplied managed resource implements the NotificationPublisherAware an instance of NotificationPublisher is injected.
/** * If the supplied managed resource implements the {@link NotificationPublisherAware} an instance of * {@link org.springframework.jmx.export.notification.NotificationPublisher} is injected. */
private void injectNotificationPublisherIfNecessary( Object managedResource, @Nullable ModelMBean modelMBean, @Nullable ObjectName objectName) { if (managedResource instanceof NotificationPublisherAware && modelMBean != null && objectName != null) { ((NotificationPublisherAware) managedResource).setNotificationPublisher( new ModelMBeanNotificationPublisher(modelMBean, objectName, managedResource)); } }
Register the configured NotificationListeners with the MBeanServer.
/** * Register the configured {@link NotificationListener NotificationListeners} * with the {@link MBeanServer}. */
private void registerNotificationListeners() throws MBeanExportException { if (this.notificationListeners != null) { Assert.state(this.server != null, "No MBeanServer available"); for (NotificationListenerBean bean : this.notificationListeners) { try { ObjectName[] mappedObjectNames = bean.getResolvedObjectNames(); if (mappedObjectNames == null) { // Mapped to all MBeans registered by the MBeanExporter. mappedObjectNames = getRegisteredObjectNames(); } if (this.registeredNotificationListeners.put(bean, mappedObjectNames) == null) { for (ObjectName mappedObjectName : mappedObjectNames) { this.server.addNotificationListener(mappedObjectName, bean.getNotificationListener(), bean.getNotificationFilter(), bean.getHandback()); } } } catch (Throwable ex) { throw new MBeanExportException("Unable to register NotificationListener", ex); } } } }
Unregister the configured NotificationListeners from the MBeanServer.
/** * Unregister the configured {@link NotificationListener NotificationListeners} * from the {@link MBeanServer}. */
private void unregisterNotificationListeners() { if (this.server != null) { this.registeredNotificationListeners.forEach((bean, mappedObjectNames) -> { for (ObjectName mappedObjectName : mappedObjectNames) { try { this.server.removeNotificationListener(mappedObjectName, bean.getNotificationListener(), bean.getNotificationFilter(), bean.getHandback()); } catch (Throwable ex) { if (logger.isDebugEnabled()) { logger.debug("Unable to unregister NotificationListener", ex); } } } }); } this.registeredNotificationListeners.clear(); }
Called when an MBean is registered. Notifies all registered MBeanExporterListeners of the registration event.

Please note that if an MBeanExporterListener throws a (runtime) exception when notified, this will essentially interrupt the notification process and any remaining listeners that have yet to be notified will not (obviously) receive the MBeanExporterListener.mbeanRegistered(ObjectName) callback.

Params:
  • objectName – the ObjectName of the registered MBean
/** * Called when an MBean is registered. Notifies all registered * {@link MBeanExporterListener MBeanExporterListeners} of the registration event. * <p>Please note that if an {@link MBeanExporterListener} throws a (runtime) * exception when notified, this will essentially interrupt the notification process * and any remaining listeners that have yet to be notified will not (obviously) * receive the {@link MBeanExporterListener#mbeanRegistered(javax.management.ObjectName)} * callback. * @param objectName the {@code ObjectName} of the registered MBean */
@Override protected void onRegister(ObjectName objectName) { notifyListenersOfRegistration(objectName); }
Called when an MBean is unregistered. Notifies all registered MBeanExporterListeners of the unregistration event.

Please note that if an MBeanExporterListener throws a (runtime) exception when notified, this will essentially interrupt the notification process and any remaining listeners that have yet to be notified will not (obviously) receive the MBeanExporterListener.mbeanUnregistered(ObjectName) callback.

Params:
  • objectName – the ObjectName of the unregistered MBean
/** * Called when an MBean is unregistered. Notifies all registered * {@link MBeanExporterListener MBeanExporterListeners} of the unregistration event. * <p>Please note that if an {@link MBeanExporterListener} throws a (runtime) * exception when notified, this will essentially interrupt the notification process * and any remaining listeners that have yet to be notified will not (obviously) * receive the {@link MBeanExporterListener#mbeanUnregistered(javax.management.ObjectName)} * callback. * @param objectName the {@code ObjectName} of the unregistered MBean */
@Override protected void onUnregister(ObjectName objectName) { notifyListenersOfUnregistration(objectName); }
Notifies all registered MBeanExporterListeners of the registration of the MBean identified by the supplied ObjectName.
/** * Notifies all registered {@link MBeanExporterListener MBeanExporterListeners} of the * registration of the MBean identified by the supplied {@link ObjectName}. */
private void notifyListenersOfRegistration(ObjectName objectName) { if (this.listeners != null) { for (MBeanExporterListener listener : this.listeners) { listener.mbeanRegistered(objectName); } } }
Notifies all registered MBeanExporterListeners of the unregistration of the MBean identified by the supplied ObjectName.
/** * Notifies all registered {@link MBeanExporterListener MBeanExporterListeners} of the * unregistration of the MBean identified by the supplied {@link ObjectName}. */
private void notifyListenersOfUnregistration(ObjectName objectName) { if (this.listeners != null) { for (MBeanExporterListener listener : this.listeners) { listener.mbeanUnregistered(objectName); } } } //--------------------------------------------------------------------- // Inner classes for internal use //---------------------------------------------------------------------
Internal callback interface for the autodetection process.
/** * Internal callback interface for the autodetection process. */
@FunctionalInterface private interface AutodetectCallback {
Called during the autodetection process to decide whether or not a bean should be included.
Params:
  • beanClass – the class of the bean
  • beanName – the name of the bean
/** * Called during the autodetection process to decide whether * or not a bean should be included. * @param beanClass the class of the bean * @param beanName the name of the bean */
boolean include(Class<?> beanClass, String beanName); }
Extension of LazyInitTargetSource that will inject a NotificationPublisher into the lazy resource as it is created if required.
/** * Extension of {@link LazyInitTargetSource} that will inject a * {@link org.springframework.jmx.export.notification.NotificationPublisher} * into the lazy resource as it is created if required. */
@SuppressWarnings("serial") private class NotificationPublisherAwareLazyTargetSource extends LazyInitTargetSource { @Nullable private ModelMBean modelMBean; @Nullable private ObjectName objectName; public void setModelMBean(ModelMBean modelMBean) { this.modelMBean = modelMBean; } public void setObjectName(ObjectName objectName) { this.objectName = objectName; } @Override @Nullable public Object getTarget() { try { return super.getTarget(); } catch (RuntimeException ex) { if (logger.isInfoEnabled()) { logger.info("Failed to retrieve target for JMX-exposed bean [" + this.objectName + "]: " + ex); } throw ex; } } @Override protected void postProcessTargetObject(Object targetObject) { injectNotificationPublisherIfNecessary(targetObject, this.modelMBean, this.objectName); } } }