org.springframework.jmx.export.MBeanExporter Maven / Gradle / Ivy
/*
* 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 MBeanExporterListener
s 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);
}
}
}