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.support; 018 019import java.util.LinkedHashSet; 020import java.util.Set; 021 022import javax.management.InstanceAlreadyExistsException; 023import javax.management.InstanceNotFoundException; 024import javax.management.JMException; 025import javax.management.MBeanServer; 026import javax.management.ObjectInstance; 027import javax.management.ObjectName; 028 029import org.apache.commons.logging.Log; 030import org.apache.commons.logging.LogFactory; 031 032import org.springframework.lang.Nullable; 033import org.springframework.util.Assert; 034 035/** 036 * Provides supporting infrastructure for registering MBeans with an 037 * {@link javax.management.MBeanServer}. The behavior when encountering 038 * an existing MBean at a given {@link ObjectName} is fully configurable 039 * allowing for flexible registration settings. 040 * 041 * <p>All registered MBeans are tracked and can be unregistered by calling 042 * the #{@link #unregisterBeans()} method. 043 * 044 * <p>Sub-classes can receive notifications when an MBean is registered or 045 * unregistered by overriding the {@link #onRegister(ObjectName)} and 046 * {@link #onUnregister(ObjectName)} methods respectively. 047 * 048 * <p>By default, the registration process will fail if attempting to 049 * register an MBean using a {@link javax.management.ObjectName} that is 050 * already used. 051 * 052 * <p>By setting the {@link #setRegistrationPolicy(RegistrationPolicy) registrationPolicy} 053 * property to {@link RegistrationPolicy#IGNORE_EXISTING} the registration process 054 * will simply ignore existing MBeans leaving them registered. This is useful in settings 055 * where multiple applications want to share a common MBean in a shared {@link MBeanServer}. 056 * 057 * <p>Setting {@link #setRegistrationPolicy(RegistrationPolicy) registrationPolicy} property 058 * to {@link RegistrationPolicy#REPLACE_EXISTING} will cause existing MBeans to be replaced 059 * during registration if necessary. This is useful in situations where you can't guarantee 060 * the state of your {@link MBeanServer}. 061 * 062 * @author Rob Harrop 063 * @author Juergen Hoeller 064 * @author Phillip Webb 065 * @since 2.0 066 * @see #setServer 067 * @see #setRegistrationPolicy 068 * @see org.springframework.jmx.export.MBeanExporter 069 */ 070public class MBeanRegistrationSupport { 071 072 /** 073 * {@code Log} instance for this class. 074 */ 075 protected final Log logger = LogFactory.getLog(getClass()); 076 077 /** 078 * The {@code MBeanServer} instance being used to register beans. 079 */ 080 @Nullable 081 protected MBeanServer server; 082 083 /** 084 * The beans that have been registered by this exporter. 085 */ 086 private final Set<ObjectName> registeredBeans = new LinkedHashSet<>(); 087 088 /** 089 * The policy used when registering an MBean and finding that it already exists. 090 * By default an exception is raised. 091 */ 092 private RegistrationPolicy registrationPolicy = RegistrationPolicy.FAIL_ON_EXISTING; 093 094 095 /** 096 * Specify the {@code MBeanServer} instance with which all beans should 097 * be registered. The {@code MBeanExporter} will attempt to locate an 098 * existing {@code MBeanServer} if none is supplied. 099 */ 100 public void setServer(@Nullable MBeanServer server) { 101 this.server = server; 102 } 103 104 /** 105 * Return the {@code MBeanServer} that the beans will be registered with. 106 */ 107 @Nullable 108 public final MBeanServer getServer() { 109 return this.server; 110 } 111 112 /** 113 * The policy to use when attempting to register an MBean 114 * under an {@link javax.management.ObjectName} that already exists. 115 * @param registrationPolicy the policy to use 116 * @since 3.2 117 */ 118 public void setRegistrationPolicy(RegistrationPolicy registrationPolicy) { 119 Assert.notNull(registrationPolicy, "RegistrationPolicy must not be null"); 120 this.registrationPolicy = registrationPolicy; 121 } 122 123 124 /** 125 * Actually register the MBean with the server. The behavior when encountering 126 * an existing MBean can be configured using {@link #setRegistrationPolicy}. 127 * @param mbean the MBean instance 128 * @param objectName the suggested ObjectName for the MBean 129 * @throws JMException if the registration failed 130 */ 131 protected void doRegister(Object mbean, ObjectName objectName) throws JMException { 132 Assert.state(this.server != null, "No MBeanServer set"); 133 ObjectName actualObjectName; 134 135 synchronized (this.registeredBeans) { 136 ObjectInstance registeredBean = null; 137 try { 138 registeredBean = this.server.registerMBean(mbean, objectName); 139 } 140 catch (InstanceAlreadyExistsException ex) { 141 if (this.registrationPolicy == RegistrationPolicy.IGNORE_EXISTING) { 142 if (logger.isDebugEnabled()) { 143 logger.debug("Ignoring existing MBean at [" + objectName + "]"); 144 } 145 } 146 else if (this.registrationPolicy == RegistrationPolicy.REPLACE_EXISTING) { 147 try { 148 if (logger.isDebugEnabled()) { 149 logger.debug("Replacing existing MBean at [" + objectName + "]"); 150 } 151 this.server.unregisterMBean(objectName); 152 registeredBean = this.server.registerMBean(mbean, objectName); 153 } 154 catch (InstanceNotFoundException ex2) { 155 if (logger.isInfoEnabled()) { 156 logger.info("Unable to replace existing MBean at [" + objectName + "]", ex2); 157 } 158 throw ex; 159 } 160 } 161 else { 162 throw ex; 163 } 164 } 165 166 // Track registration and notify listeners. 167 actualObjectName = (registeredBean != null ? registeredBean.getObjectName() : null); 168 if (actualObjectName == null) { 169 actualObjectName = objectName; 170 } 171 this.registeredBeans.add(actualObjectName); 172 } 173 174 onRegister(actualObjectName, mbean); 175 } 176 177 /** 178 * Unregisters all beans that have been registered by an instance of this class. 179 */ 180 protected void unregisterBeans() { 181 Set<ObjectName> snapshot; 182 synchronized (this.registeredBeans) { 183 snapshot = new LinkedHashSet<>(this.registeredBeans); 184 } 185 if (!snapshot.isEmpty()) { 186 logger.debug("Unregistering JMX-exposed beans"); 187 for (ObjectName objectName : snapshot) { 188 doUnregister(objectName); 189 } 190 } 191 } 192 193 /** 194 * Actually unregister the specified MBean from the server. 195 * @param objectName the suggested ObjectName for the MBean 196 */ 197 protected void doUnregister(ObjectName objectName) { 198 Assert.state(this.server != null, "No MBeanServer set"); 199 boolean actuallyUnregistered = false; 200 201 synchronized (this.registeredBeans) { 202 if (this.registeredBeans.remove(objectName)) { 203 try { 204 // MBean might already have been unregistered by an external process 205 if (this.server.isRegistered(objectName)) { 206 this.server.unregisterMBean(objectName); 207 actuallyUnregistered = true; 208 } 209 else { 210 if (logger.isInfoEnabled()) { 211 logger.info("Could not unregister MBean [" + objectName + "] as said MBean " + 212 "is not registered (perhaps already unregistered by an external process)"); 213 } 214 } 215 } 216 catch (JMException ex) { 217 if (logger.isInfoEnabled()) { 218 logger.info("Could not unregister MBean [" + objectName + "]", ex); 219 } 220 } 221 } 222 } 223 224 if (actuallyUnregistered) { 225 onUnregister(objectName); 226 } 227 } 228 229 /** 230 * Return the {@link ObjectName ObjectNames} of all registered beans. 231 */ 232 protected final ObjectName[] getRegisteredObjectNames() { 233 synchronized (this.registeredBeans) { 234 return this.registeredBeans.toArray(new ObjectName[0]); 235 } 236 } 237 238 239 /** 240 * Called when an MBean is registered under the given {@link ObjectName}. Allows 241 * subclasses to perform additional processing when an MBean is registered. 242 * <p>The default implementation delegates to {@link #onRegister(ObjectName)}. 243 * @param objectName the actual {@link ObjectName} that the MBean was registered with 244 * @param mbean the registered MBean instance 245 */ 246 protected void onRegister(ObjectName objectName, Object mbean) { 247 onRegister(objectName); 248 } 249 250 /** 251 * Called when an MBean is registered under the given {@link ObjectName}. Allows 252 * subclasses to perform additional processing when an MBean is registered. 253 * <p>The default implementation is empty. Can be overridden in subclasses. 254 * @param objectName the actual {@link ObjectName} that the MBean was registered with 255 */ 256 protected void onRegister(ObjectName objectName) { 257 } 258 259 /** 260 * Called when an MBean is unregistered under the given {@link ObjectName}. Allows 261 * subclasses to perform additional processing when an MBean is unregistered. 262 * <p>The default implementation is empty. Can be overridden in subclasses. 263 * @param objectName the {@link ObjectName} that the MBean was registered with 264 */ 265 protected void onUnregister(ObjectName objectName) { 266 } 267 268}