001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.jmx.export; 018 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.HashMap; 022import java.util.HashSet; 023import java.util.LinkedHashMap; 024import java.util.LinkedHashSet; 025import java.util.List; 026import java.util.Map; 027import java.util.Set; 028import javax.management.DynamicMBean; 029import javax.management.JMException; 030import javax.management.MBeanException; 031import javax.management.MBeanServer; 032import javax.management.MalformedObjectNameException; 033import javax.management.NotCompliantMBeanException; 034import javax.management.NotificationListener; 035import javax.management.ObjectName; 036import javax.management.StandardMBean; 037import javax.management.modelmbean.ModelMBean; 038import javax.management.modelmbean.ModelMBeanInfo; 039import javax.management.modelmbean.RequiredModelMBean; 040 041import org.springframework.aop.framework.ProxyFactory; 042import org.springframework.aop.scope.ScopedProxyUtils; 043import org.springframework.aop.support.AopUtils; 044import org.springframework.aop.target.LazyInitTargetSource; 045import org.springframework.beans.factory.BeanClassLoaderAware; 046import org.springframework.beans.factory.BeanFactory; 047import org.springframework.beans.factory.BeanFactoryAware; 048import org.springframework.beans.factory.CannotLoadBeanClassException; 049import org.springframework.beans.factory.DisposableBean; 050import org.springframework.beans.factory.InitializingBean; 051import org.springframework.beans.factory.ListableBeanFactory; 052import org.springframework.beans.factory.SmartInitializingSingleton; 053import org.springframework.beans.factory.config.ConfigurableBeanFactory; 054import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 055import org.springframework.core.Constants; 056import org.springframework.jmx.export.assembler.AutodetectCapableMBeanInfoAssembler; 057import org.springframework.jmx.export.assembler.MBeanInfoAssembler; 058import org.springframework.jmx.export.assembler.SimpleReflectiveMBeanInfoAssembler; 059import org.springframework.jmx.export.naming.KeyNamingStrategy; 060import org.springframework.jmx.export.naming.ObjectNamingStrategy; 061import org.springframework.jmx.export.naming.SelfNaming; 062import org.springframework.jmx.export.notification.ModelMBeanNotificationPublisher; 063import org.springframework.jmx.export.notification.NotificationPublisherAware; 064import org.springframework.jmx.support.JmxUtils; 065import org.springframework.jmx.support.MBeanRegistrationSupport; 066import org.springframework.util.Assert; 067import org.springframework.util.ClassUtils; 068import org.springframework.util.CollectionUtils; 069import org.springframework.util.ObjectUtils; 070 071/** 072 * JMX exporter that allows for exposing any <i>Spring-managed bean</i> to a 073 * JMX {@link javax.management.MBeanServer}, without the need to define any 074 * JMX-specific information in the bean classes. 075 * 076 * <p>If a bean implements one of the JMX management interfaces, MBeanExporter can 077 * simply register the MBean with the server through its autodetection process. 078 * 079 * <p>If a bean does not implement one of the JMX management interfaces, MBeanExporter 080 * will create the management information using the supplied {@link MBeanInfoAssembler}. 081 * 082 * <p>A list of {@link MBeanExporterListener MBeanExporterListeners} can be registered 083 * via the {@link #setListeners(MBeanExporterListener[]) listeners} property, allowing 084 * application code to be notified of MBean registration and unregistration events. 085 * 086 * <p>This exporter is compatible with MBeans as well as MXBeans. 087 * 088 * @author Rob Harrop 089 * @author Juergen Hoeller 090 * @author Rick Evans 091 * @author Mark Fisher 092 * @author Stephane Nicoll 093 * @since 1.2 094 * @see #setBeans 095 * @see #setAutodetect 096 * @see #setAssembler 097 * @see #setListeners 098 * @see org.springframework.jmx.export.assembler.MBeanInfoAssembler 099 * @see MBeanExporterListener 100 */ 101public class MBeanExporter extends MBeanRegistrationSupport implements MBeanExportOperations, 102 BeanClassLoaderAware, BeanFactoryAware, InitializingBean, SmartInitializingSingleton, DisposableBean { 103 104 /** 105 * Autodetection mode indicating that no autodetection should be used. 106 */ 107 public static final int AUTODETECT_NONE = 0; 108 109 /** 110 * Autodetection mode indicating that only valid MBeans should be autodetected. 111 */ 112 public static final int AUTODETECT_MBEAN = 1; 113 114 /** 115 * Autodetection mode indicating that only the {@link MBeanInfoAssembler} should be able 116 * to autodetect beans. 117 */ 118 public static final int AUTODETECT_ASSEMBLER = 2; 119 120 /** 121 * Autodetection mode indicating that all autodetection mechanisms should be used. 122 */ 123 public static final int AUTODETECT_ALL = AUTODETECT_MBEAN | AUTODETECT_ASSEMBLER; 124 125 126 /** 127 * Wildcard used to map a {@link javax.management.NotificationListener} 128 * to all MBeans registered by the {@code MBeanExporter}. 129 */ 130 private static final String WILDCARD = "*"; 131 132 /** Constant for the JMX {@code mr_type} "ObjectReference" */ 133 private static final String MR_TYPE_OBJECT_REFERENCE = "ObjectReference"; 134 135 /** Prefix for the autodetect constants defined in this class */ 136 private static final String CONSTANT_PREFIX_AUTODETECT = "AUTODETECT_"; 137 138 139 /** Constants instance for this class */ 140 private static final Constants constants = new Constants(MBeanExporter.class); 141 142 /** The beans to be exposed as JMX managed resources, with JMX names as keys */ 143 private Map<String, Object> beans; 144 145 /** The autodetect mode to use for this MBeanExporter */ 146 private Integer autodetectMode; 147 148 /** Whether to eagerly initialize candidate beans when autodetecting MBeans */ 149 private boolean allowEagerInit = false; 150 151 /** Stores the MBeanInfoAssembler to use for this exporter */ 152 private MBeanInfoAssembler assembler = new SimpleReflectiveMBeanInfoAssembler(); 153 154 /** The strategy to use for creating ObjectNames for an object */ 155 private ObjectNamingStrategy namingStrategy = new KeyNamingStrategy(); 156 157 /** Indicates whether Spring should modify generated ObjectNames */ 158 private boolean ensureUniqueRuntimeObjectNames = true; 159 160 /** Indicates whether Spring should expose the managed resource ClassLoader in the MBean */ 161 private boolean exposeManagedResourceClassLoader = true; 162 163 /** A set of bean names that should be excluded from autodetection */ 164 private Set<String> excludedBeans = new HashSet<String>(); 165 166 /** The MBeanExporterListeners registered with this exporter. */ 167 private MBeanExporterListener[] listeners; 168 169 /** The NotificationListeners to register for the MBeans registered by this exporter */ 170 private NotificationListenerBean[] notificationListeners; 171 172 /** Map of actually registered NotificationListeners */ 173 private final Map<NotificationListenerBean, ObjectName[]> registeredNotificationListeners = 174 new LinkedHashMap<NotificationListenerBean, ObjectName[]>(); 175 176 /** Stores the ClassLoader to use for generating lazy-init proxies */ 177 private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); 178 179 /** Stores the BeanFactory for use in autodetection process */ 180 private ListableBeanFactory beanFactory; 181 182 183 /** 184 * Supply a {@code Map} of beans to be registered with the JMX 185 * {@code MBeanServer}. 186 * <p>The String keys are the basis for the creation of JMX object names. 187 * By default, a JMX {@code ObjectName} will be created straight 188 * from the given key. This can be customized through specifying a 189 * custom {@code NamingStrategy}. 190 * <p>Both bean instances and bean names are allowed as values. 191 * Bean instances are typically linked in through bean references. 192 * Bean names will be resolved as beans in the current factory, respecting 193 * lazy-init markers (that is, not triggering initialization of such beans). 194 * @param beans Map with JMX names as keys and bean instances or bean names 195 * as values 196 * @see #setNamingStrategy 197 * @see org.springframework.jmx.export.naming.KeyNamingStrategy 198 * @see javax.management.ObjectName#ObjectName(String) 199 */ 200 public void setBeans(Map<String, Object> beans) { 201 this.beans = beans; 202 } 203 204 /** 205 * Set whether to autodetect MBeans in the bean factory that this exporter 206 * runs in. Will also ask an {@code AutodetectCapableMBeanInfoAssembler} 207 * if available. 208 * <p>This feature is turned off by default. Explicitly specify 209 * {@code true} here to enable autodetection. 210 * @see #setAssembler 211 * @see AutodetectCapableMBeanInfoAssembler 212 * @see #isMBean 213 */ 214 public void setAutodetect(boolean autodetect) { 215 this.autodetectMode = (autodetect ? AUTODETECT_ALL : AUTODETECT_NONE); 216 } 217 218 /** 219 * Set the autodetection mode to use. 220 * @throws IllegalArgumentException if the supplied value is not 221 * one of the {@code AUTODETECT_} constants 222 * @see #setAutodetectModeName(String) 223 * @see #AUTODETECT_ALL 224 * @see #AUTODETECT_ASSEMBLER 225 * @see #AUTODETECT_MBEAN 226 * @see #AUTODETECT_NONE 227 */ 228 public void setAutodetectMode(int autodetectMode) { 229 if (!constants.getValues(CONSTANT_PREFIX_AUTODETECT).contains(autodetectMode)) { 230 throw new IllegalArgumentException("Only values of autodetect constants allowed"); 231 } 232 this.autodetectMode = autodetectMode; 233 } 234 235 /** 236 * Set the autodetection mode to use by name. 237 * @throws IllegalArgumentException if the supplied value is not resolvable 238 * to one of the {@code AUTODETECT_} constants or is {@code null} 239 * @see #setAutodetectMode(int) 240 * @see #AUTODETECT_ALL 241 * @see #AUTODETECT_ASSEMBLER 242 * @see #AUTODETECT_MBEAN 243 * @see #AUTODETECT_NONE 244 */ 245 public void setAutodetectModeName(String constantName) { 246 if (constantName == null || !constantName.startsWith(CONSTANT_PREFIX_AUTODETECT)) { 247 throw new IllegalArgumentException("Only autodetect constants allowed"); 248 } 249 this.autodetectMode = (Integer) constants.asNumber(constantName); 250 } 251 252 /** 253 * Specify whether to allow eager initialization of candidate beans 254 * when autodetecting MBeans in the Spring application context. 255 * <p>Default is "false", respecting lazy-init flags on bean definitions. 256 * Switch this to "true" in order to search lazy-init beans as well, 257 * including FactoryBean-produced objects that haven't been initialized yet. 258 */ 259 public void setAllowEagerInit(boolean allowEagerInit) { 260 this.allowEagerInit = allowEagerInit; 261 } 262 263 /** 264 * Set the implementation of the {@code MBeanInfoAssembler} interface to use 265 * for this exporter. Default is a {@code SimpleReflectiveMBeanInfoAssembler}. 266 * <p>The passed-in assembler can optionally implement the 267 * {@code AutodetectCapableMBeanInfoAssembler} interface, which enables it 268 * to participate in the exporter's MBean autodetection process. 269 * @see org.springframework.jmx.export.assembler.SimpleReflectiveMBeanInfoAssembler 270 * @see org.springframework.jmx.export.assembler.AutodetectCapableMBeanInfoAssembler 271 * @see org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler 272 * @see #setAutodetect 273 */ 274 public void setAssembler(MBeanInfoAssembler assembler) { 275 this.assembler = assembler; 276 } 277 278 /** 279 * Set the implementation of the {@code ObjectNamingStrategy} interface 280 * to use for this exporter. Default is a {@code KeyNamingStrategy}. 281 * @see org.springframework.jmx.export.naming.KeyNamingStrategy 282 * @see org.springframework.jmx.export.naming.MetadataNamingStrategy 283 */ 284 public void setNamingStrategy(ObjectNamingStrategy namingStrategy) { 285 this.namingStrategy = namingStrategy; 286 } 287 288 /** 289 * Indicates whether Spring should ensure that {@link ObjectName ObjectNames} 290 * generated by the configured {@link ObjectNamingStrategy} for 291 * runtime-registered MBeans ({@link #registerManagedResource}) should get 292 * modified: to ensure uniqueness for every instance of a managed {@code Class}. 293 * <p>The default value is {@code true}. 294 * @see #registerManagedResource 295 * @see JmxUtils#appendIdentityToObjectName(javax.management.ObjectName, Object) 296 */ 297 public void setEnsureUniqueRuntimeObjectNames(boolean ensureUniqueRuntimeObjectNames) { 298 this.ensureUniqueRuntimeObjectNames = ensureUniqueRuntimeObjectNames; 299 } 300 301 /** 302 * Indicates whether or not the managed resource should be exposed on the 303 * {@link Thread#getContextClassLoader() thread context ClassLoader} before 304 * allowing any invocations on the MBean to occur. 305 * <p>The default value is {@code true}, exposing a {@link SpringModelMBean} 306 * which performs thread context ClassLoader management. Switch this flag off to 307 * expose a standard JMX {@link javax.management.modelmbean.RequiredModelMBean}. 308 */ 309 public void setExposeManagedResourceClassLoader(boolean exposeManagedResourceClassLoader) { 310 this.exposeManagedResourceClassLoader = exposeManagedResourceClassLoader; 311 } 312 313 /** 314 * Set the list of names for beans that should be excluded from autodetection. 315 */ 316 public void setExcludedBeans(String... excludedBeans) { 317 this.excludedBeans.clear(); 318 if (excludedBeans != null) { 319 this.excludedBeans.addAll(Arrays.asList(excludedBeans)); 320 } 321 } 322 323 /** 324 * Add the name of bean that should be excluded from autodetection. 325 */ 326 public void addExcludedBean(String excludedBean) { 327 Assert.notNull(excludedBean, "ExcludedBean must not be null"); 328 this.excludedBeans.add(excludedBean); 329 } 330 331 /** 332 * Set the {@code MBeanExporterListener}s that should be notified 333 * of MBean registration and unregistration events. 334 * @see MBeanExporterListener 335 */ 336 public void setListeners(MBeanExporterListener... listeners) { 337 this.listeners = listeners; 338 } 339 340 /** 341 * Set the {@link NotificationListenerBean NotificationListenerBeans} 342 * containing the 343 * {@link javax.management.NotificationListener NotificationListeners} 344 * that will be registered with the {@link MBeanServer}. 345 * @see #setNotificationListenerMappings(java.util.Map) 346 * @see NotificationListenerBean 347 */ 348 public void setNotificationListeners(NotificationListenerBean... notificationListeners) { 349 this.notificationListeners = notificationListeners; 350 } 351 352 /** 353 * Set the {@link NotificationListener NotificationListeners} to register 354 * with the {@link javax.management.MBeanServer}. 355 * <P>The key of each entry in the {@code Map} is a {@link String} 356 * representation of the {@link javax.management.ObjectName} or the bean 357 * name of the MBean the listener should be registered for. Specifying an 358 * asterisk ({@code *}) for a key will cause the listener to be 359 * associated with all MBeans registered by this class at startup time. 360 * <p>The value of each entry is the 361 * {@link javax.management.NotificationListener} to register. For more 362 * advanced options such as registering 363 * {@link javax.management.NotificationFilter NotificationFilters} and 364 * handback objects see {@link #setNotificationListeners(NotificationListenerBean[])}. 365 */ 366 public void setNotificationListenerMappings(Map<?, ? extends NotificationListener> listeners) { 367 Assert.notNull(listeners, "'listeners' must not be null"); 368 List<NotificationListenerBean> notificationListeners = 369 new ArrayList<NotificationListenerBean>(listeners.size()); 370 371 for (Map.Entry<?, ? extends NotificationListener> entry : listeners.entrySet()) { 372 // Get the listener from the map value. 373 NotificationListenerBean bean = new NotificationListenerBean(entry.getValue()); 374 // Get the ObjectName from the map key. 375 Object key = entry.getKey(); 376 if (key != null && !WILDCARD.equals(key)) { 377 // This listener is mapped to a specific ObjectName. 378 bean.setMappedObjectName(entry.getKey()); 379 } 380 notificationListeners.add(bean); 381 } 382 383 this.notificationListeners = 384 notificationListeners.toArray(new NotificationListenerBean[notificationListeners.size()]); 385 } 386 387 @Override 388 public void setBeanClassLoader(ClassLoader classLoader) { 389 this.beanClassLoader = classLoader; 390 } 391 392 /** 393 * This callback is only required for resolution of bean names in the 394 * {@link #setBeans(java.util.Map) "beans"} {@link Map} and for 395 * autodetection of MBeans (in the latter case, a 396 * {@code ListableBeanFactory} is required). 397 * @see #setBeans 398 * @see #setAutodetect 399 */ 400 @Override 401 public void setBeanFactory(BeanFactory beanFactory) { 402 if (beanFactory instanceof ListableBeanFactory) { 403 this.beanFactory = (ListableBeanFactory) beanFactory; 404 } 405 else { 406 logger.info("MBeanExporter not running in a ListableBeanFactory: autodetection of MBeans not available."); 407 } 408 } 409 410 411 //--------------------------------------------------------------------- 412 // Lifecycle in bean factory: automatically register/unregister beans 413 //--------------------------------------------------------------------- 414 415 @Override 416 public void afterPropertiesSet() { 417 // If no server was provided then try to find one. This is useful in an environment 418 // where there is already an MBeanServer loaded. 419 if (this.server == null) { 420 this.server = JmxUtils.locateMBeanServer(); 421 } 422 } 423 424 /** 425 * Kick off bean registration automatically after the regular singleton instantiation phase. 426 * @see #registerBeans() 427 */ 428 @Override 429 public void afterSingletonsInstantiated() { 430 try { 431 logger.info("Registering beans for JMX exposure on startup"); 432 registerBeans(); 433 registerNotificationListeners(); 434 } 435 catch (RuntimeException ex) { 436 // Unregister beans already registered by this exporter. 437 unregisterNotificationListeners(); 438 unregisterBeans(); 439 throw ex; 440 } 441 } 442 443 /** 444 * Unregisters all beans that this exported has exposed via JMX 445 * when the enclosing {@code ApplicationContext} is destroyed. 446 */ 447 @Override 448 public void destroy() { 449 logger.info("Unregistering JMX-exposed beans on shutdown"); 450 unregisterNotificationListeners(); 451 unregisterBeans(); 452 } 453 454 455 //--------------------------------------------------------------------- 456 // Implementation of MBeanExportOperations interface 457 //--------------------------------------------------------------------- 458 459 @Override 460 public ObjectName registerManagedResource(Object managedResource) throws MBeanExportException { 461 Assert.notNull(managedResource, "Managed resource must not be null"); 462 ObjectName objectName; 463 try { 464 objectName = getObjectName(managedResource, null); 465 if (this.ensureUniqueRuntimeObjectNames) { 466 objectName = JmxUtils.appendIdentityToObjectName(objectName, managedResource); 467 } 468 } 469 catch (Throwable ex) { 470 throw new MBeanExportException("Unable to generate ObjectName for MBean [" + managedResource + "]", ex); 471 } 472 registerManagedResource(managedResource, objectName); 473 return objectName; 474 } 475 476 @Override 477 public void registerManagedResource(Object managedResource, ObjectName objectName) throws MBeanExportException { 478 Assert.notNull(managedResource, "Managed resource must not be null"); 479 Assert.notNull(objectName, "ObjectName must not be null"); 480 try { 481 if (isMBean(managedResource.getClass())) { 482 doRegister(managedResource, objectName); 483 } 484 else { 485 ModelMBean mbean = createAndConfigureMBean(managedResource, managedResource.getClass().getName()); 486 doRegister(mbean, objectName); 487 injectNotificationPublisherIfNecessary(managedResource, mbean, objectName); 488 } 489 } 490 catch (JMException ex) { 491 throw new UnableToRegisterMBeanException( 492 "Unable to register MBean [" + managedResource + "] with object name [" + objectName + "]", ex); 493 } 494 } 495 496 @Override 497 public void unregisterManagedResource(ObjectName objectName) { 498 Assert.notNull(objectName, "ObjectName must not be null"); 499 doUnregister(objectName); 500 } 501 502 503 //--------------------------------------------------------------------- 504 // Exporter implementation 505 //--------------------------------------------------------------------- 506 507 /** 508 * Register the defined beans with the {@link MBeanServer}. 509 * <p>Each bean is exposed to the {@code MBeanServer} via a 510 * {@code ModelMBean}. The actual implemetation of the 511 * {@code ModelMBean} interface used depends on the implementation of 512 * the {@code ModelMBeanProvider} interface that is configured. By 513 * default the {@code RequiredModelMBean} class that is supplied with 514 * all JMX implementations is used. 515 * <p>The management interface produced for each bean is dependent on the 516 * {@code MBeanInfoAssembler} implementation being used. The 517 * {@code ObjectName} given to each bean is dependent on the 518 * implementation of the {@code ObjectNamingStrategy} interface being used. 519 */ 520 protected void registerBeans() { 521 // The beans property may be null, for example if we are relying solely on autodetection. 522 if (this.beans == null) { 523 this.beans = new HashMap<String, Object>(); 524 // Use AUTODETECT_ALL as default in no beans specified explicitly. 525 if (this.autodetectMode == null) { 526 this.autodetectMode = AUTODETECT_ALL; 527 } 528 } 529 530 // Perform autodetection, if desired. 531 int mode = (this.autodetectMode != null ? this.autodetectMode : AUTODETECT_NONE); 532 if (mode != AUTODETECT_NONE) { 533 if (this.beanFactory == null) { 534 throw new MBeanExportException("Cannot autodetect MBeans if not running in a BeanFactory"); 535 } 536 if (mode == AUTODETECT_MBEAN || mode == AUTODETECT_ALL) { 537 // Autodetect any beans that are already MBeans. 538 logger.debug("Autodetecting user-defined JMX MBeans"); 539 autodetectMBeans(); 540 } 541 // Allow the assembler a chance to vote for bean inclusion. 542 if ((mode == AUTODETECT_ASSEMBLER || mode == AUTODETECT_ALL) && 543 this.assembler instanceof AutodetectCapableMBeanInfoAssembler) { 544 autodetectBeans((AutodetectCapableMBeanInfoAssembler) this.assembler); 545 } 546 } 547 548 if (!this.beans.isEmpty()) { 549 for (Map.Entry<String, Object> entry : this.beans.entrySet()) { 550 registerBeanNameOrInstance(entry.getValue(), entry.getKey()); 551 } 552 } 553 } 554 555 /** 556 * Return whether the specified bean definition should be considered as lazy-init. 557 * @param beanFactory the bean factory that is supposed to contain the bean definition 558 * @param beanName the name of the bean to check 559 * @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory#getBeanDefinition 560 * @see org.springframework.beans.factory.config.BeanDefinition#isLazyInit 561 */ 562 protected boolean isBeanDefinitionLazyInit(ListableBeanFactory beanFactory, String beanName) { 563 return (beanFactory instanceof ConfigurableListableBeanFactory && beanFactory.containsBeanDefinition(beanName) && 564 ((ConfigurableListableBeanFactory) beanFactory).getBeanDefinition(beanName).isLazyInit()); 565 } 566 567 /** 568 * Register an individual bean with the {@link #setServer MBeanServer}. 569 * <p>This method is responsible for deciding <strong>how</strong> a bean 570 * should be exposed to the {@code MBeanServer}. Specifically, if the 571 * supplied {@code mapValue} is the name of a bean that is configured 572 * for lazy initialization, then a proxy to the resource is registered with 573 * the {@code MBeanServer} so that the lazy load behavior is 574 * honored. If the bean is already an MBean then it will be registered 575 * directly with the {@code MBeanServer} without any intervention. For 576 * all other beans or bean names, the resource itself is registered with 577 * the {@code MBeanServer} directly. 578 * @param mapValue the value configured for this bean in the beans map; 579 * may be either the {@code String} name of a bean, or the bean itself 580 * @param beanKey the key associated with this bean in the beans map 581 * @return the {@code ObjectName} under which the resource was registered, 582 * or {@code null} if the actual resource was {@code null} as well 583 * @throws MBeanExportException if the export failed 584 * @see #setBeans 585 * @see #registerBeanInstance 586 * @see #registerLazyInit 587 */ 588 protected ObjectName registerBeanNameOrInstance(Object mapValue, String beanKey) throws MBeanExportException { 589 try { 590 if (mapValue instanceof String) { 591 // Bean name pointing to a potentially lazy-init bean in the factory. 592 if (this.beanFactory == null) { 593 throw new MBeanExportException("Cannot resolve bean names if not running in a BeanFactory"); 594 } 595 String beanName = (String) mapValue; 596 if (isBeanDefinitionLazyInit(this.beanFactory, beanName)) { 597 ObjectName objectName = registerLazyInit(beanName, beanKey); 598 replaceNotificationListenerBeanNameKeysIfNecessary(beanName, objectName); 599 return objectName; 600 } 601 else { 602 Object bean = this.beanFactory.getBean(beanName); 603 if (bean != null) { 604 ObjectName objectName = registerBeanInstance(bean, beanKey); 605 replaceNotificationListenerBeanNameKeysIfNecessary(beanName, objectName); 606 return objectName; 607 } 608 } 609 } 610 else if (mapValue != null) { 611 // Plain bean instance -> register it directly. 612 if (this.beanFactory != null) { 613 Map<String, ?> beansOfSameType = 614 this.beanFactory.getBeansOfType(mapValue.getClass(), false, this.allowEagerInit); 615 for (Map.Entry<String, ?> entry : beansOfSameType.entrySet()) { 616 if (entry.getValue() == mapValue) { 617 String beanName = entry.getKey(); 618 ObjectName objectName = registerBeanInstance(mapValue, beanKey); 619 replaceNotificationListenerBeanNameKeysIfNecessary(beanName, objectName); 620 return objectName; 621 } 622 } 623 } 624 return registerBeanInstance(mapValue, beanKey); 625 } 626 } 627 catch (Throwable ex) { 628 throw new UnableToRegisterMBeanException( 629 "Unable to register MBean [" + mapValue + "] with key '" + beanKey + "'", ex); 630 } 631 return null; 632 } 633 634 /** 635 * Replace any bean names used as keys in the {@code NotificationListener} 636 * mappings with their corresponding {@code ObjectName} values. 637 * @param beanName the name of the bean to be registered 638 * @param objectName the {@code ObjectName} under which the bean will be registered 639 * with the {@code MBeanServer} 640 */ 641 private void replaceNotificationListenerBeanNameKeysIfNecessary(String beanName, ObjectName objectName) { 642 if (this.notificationListeners != null) { 643 for (NotificationListenerBean notificationListener : this.notificationListeners) { 644 notificationListener.replaceObjectName(beanName, objectName); 645 } 646 } 647 } 648 649 /** 650 * Registers an existing MBean or an MBean adapter for a plain bean 651 * with the {@code MBeanServer}. 652 * @param bean the bean to register, either an MBean or a plain bean 653 * @param beanKey the key associated with this bean in the beans map 654 * @return the {@code ObjectName} under which the bean was registered 655 * with the {@code MBeanServer} 656 */ 657 private ObjectName registerBeanInstance(Object bean, String beanKey) throws JMException { 658 ObjectName objectName = getObjectName(bean, beanKey); 659 Object mbeanToExpose = null; 660 if (isMBean(bean.getClass())) { 661 mbeanToExpose = bean; 662 } 663 else { 664 DynamicMBean adaptedBean = adaptMBeanIfPossible(bean); 665 if (adaptedBean != null) { 666 mbeanToExpose = adaptedBean; 667 } 668 } 669 670 if (mbeanToExpose != null) { 671 if (logger.isInfoEnabled()) { 672 logger.info("Located MBean '" + beanKey + "': registering with JMX server as MBean [" + 673 objectName + "]"); 674 } 675 doRegister(mbeanToExpose, objectName); 676 } 677 else { 678 if (logger.isInfoEnabled()) { 679 logger.info("Located managed bean '" + beanKey + "': registering with JMX server as MBean [" + 680 objectName + "]"); 681 } 682 ModelMBean mbean = createAndConfigureMBean(bean, beanKey); 683 doRegister(mbean, objectName); 684 injectNotificationPublisherIfNecessary(bean, mbean, objectName); 685 } 686 687 return objectName; 688 } 689 690 /** 691 * Register beans that are configured for lazy initialization with the 692 * {@code MBeanServer} indirectly through a proxy. 693 * @param beanName the name of the bean in the {@code BeanFactory} 694 * @param beanKey the key associated with this bean in the beans map 695 * @return the {@code ObjectName} under which the bean was registered 696 * with the {@code MBeanServer} 697 */ 698 private ObjectName registerLazyInit(String beanName, String beanKey) throws JMException { 699 ProxyFactory proxyFactory = new ProxyFactory(); 700 proxyFactory.setProxyTargetClass(true); 701 proxyFactory.setFrozen(true); 702 703 if (isMBean(this.beanFactory.getType(beanName))) { 704 // A straight MBean... Let's create a simple lazy-init CGLIB proxy for it. 705 LazyInitTargetSource targetSource = new LazyInitTargetSource(); 706 targetSource.setTargetBeanName(beanName); 707 targetSource.setBeanFactory(this.beanFactory); 708 proxyFactory.setTargetSource(targetSource); 709 710 Object proxy = proxyFactory.getProxy(this.beanClassLoader); 711 ObjectName objectName = getObjectName(proxy, beanKey); 712 if (logger.isDebugEnabled()) { 713 logger.debug("Located MBean '" + beanKey + "': registering with JMX server as lazy-init MBean [" + 714 objectName + "]"); 715 } 716 doRegister(proxy, objectName); 717 return objectName; 718 } 719 720 else { 721 // A simple bean... Let's create a lazy-init ModelMBean proxy with notification support. 722 NotificationPublisherAwareLazyTargetSource targetSource = new NotificationPublisherAwareLazyTargetSource(); 723 targetSource.setTargetBeanName(beanName); 724 targetSource.setBeanFactory(this.beanFactory); 725 proxyFactory.setTargetSource(targetSource); 726 727 Object proxy = proxyFactory.getProxy(this.beanClassLoader); 728 ObjectName objectName = getObjectName(proxy, beanKey); 729 if (logger.isDebugEnabled()) { 730 logger.debug("Located simple bean '" + beanKey + "': registering with JMX server as lazy-init MBean [" + 731 objectName + "]"); 732 } 733 ModelMBean mbean = createAndConfigureMBean(proxy, beanKey); 734 targetSource.setModelMBean(mbean); 735 targetSource.setObjectName(objectName); 736 doRegister(mbean, objectName); 737 return objectName; 738 } 739 } 740 741 /** 742 * Retrieve the {@code ObjectName} for a bean. 743 * <p>If the bean implements the {@code SelfNaming} interface, then the 744 * {@code ObjectName} will be retrieved using {@code SelfNaming.getObjectName()}. 745 * Otherwise, the configured {@code ObjectNamingStrategy} is used. 746 * @param bean the name of the bean in the {@code BeanFactory} 747 * @param beanKey the key associated with the bean in the beans map 748 * @return the {@code ObjectName} for the supplied bean 749 * @throws javax.management.MalformedObjectNameException 750 * if the retrieved {@code ObjectName} is malformed 751 */ 752 protected ObjectName getObjectName(Object bean, String beanKey) throws MalformedObjectNameException { 753 if (bean instanceof SelfNaming) { 754 return ((SelfNaming) bean).getObjectName(); 755 } 756 else { 757 return this.namingStrategy.getObjectName(bean, beanKey); 758 } 759 } 760 761 /** 762 * Determine whether the given bean class qualifies as an MBean as-is. 763 * <p>The default implementation delegates to {@link JmxUtils#isMBean}, 764 * which checks for {@link javax.management.DynamicMBean} classes as well 765 * as classes with corresponding "*MBean" interface (Standard MBeans) 766 * or corresponding "*MXBean" interface (Java 6 MXBeans). 767 * @param beanClass the bean class to analyze 768 * @return whether the class qualifies as an MBean 769 * @see org.springframework.jmx.support.JmxUtils#isMBean(Class) 770 */ 771 protected boolean isMBean(Class<?> beanClass) { 772 return JmxUtils.isMBean(beanClass); 773 } 774 775 /** 776 * Build an adapted MBean for the given bean instance, if possible. 777 * <p>The default implementation builds a JMX 1.2 StandardMBean 778 * for the target's MBean/MXBean interface in case of an AOP proxy, 779 * delegating the interface's management operations to the proxy. 780 * @param bean the original bean instance 781 * @return the adapted MBean, or {@code null} if not possible 782 */ 783 @SuppressWarnings("unchecked") 784 protected DynamicMBean adaptMBeanIfPossible(Object bean) throws JMException { 785 Class<?> targetClass = AopUtils.getTargetClass(bean); 786 if (targetClass != bean.getClass()) { 787 Class<?> ifc = JmxUtils.getMXBeanInterface(targetClass); 788 if (ifc != null) { 789 if (!ifc.isInstance(bean)) { 790 throw new NotCompliantMBeanException("Managed bean [" + bean + 791 "] has a target class with an MXBean interface but does not expose it in the proxy"); 792 } 793 return new StandardMBean(bean, ((Class<Object>) ifc), true); 794 } 795 else { 796 ifc = JmxUtils.getMBeanInterface(targetClass); 797 if (ifc != null) { 798 if (!ifc.isInstance(bean)) { 799 throw new NotCompliantMBeanException("Managed bean [" + bean + 800 "] has a target class with an MBean interface but does not expose it in the proxy"); 801 } 802 return new StandardMBean(bean, ((Class<Object>) ifc)); 803 } 804 } 805 } 806 return null; 807 } 808 809 /** 810 * Creates an MBean that is configured with the appropriate management 811 * interface for the supplied managed resource. 812 * @param managedResource the resource that is to be exported as an MBean 813 * @param beanKey the key associated with the managed bean 814 * @see #createModelMBean() 815 * @see #getMBeanInfo(Object, String) 816 */ 817 protected ModelMBean createAndConfigureMBean(Object managedResource, String beanKey) 818 throws MBeanExportException { 819 try { 820 ModelMBean mbean = createModelMBean(); 821 mbean.setModelMBeanInfo(getMBeanInfo(managedResource, beanKey)); 822 mbean.setManagedResource(managedResource, MR_TYPE_OBJECT_REFERENCE); 823 return mbean; 824 } 825 catch (Throwable ex) { 826 throw new MBeanExportException("Could not create ModelMBean for managed resource [" + 827 managedResource + "] with key '" + beanKey + "'", ex); 828 } 829 } 830 831 /** 832 * Create an instance of a class that implements {@code ModelMBean}. 833 * <p>This method is called to obtain a {@code ModelMBean} instance to 834 * use when registering a bean. This method is called once per bean during the 835 * registration phase and must return a new instance of {@code ModelMBean} 836 * @return a new instance of a class that implements {@code ModelMBean} 837 * @throws javax.management.MBeanException if creation of the ModelMBean failed 838 */ 839 protected ModelMBean createModelMBean() throws MBeanException { 840 return (this.exposeManagedResourceClassLoader ? new SpringModelMBean() : new RequiredModelMBean()); 841 } 842 843 /** 844 * Gets the {@code ModelMBeanInfo} for the bean with the supplied key 845 * and of the supplied type. 846 */ 847 private ModelMBeanInfo getMBeanInfo(Object managedBean, String beanKey) throws JMException { 848 ModelMBeanInfo info = this.assembler.getMBeanInfo(managedBean, beanKey); 849 if (logger.isWarnEnabled() && ObjectUtils.isEmpty(info.getAttributes()) && 850 ObjectUtils.isEmpty(info.getOperations())) { 851 logger.warn("Bean with key '" + beanKey + 852 "' has been registered as an MBean but has no exposed attributes or operations"); 853 } 854 return info; 855 } 856 857 858 //--------------------------------------------------------------------- 859 // Autodetection process 860 //--------------------------------------------------------------------- 861 862 /** 863 * Invoked when using an {@code AutodetectCapableMBeanInfoAssembler}. 864 * Gives the assembler the opportunity to add additional beans from the 865 * {@code BeanFactory} to the list of beans to be exposed via JMX. 866 * <p>This implementation prevents a bean from being added to the list 867 * automatically if it has already been added manually, and it prevents 868 * certain internal classes from being registered automatically. 869 */ 870 private void autodetectBeans(final AutodetectCapableMBeanInfoAssembler assembler) { 871 autodetect(new AutodetectCallback() { 872 @Override 873 public boolean include(Class<?> beanClass, String beanName) { 874 return assembler.includeBean(beanClass, beanName); 875 } 876 }); 877 } 878 879 /** 880 * Attempts to detect any beans defined in the {@code ApplicationContext} that are 881 * valid MBeans and registers them automatically with the {@code MBeanServer}. 882 */ 883 private void autodetectMBeans() { 884 autodetect(new AutodetectCallback() { 885 @Override 886 public boolean include(Class<?> beanClass, String beanName) { 887 return isMBean(beanClass); 888 } 889 }); 890 } 891 892 /** 893 * Performs the actual autodetection process, delegating to an 894 * {@code AutodetectCallback} instance to vote on the inclusion of a 895 * given bean. 896 * @param callback the {@code AutodetectCallback} to use when deciding 897 * whether to include a bean or not 898 */ 899 private void autodetect(AutodetectCallback callback) { 900 Set<String> beanNames = new LinkedHashSet<String>(this.beanFactory.getBeanDefinitionCount()); 901 beanNames.addAll(Arrays.asList(this.beanFactory.getBeanDefinitionNames())); 902 if (this.beanFactory instanceof ConfigurableBeanFactory) { 903 beanNames.addAll(Arrays.asList(((ConfigurableBeanFactory) this.beanFactory).getSingletonNames())); 904 } 905 for (String beanName : beanNames) { 906 if (!isExcluded(beanName) && !isBeanDefinitionAbstract(this.beanFactory, beanName)) { 907 try { 908 Class<?> beanClass = this.beanFactory.getType(beanName); 909 if (beanClass != null && callback.include(beanClass, beanName)) { 910 boolean lazyInit = isBeanDefinitionLazyInit(this.beanFactory, beanName); 911 Object beanInstance = (!lazyInit ? this.beanFactory.getBean(beanName) : null); 912 if (!ScopedProxyUtils.isScopedTarget(beanName) && !this.beans.containsValue(beanName) && 913 (beanInstance == null || 914 !CollectionUtils.containsInstance(this.beans.values(), beanInstance))) { 915 // Not already registered for JMX exposure. 916 this.beans.put(beanName, (beanInstance != null ? beanInstance : beanName)); 917 if (logger.isInfoEnabled()) { 918 logger.info("Bean with name '" + beanName + "' has been autodetected for JMX exposure"); 919 } 920 } 921 else { 922 if (logger.isDebugEnabled()) { 923 logger.debug("Bean with name '" + beanName + "' is already registered for JMX exposure"); 924 } 925 } 926 } 927 } 928 catch (CannotLoadBeanClassException ex) { 929 if (this.allowEagerInit) { 930 throw ex; 931 } 932 // otherwise ignore beans where the class is not resolvable 933 } 934 } 935 } 936 } 937 938 /** 939 * Indicates whether or not a particular bean name is present in the excluded beans list. 940 */ 941 private boolean isExcluded(String beanName) { 942 return (this.excludedBeans.contains(beanName) || 943 (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX) && 944 this.excludedBeans.contains(beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length())))); 945 } 946 947 /** 948 * Return whether the specified bean definition should be considered as abstract. 949 */ 950 private boolean isBeanDefinitionAbstract(ListableBeanFactory beanFactory, String beanName) { 951 return (beanFactory instanceof ConfigurableListableBeanFactory && beanFactory.containsBeanDefinition(beanName) && 952 ((ConfigurableListableBeanFactory) beanFactory).getBeanDefinition(beanName).isAbstract()); 953 } 954 955 956 //--------------------------------------------------------------------- 957 // Notification and listener management 958 //--------------------------------------------------------------------- 959 960 /** 961 * If the supplied managed resource implements the {@link NotificationPublisherAware} an instance of 962 * {@link org.springframework.jmx.export.notification.NotificationPublisher} is injected. 963 */ 964 private void injectNotificationPublisherIfNecessary( 965 Object managedResource, ModelMBean modelMBean, ObjectName objectName) { 966 967 if (managedResource instanceof NotificationPublisherAware) { 968 ((NotificationPublisherAware) managedResource).setNotificationPublisher( 969 new ModelMBeanNotificationPublisher(modelMBean, objectName, managedResource)); 970 } 971 } 972 973 /** 974 * Register the configured {@link NotificationListener NotificationListeners} 975 * with the {@link MBeanServer}. 976 */ 977 private void registerNotificationListeners() throws MBeanExportException { 978 if (this.notificationListeners != null) { 979 for (NotificationListenerBean bean : this.notificationListeners) { 980 try { 981 ObjectName[] mappedObjectNames = bean.getResolvedObjectNames(); 982 if (mappedObjectNames == null) { 983 // Mapped to all MBeans registered by the MBeanExporter. 984 mappedObjectNames = getRegisteredObjectNames(); 985 } 986 if (this.registeredNotificationListeners.put(bean, mappedObjectNames) == null) { 987 for (ObjectName mappedObjectName : mappedObjectNames) { 988 this.server.addNotificationListener(mappedObjectName, bean.getNotificationListener(), 989 bean.getNotificationFilter(), bean.getHandback()); 990 } 991 } 992 } 993 catch (Throwable ex) { 994 throw new MBeanExportException("Unable to register NotificationListener", ex); 995 } 996 } 997 } 998 } 999 1000 /** 1001 * Unregister the configured {@link NotificationListener NotificationListeners} 1002 * from the {@link MBeanServer}. 1003 */ 1004 private void unregisterNotificationListeners() { 1005 for (Map.Entry<NotificationListenerBean, ObjectName[]> entry : this.registeredNotificationListeners.entrySet()) { 1006 NotificationListenerBean bean = entry.getKey(); 1007 ObjectName[] mappedObjectNames = entry.getValue(); 1008 for (ObjectName mappedObjectName : mappedObjectNames) { 1009 try { 1010 this.server.removeNotificationListener(mappedObjectName, bean.getNotificationListener(), 1011 bean.getNotificationFilter(), bean.getHandback()); 1012 } 1013 catch (Throwable ex) { 1014 if (logger.isDebugEnabled()) { 1015 logger.debug("Unable to unregister NotificationListener", ex); 1016 } 1017 } 1018 } 1019 } 1020 this.registeredNotificationListeners.clear(); 1021 } 1022 1023 /** 1024 * Called when an MBean is registered. Notifies all registered 1025 * {@link MBeanExporterListener MBeanExporterListeners} of the registration event. 1026 * <p>Please note that if an {@link MBeanExporterListener} throws a (runtime) 1027 * exception when notified, this will essentially interrupt the notification process 1028 * and any remaining listeners that have yet to be notified will not (obviously) 1029 * receive the {@link MBeanExporterListener#mbeanRegistered(javax.management.ObjectName)} 1030 * callback. 1031 * @param objectName the {@code ObjectName} of the registered MBean 1032 */ 1033 @Override 1034 protected void onRegister(ObjectName objectName) { 1035 notifyListenersOfRegistration(objectName); 1036 } 1037 1038 /** 1039 * Called when an MBean is unregistered. Notifies all registered 1040 * {@link MBeanExporterListener MBeanExporterListeners} of the unregistration event. 1041 * <p>Please note that if an {@link MBeanExporterListener} throws a (runtime) 1042 * exception when notified, this will essentially interrupt the notification process 1043 * and any remaining listeners that have yet to be notified will not (obviously) 1044 * receive the {@link MBeanExporterListener#mbeanUnregistered(javax.management.ObjectName)} 1045 * callback. 1046 * @param objectName the {@code ObjectName} of the unregistered MBean 1047 */ 1048 @Override 1049 protected void onUnregister(ObjectName objectName) { 1050 notifyListenersOfUnregistration(objectName); 1051 } 1052 1053 1054 /** 1055 * Notifies all registered {@link MBeanExporterListener MBeanExporterListeners} of the 1056 * registration of the MBean identified by the supplied {@link ObjectName}. 1057 */ 1058 private void notifyListenersOfRegistration(ObjectName objectName) { 1059 if (this.listeners != null) { 1060 for (MBeanExporterListener listener : this.listeners) { 1061 listener.mbeanRegistered(objectName); 1062 } 1063 } 1064 } 1065 1066 /** 1067 * Notifies all registered {@link MBeanExporterListener MBeanExporterListeners} of the 1068 * unregistration of the MBean identified by the supplied {@link ObjectName}. 1069 */ 1070 private void notifyListenersOfUnregistration(ObjectName objectName) { 1071 if (this.listeners != null) { 1072 for (MBeanExporterListener listener : this.listeners) { 1073 listener.mbeanUnregistered(objectName); 1074 } 1075 } 1076 } 1077 1078 1079 //--------------------------------------------------------------------- 1080 // Inner classes for internal use 1081 //--------------------------------------------------------------------- 1082 1083 /** 1084 * Internal callback interface for the autodetection process. 1085 */ 1086 private static interface AutodetectCallback { 1087 1088 /** 1089 * Called during the autodetection process to decide whether 1090 * or not a bean should be included. 1091 * @param beanClass the class of the bean 1092 * @param beanName the name of the bean 1093 */ 1094 boolean include(Class<?> beanClass, String beanName); 1095 } 1096 1097 1098 /** 1099 * Extension of {@link LazyInitTargetSource} that will inject a 1100 * {@link org.springframework.jmx.export.notification.NotificationPublisher} 1101 * into the lazy resource as it is created if required. 1102 */ 1103 @SuppressWarnings("serial") 1104 private class NotificationPublisherAwareLazyTargetSource extends LazyInitTargetSource { 1105 1106 private ModelMBean modelMBean; 1107 1108 private ObjectName objectName; 1109 1110 public void setModelMBean(ModelMBean modelMBean) { 1111 this.modelMBean = modelMBean; 1112 } 1113 1114 public void setObjectName(ObjectName objectName) { 1115 this.objectName = objectName; 1116 } 1117 1118 @Override 1119 public Object getTarget() { 1120 try { 1121 return super.getTarget(); 1122 } 1123 catch (RuntimeException ex) { 1124 if (logger.isWarnEnabled()) { 1125 logger.warn("Failed to retrieve target for JMX-exposed bean [" + this.objectName + "]: " + ex); 1126 } 1127 throw ex; 1128 } 1129 } 1130 1131 @Override 1132 protected void postProcessTargetObject(Object targetObject) { 1133 injectNotificationPublisherIfNecessary(targetObject, this.modelMBean, this.objectName); 1134 } 1135 } 1136 1137}