All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.jmx.export.MBeanExporter Maven / Gradle / Ivy

There is a newer version: 5.3.34
Show newest version
/*
 * Copyright 2002-2007 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
 *
 *      http://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.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.management.InstanceNotFoundException;
import javax.management.JMException;
import javax.management.MBeanException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectName;
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.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.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
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.jmx.support.ObjectNameManager;
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 the bean implements one of the JMX management interfaces, * then MBeanExporter can simply register the MBean with the server * automatically, through its autodetection process. * *

If the bean does not implement one of the JMX management interfaces, * then MBeanExporter will create the management information using the * supplied {@link MBeanInfoAssembler} implementation. * *

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. * * @author Rob Harrop * @author Juergen Hoeller * @author Rick Evans * @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, DisposableBean { /** * 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. */ public static final int AUTODETECT_MBEAN = 1; /** * 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. */ public static final int AUTODETECT_ALL = AUTODETECT_MBEAN | AUTODETECT_ASSEMBLER; /** * Wildcard used to map a {@link javax.management.NotificationListener} * to all MBeans registered by the MBeanExporter. */ private static final String WILDCARD = "*"; /** Constant for the JMX mr_type "ObjectReference" */ private static final String MR_TYPE_OBJECT_REFERENCE = "ObjectReference"; /** Prefix for the autodetect constants defined in this class */ private static final String CONSTANT_PREFIX_AUTODETECT = "AUTODETECT_"; /** 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 */ private Map beans; /** The autodetect mode to use for this MBeanExporter */ private int autodetectMode = AUTODETECT_NONE; /** Indicates whether Spring should modify generated ObjectNames */ private boolean ensureUniqueRuntimeObjectNames = true; /** Indicates whether Spring should expose the managed resource ClassLoader in the MBean */ private boolean exposeManagedResourceClassLoader = false; /** A set of bean names that should be excluded from autodetection */ private Set excludedBeans; /** The MBeanExporterListeners registered with this exporter. */ private MBeanExporterListener[] listeners; /** The NotificationListeners to register for the MBeans registered by this exporter */ private NotificationListenerBean[] notificationListeners = new NotificationListenerBean[0]; /** Stores the MBeanInfoAssembler to use for this exporter */ private MBeanInfoAssembler assembler = new SimpleReflectiveMBeanInfoAssembler(); /** The strategy to use for creating ObjectNames for an object */ private ObjectNamingStrategy namingStrategy = new KeyNamingStrategy(); /** Stores the ClassLoader to use for generating lazy-init proxies */ private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); /** Stores the BeanFactory for use in autodetection process */ 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). * @param beans 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 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 #setAssembler * @see AutodetectCapableMBeanInfoAssembler * @see #isMBean * @deprecated in favor of {@link #setAutodetectModeName(String)} */ public void setAutodetect(boolean autodetect) { this.autodetectMode = (autodetect ? AUTODETECT_ALL : AUTODETECT_NONE); } /** * Sets the autodetection mode to use. * @exception IllegalArgumentException if the supplied value is not * one of the 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(new Integer(autodetectMode))) { throw new IllegalArgumentException("Only values of autodetect constants allowed"); } this.autodetectMode = autodetectMode; } /** * Sets the autodetection mode to use by name. * @exception IllegalArgumentException if the supplied value is not resolvable * to one of the AUTODETECT_ constants or is null * @see #setAutodetectMode(int) * @see #AUTODETECT_ALL * @see #AUTODETECT_ASSEMBLER * @see #AUTODETECT_MBEAN * @see #AUTODETECT_NONE */ public void setAutodetectModeName(String constantName) { if (constantName == null || !constantName.startsWith(CONSTANT_PREFIX_AUTODETECT)) { throw new IllegalArgumentException("Only autodetect constants allowed"); } this.autodetectMode = constants.asNumber(constantName).intValue(); } /** * 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 particiapte 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 org.springframework.jmx.export.naming.KeyNamingStrategy * @see org.springframework.jmx.export.naming.MetadataNamingStrategy */ public void setNamingStrategy(ObjectNamingStrategy namingStrategy) { this.namingStrategy = namingStrategy; } /** * Set the MBeanExporterListeners that should be notified * of MBean registration and unregistration events. * @see MBeanExporterListener */ public void setListeners(MBeanExporterListener[] listeners) { this.listeners = listeners; } /** * Set the list of names for beans that should be excluded from autodetection. */ public void setExcludedBeans(String[] excludedBeans) { this.excludedBeans = (excludedBeans != null ? new HashSet(Arrays.asList(excludedBeans)) : null); } /** * Indicates whether Spring should ensure that {@link ObjectName ObjectNames} generated by * the configured {@link ObjectNamingStrategy} for runtime-registered MBeans should be modified * to ensure uniqueness for every instance of managed Class. Default value is * true. * @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 as the * {@link Thread#getContextClassLoader() thread context ClassLoader} before allowing * any invocations on the MBean to occur. Default value is false. */ public void setExposeManagedResourceClassLoader(boolean exposeManagedResourceClassLoader) { this.exposeManagedResourceClassLoader = exposeManagedResourceClassLoader; } /** * 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 {@link NotificationListener NotificationListeners} to register with the * {@link javax.management.MBeanServer}. The key of each entry in the Map is * a String representation of the {@link javax.management.ObjectName} of the MBean the * listener should be registered for. Specifying an asterisk (*) will cause * the listener to be associated with all MBeans registered by this class at startup time. *

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 listeners) { Assert.notNull(listeners, "Property 'notificationListenerMappings' must not be null"); List notificationListeners = new ArrayList(listeners.size()); for (Iterator iterator = listeners.entrySet().iterator(); iterator.hasNext();) { Map.Entry entry = (Map.Entry) iterator.next(); // Get the listener from the map value. Object value = entry.getValue(); if (!(value instanceof NotificationListener)) { throw new IllegalArgumentException( "Map entry value [" + value + "] is not a NotificationListener"); } NotificationListenerBean bean = new NotificationListenerBean((NotificationListener) value); // Get the ObjectName from the map key. Object key = entry.getKey(); if (key != null && !WILDCARD.equals(key)) { // This listener is mapped to a specific ObjectName. bean.setMappedObjectName(entry.getKey().toString()); } notificationListeners.add(bean); } this.notificationListeners = (NotificationListenerBean[]) notificationListeners.toArray(new NotificationListenerBean[notificationListeners.size()]); } 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 #setBeans * @see #setAutodetect * @see org.springframework.beans.factory.ListableBeanFactory */ public void setBeanFactory(BeanFactory beanFactory) { if (beanFactory instanceof ListableBeanFactory) { this.beanFactory = (ListableBeanFactory) beanFactory; } else { logger.info("MBeanExporter not running in a ListableBeanFactory: Autodetection of MBeans not available."); } } //--------------------------------------------------------------------- // Lifecycle in bean factory: automatically register/unregister beans //--------------------------------------------------------------------- /** * Start bean registration automatically when deployed in an * ApplicationContext. * @see #registerBeans() */ public void afterPropertiesSet() { logger.info("Registering beans for JMX exposure on startup"); registerBeans(); } /** * Unregisters all beans that this exported has exposed via JMX * when the enclosing ApplicationContext is destroyed. */ public void destroy() { logger.info("Unregistering JMX-exposed beans on shutdown"); unregisterBeans(); } //--------------------------------------------------------------------- // Implementation of MBeanExportOperations interface //--------------------------------------------------------------------- public ObjectName registerManagedResource(Object managedResource) throws MBeanExportException { Assert.notNull(managedResource, "Managed resource must not be null"); try { ObjectName objectName = getObjectName(managedResource, null); if (this.ensureUniqueRuntimeObjectNames) { objectName = JmxUtils.appendIdentityToObjectName(objectName, managedResource); } registerManagedResource(managedResource, objectName); return objectName; } catch (MalformedObjectNameException ex) { throw new MBeanExportException("Unable to generate ObjectName for MBean [" + managedResource + "]", ex); } } 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"); Object mbean = null; if (isMBean(managedResource.getClass())) { mbean = managedResource; } else { mbean = createAndConfigureMBean(managedResource, managedResource.getClass().getName()); } try { doRegister(mbean, objectName); } catch (JMException ex) { throw new UnableToRegisterMBeanException( "Unable to register MBean [" + managedResource + "] with object name [" + objectName + "]", ex); } } //--------------------------------------------------------------------- // Exporter implementation //--------------------------------------------------------------------- /** * Registers the defined beans with the MBeanServer. Each bean is exposed * to the MBeanServer via a ModelMBean. The actual implemetation * 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. */ protected void registerBeans() { // If no server was provided then try to find one. // This is useful in an environment such as JDK 1.5, Tomcat // or JBoss where there is already an MBeanServer loaded. if (this.server == null) { this.server = JmxUtils.locateMBeanServer(); } // The beans property may be null, for example // if we are relying solely on autodetection. if (this.beans == null) { this.beans = new HashMap(); } // Perform autodetection, if desired. if (this.autodetectMode != AUTODETECT_NONE) { if (this.beanFactory == null) { throw new MBeanExportException("Cannot autodetect MBeans if not running in a BeanFactory"); } if (isAutodetectModeEnabled(AUTODETECT_MBEAN)) { // Autodetect any beans that are already MBeans. logger.info("Autodetecting user-defined JMX MBeans"); autodetectMBeans(); } // Allow the assembler a chance to vote for bean inclusion. if (isAutodetectModeEnabled(AUTODETECT_ASSEMBLER) && this.assembler instanceof AutodetectCapableMBeanInfoAssembler) { autodetectBeans((AutodetectCapableMBeanInfoAssembler) this.assembler); } } // Check we now have at least one bean. if (this.beans.isEmpty()) { throw new IllegalArgumentException("Must specify at least one bean for registration"); } try { for (Iterator it = this.beans.entrySet().iterator(); it.hasNext();) { Map.Entry entry = (Map.Entry) it.next(); String beanKey = (String) entry.getKey(); Object value = entry.getValue(); registerBeanNameOrInstance(value, beanKey); } // All MBeans are now registered successfully - go ahead and register the notification listeners. registerNotificationListeners(); } catch (MBeanExportException ex) { // Unregister beans already registered by this exporter. unregisterBeans(); throw ex; } } /** * 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) { if (!(beanFactory instanceof ConfigurableListableBeanFactory)) { return false; } try { BeanDefinition bd = ((ConfigurableListableBeanFactory) beanFactory).getBeanDefinition(beanName); return bd.isLazyInit(); } catch (NoSuchBeanDefinitionException ex) { // Probably a directly registered singleton. return false; } } /** * Registers an individual bean with the MBeanServer. This method * is responsible for deciding how a bean should be exposed * to the MBeanServer. Specifically, if the 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 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. * @param beanKey the key associated with this bean in the beans map * @param mapValue the value configured for this bean in the beans map. * May be either the String name of a bean, or the bean itself. * @return the 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)) { return registerLazyInit(beanName, beanKey); } else { Object bean = this.beanFactory.getBean(beanName); return registerBeanInstance(bean, beanKey); } } else { // Plain bean instance -> register it directly. return registerBeanInstance(mapValue, beanKey); } } catch (JMException ex) { throw new UnableToRegisterMBeanException( "Unable to register MBean [" + mapValue + "] with key '" + beanKey + "'", ex); } } /** * Registers an existing MBean or an MBean adapter for a plain bean * with the 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 ObjectName under which the bean was registered * with the MBeanServer */ private ObjectName registerBeanInstance(Object bean, String beanKey) throws JMException { ObjectName objectName = getObjectName(bean, beanKey); if (isMBean(bean.getClass())) { if (logger.isDebugEnabled()) { logger.debug("Located MBean '" + beanKey + "': registering with JMX server as MBean [" + objectName + "]"); } doRegister(bean, objectName); } else { if (logger.isDebugEnabled()) { logger.debug("Located simple bean '" + beanKey + "': registering with JMX server as MBean [" + objectName + "]"); } ModelMBean mbean = createAndConfigureMBean(bean, beanKey); doRegister(mbean, objectName); injectNotificationPublisherIfNecessary(bean, mbean, objectName); } return objectName; } /** * Registers beans that are configured for lazy initialization with the * MBeanServer indirectly through a proxy. * @param beanName the name of the bean in the BeanFactory * @param beanKey the key associated with this bean in the beans map * @return the ObjectName under which the bean was registered * with the MBeanServer */ private ObjectName registerLazyInit(String beanName, String beanKey) throws JMException { 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. * @param bean the name of the bean in the BeanFactory * @param beanKey the key associated with the bean in the beans map * @return the ObjectName for the supplied bean * @throws javax.management.MalformedObjectNameException * if the retrieved ObjectName is malformed */ protected ObjectName getObjectName(Object bean, 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 {@link JmxUtils#isMBean}, * which checks for {@link javax.management.DynamicMBean} classes as well * as classes with corresponding "*MBean" interface (Standard MBeans). * This can be overridden in subclasses, for example to check for * JDK 1.6 MXBeans as well. * @param beanClass the bean class to analyze * @see org.springframework.jmx.support.JmxUtils#isMBean(Class) */ protected boolean isMBean(Class beanClass) { return JmxUtils.isMBean(beanClass); } /** * 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 (Exception 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 * @return a new instance of a class that implements 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. */ private ModelMBeanInfo getMBeanInfo(Object managedBean, String beanKey) throws JMException { ModelMBeanInfo info = this.assembler.getMBeanInfo(managedBean, beanKey); if (logger.isWarnEnabled() && ObjectUtils.isEmpty(info.getAttributes()) && ObjectUtils.isEmpty(info.getOperations())) { logger.warn("Bean with key '" + beanKey + "' has been registered as an MBean but has no exposed attributes or operations"); } return info; } //--------------------------------------------------------------------- // Autodetection process //--------------------------------------------------------------------- /** * Returns true if the particular autodetect mode is enabled * otherwise returns false. */ private boolean isAutodetectModeEnabled(int mode) { return (this.autodetectMode & mode) == mode; } /** * Invoked when using an AutodetectCapableMBeanInfoAssembler. * Gives the assembler the opportunity to add additional beans from the * BeanFactory to the list of beans to be exposed via JMX. *

This implementation prevents a bean from being added to the list * automatically if it has already been added manually, and it prevents * certain internal classes from being registered automatically. */ private void autodetectBeans(final AutodetectCapableMBeanInfoAssembler assembler) { autodetect(new AutodetectCallback() { public boolean include(Class beanClass, String beanName) { return assembler.includeBean(beanClass, beanName); } }); } /** * Attempts to detect any beans defined in the ApplicationContext that are * valid MBeans and registers them automatically with the MBeanServer. */ private void autodetectMBeans() { autodetect(new AutodetectCallback() { public boolean include(Class beanClass, String beanName) { return isMBean(beanClass); } }); } /** * Performs the actual autodetection process, delegating to an * AutodetectCallback instance to vote on the inclusion of a * given bean. * @param callback the AutodetectCallback to use when deciding * whether to include a bean or not */ private void autodetect(AutodetectCallback callback) { String[] beanNames = this.beanFactory.getBeanNamesForType(null); for (int i = 0; i < beanNames.length; i++) { String beanName = beanNames[i]; if (!isExcluded(beanName)) { Class beanClass = this.beanFactory.getType(beanName); if (beanClass != null && callback.include(beanClass, beanName)) { boolean lazyInit = isBeanDefinitionLazyInit(this.beanFactory, beanName); Object beanInstance = (!lazyInit ? this.beanFactory.getBean(beanName) : null); if (!this.beans.containsValue(beanName) && (beanInstance == null || !CollectionUtils.containsInstance(this.beans.values(), beanInstance))) { // Not already registered for JMX exposure. this.beans.put(beanName, (beanInstance != null ? beanInstance : beanName)); if (logger.isInfoEnabled()) { logger.info("Bean with name '" + beanName + "' has been autodetected for JMX exposure"); } } else { if (logger.isDebugEnabled()) { logger.debug("Bean with name '" + beanName + "' is already registered for JMX exposure"); } } } } } } /** * Indicates whether or not a particular bean name is present in the excluded beans list. */ private boolean isExcluded(String beanName) { return (this.excludedBeans != null && this.excludedBeans.contains(beanName)); } //--------------------------------------------------------------------- // Management of notification listeners //--------------------------------------------------------------------- /** * 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, ModelMBean modelMBean, ObjectName objectName) { if (managedResource instanceof NotificationPublisherAware) { ((NotificationPublisherAware) managedResource).setNotificationPublisher( new ModelMBeanNotificationPublisher(modelMBean, objectName, managedResource)); } } /** * Register the configured {@link NotificationListener NotificationListeners} * with the {@link MBeanServer}. */ private void registerNotificationListeners() throws MBeanExportException { for (int i = 0; i < this.notificationListeners.length; i++) { NotificationListenerBean bean = this.notificationListeners[i]; NotificationListener listener = bean.getNotificationListener(); NotificationFilter filter = bean.getNotificationFilter(); Object handback = bean.getHandback(); ObjectName[] namesToRegisterWith = getObjectNamesForNotificationListener(bean); for (int j = 0; j < namesToRegisterWith.length; j++) { ObjectName objectName = namesToRegisterWith[j]; try { this.server.addNotificationListener(objectName, listener, filter, handback); } catch (InstanceNotFoundException ex) { throw new MBeanExportException("Unable to register NotificationListener for MBean [" + objectName + "] because that MBean instance does not exist", ex); } } } } /** * Retrieve the {@link javax.management.ObjectName ObjectNames} for which a * {@link NotificationListener} should be registered. */ private ObjectName[] getObjectNamesForNotificationListener(NotificationListenerBean bean) throws MBeanExportException { String[] mappedObjectNames = bean.getMappedObjectNames(); if (mappedObjectNames != null) { ObjectName[] objectNames = new ObjectName[mappedObjectNames.length]; for (int i = 0; i < mappedObjectNames.length; i++) { String mappedName = mappedObjectNames[i]; try { objectNames[i] = ObjectNameManager.getInstance(mappedName); } catch (MalformedObjectNameException ex) { throw new MBeanExportException( "Invalid ObjectName [" + mappedName + "] specified for NotificationListener [" + bean.getNotificationListener() + "]", ex); } } return objectNames; } else { // Mapped to all MBeans registered by the MBeanExporter. return (ObjectName[]) this.registeredBeans.toArray(new ObjectName[this.registeredBeans.size()]); } } /** * Called when an MBean is registered. Notifies all registered * {@link MBeanExporterListener MBeanExporterListeners} of the registration event. *

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 ObjectName of the registered MBean */ protected void onRegister(ObjectName objectName) { notifyListenersOfRegistration(objectName); } /** * Called when an MBean is unregistered. Notifies all registered * {@link MBeanExporterListener MBeanExporterListeners} of the unregistration event. *

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 ObjectName of the unregistered MBean */ protected void onUnregister(ObjectName objectName) { notifyListenersOfUnregistration(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 (int i = 0; i < this.listeners.length; i++) { this.listeners[i].mbeanRegistered(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 (int i = 0; i < this.listeners.length; i++) { this.listeners[i].mbeanUnregistered(objectName); } } } //--------------------------------------------------------------------- // Inner classes for internal use //--------------------------------------------------------------------- /** * Internal callback interface for the autodetection process. */ private static interface AutodetectCallback { /** * 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 {@link LazyInitTargetSource} that will inject a * {@link org.springframework.jmx.export.notification.NotificationPublisher} * into the lazy resource as it is created if required. */ private class NotificationPublisherAwareLazyTargetSource extends LazyInitTargetSource { private ModelMBean modelMBean; private ObjectName objectName; public void setModelMBean(ModelMBean modelMBean) { this.modelMBean = modelMBean; } public void setObjectName(ObjectName objectName) { this.objectName = objectName; } protected void postProcessTargetObject(Object targetObject) { injectNotificationPublisherIfNecessary(targetObject, this.modelMBean, this.objectName); } } }