Save This Page
Home » spring-framework-2.5.6-with-dependencies » org.springframework » jmx » access » [javadoc | source]
    1   /*
    2    * Copyright 2002-2008 the original author or authors.
    3    *
    4    * Licensed under the Apache License, Version 2.0 (the "License");
    5    * you may not use this file except in compliance with the License.
    6    * You may obtain a copy of the License at
    7    *
    8    *      http://www.apache.org/licenses/LICENSE-2.0
    9    *
   10    * Unless required by applicable law or agreed to in writing, software
   11    * distributed under the License is distributed on an "AS IS" BASIS,
   12    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   13    * See the License for the specific language governing permissions and
   14    * limitations under the License.
   15    */
   16   
   17   package org.springframework.jmx.access;
   18   
   19   import java.beans.PropertyDescriptor;
   20   import java.io.IOException;
   21   import java.lang.reflect.Method;
   22   import java.net.MalformedURLException;
   23   import java.util.Arrays;
   24   import java.util.HashMap;
   25   import java.util.Map;
   26   
   27   import javax.management.Attribute;
   28   import javax.management.InstanceNotFoundException;
   29   import javax.management.IntrospectionException;
   30   import javax.management.JMException;
   31   import javax.management.JMX;
   32   import javax.management.MBeanAttributeInfo;
   33   import javax.management.MBeanException;
   34   import javax.management.MBeanInfo;
   35   import javax.management.MBeanOperationInfo;
   36   import javax.management.MBeanServerConnection;
   37   import javax.management.MBeanServerInvocationHandler;
   38   import javax.management.MalformedObjectNameException;
   39   import javax.management.ObjectName;
   40   import javax.management.OperationsException;
   41   import javax.management.ReflectionException;
   42   import javax.management.RuntimeErrorException;
   43   import javax.management.RuntimeMBeanException;
   44   import javax.management.RuntimeOperationsException;
   45   import javax.management.openmbean.CompositeData;
   46   import javax.management.openmbean.TabularData;
   47   import javax.management.remote.JMXServiceURL;
   48   
   49   import org.aopalliance.intercept.MethodInterceptor;
   50   import org.aopalliance.intercept.MethodInvocation;
   51   import org.apache.commons.logging.Log;
   52   import org.apache.commons.logging.LogFactory;
   53   
   54   import org.springframework.beans.BeanUtils;
   55   import org.springframework.beans.factory.BeanClassLoaderAware;
   56   import org.springframework.beans.factory.DisposableBean;
   57   import org.springframework.beans.factory.InitializingBean;
   58   import org.springframework.core.JdkVersion;
   59   import org.springframework.jmx.support.JmxUtils;
   60   import org.springframework.jmx.support.ObjectNameManager;
   61   import org.springframework.util.ClassUtils;
   62   import org.springframework.util.ReflectionUtils;
   63   
   64   /**
   65    * {@link org.aopalliance.intercept.MethodInterceptor} that routes calls to an
   66    * MBean running on the supplied <code>MBeanServerConnection</code>.
   67    * Works for both local and remote <code>MBeanServerConnection</code>s.
   68    *
   69    * <p>By default, the <code>MBeanClientInterceptor</code> will connect to the
   70    * <code>MBeanServer</code> and cache MBean metadata at startup. This can
   71    * be undesirable when running against a remote <code>MBeanServer</code>
   72    * that may not be running when the application starts. Through setting the
   73    * {@link #setConnectOnStartup(boolean) connectOnStartup} property to "false",
   74    * you can defer this process until the first invocation against the proxy.
   75    *
   76    * <p>Requires JMX 1.2's <code>MBeanServerConnection</code> feature.
   77    * As a consequence, this class will not work on JMX 1.0.
   78    *
   79    * <p>This functionality is usually used through {@link MBeanProxyFactoryBean}.
   80    * See the javadoc of that class for more information.
   81    *
   82    * @author Rob Harrop
   83    * @author Juergen Hoeller
   84    * @since 1.2
   85    * @see MBeanProxyFactoryBean
   86    * @see #setConnectOnStartup
   87    */
   88   public class MBeanClientInterceptor
   89   		implements MethodInterceptor, BeanClassLoaderAware, InitializingBean, DisposableBean {
   90   
   91   	/** Logger available to subclasses */
   92   	protected final Log logger = LogFactory.getLog(getClass());
   93   
   94   	private MBeanServerConnection server;
   95   
   96   	private JMXServiceURL serviceUrl;
   97   
   98   	private Map environment;
   99   
  100   	private String agentId;
  101   
  102   	private boolean connectOnStartup = true;
  103   
  104   	private boolean refreshOnConnectFailure = false;
  105   
  106   	private ObjectName objectName;
  107   
  108   	private boolean useStrictCasing = true;
  109   
  110   	private Class managementInterface;
  111   
  112   	private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
  113   
  114   	private final ConnectorDelegate connector = new ConnectorDelegate();
  115   
  116   	private MBeanServerConnection serverToUse;
  117   
  118   	private MBeanServerInvocationHandler invocationHandler;
  119   
  120   	private Map allowedAttributes;
  121   
  122   	private Map allowedOperations;
  123   
  124   	private final Map signatureCache = new HashMap();
  125   
  126   	private final Object preparationMonitor = new Object();
  127   
  128   
  129   	/**
  130   	 * Set the <code>MBeanServerConnection</code> used to connect to the
  131   	 * MBean which all invocations are routed to.
  132   	 */
  133   	public void setServer(MBeanServerConnection server) {
  134   		this.server = server;
  135   	}
  136   
  137   	/**
  138   	 * Set the service URL of the remote <code>MBeanServer</code>.
  139   	 */
  140   	public void setServiceUrl(String url) throws MalformedURLException {
  141   		this.serviceUrl = new JMXServiceURL(url);
  142   	}
  143   
  144   	/**
  145   	 * Specify the environment for the JMX connector.
  146   	 * @see javax.management.remote.JMXConnectorFactory#connect(javax.management.remote.JMXServiceURL, java.util.Map)
  147   	 */
  148   	public void setEnvironment(Map environment) {
  149   		this.environment = environment;
  150   	}
  151   
  152   	/**
  153   	 * Allow Map access to the environment to be set for the connector,
  154   	 * with the option to add or override specific entries.
  155   	 * <p>Useful for specifying entries directly, for example via
  156   	 * "environment[myKey]". This is particularly useful for
  157   	 * adding or overriding entries in child bean definitions.
  158   	 */
  159   	public Map getEnvironment() {
  160   		return this.environment;
  161   	}
  162   
  163   	/**
  164   	 * Set the agent id of the <code>MBeanServer</code> to locate.
  165   	 * <p>Default is none. If specified, this will result in an
  166   	 * attempt being made to locate the attendant MBeanServer, unless
  167   	 * the {@link #setServiceUrl "serviceUrl"} property has been set.
  168   	 * @see javax.management.MBeanServerFactory#findMBeanServer(String)
  169   	 */
  170   	public void setAgentId(String agentId) {
  171   		this.agentId = agentId;
  172   	}
  173   
  174   	/**
  175   	 * Set whether or not the proxy should connect to the <code>MBeanServer</code>
  176   	 * at creation time ("true") or the first time it is invoked ("false").
  177   	 * Default is "true".
  178   	 */
  179   	public void setConnectOnStartup(boolean connectOnStartup) {
  180   		this.connectOnStartup = connectOnStartup;
  181   	}
  182   
  183   	/**
  184   	 * Set whether to refresh the MBeanServer connection on connect failure.
  185   	 * Default is "false".
  186   	 * <p>Can be turned on to allow for hot restart of the JMX server,
  187   	 * automatically reconnecting and retrying in case of an IOException.
  188   	 */
  189   	public void setRefreshOnConnectFailure(boolean refreshOnConnectFailure) {
  190   		this.refreshOnConnectFailure = refreshOnConnectFailure;
  191   	}
  192   
  193   	/**
  194   	 * Set the <code>ObjectName</code> of the MBean which calls are routed to,
  195   	 * as <code>ObjectName</code> instance or as <code>String</code>.
  196   	 */
  197   	public void setObjectName(Object objectName) throws MalformedObjectNameException {
  198   		this.objectName = ObjectNameManager.getInstance(objectName);
  199   	}
  200   
  201   	/**
  202   	 * Set whether to use strict casing for attributes. Enabled by default.
  203   	 * <p>When using strict casing, a JavaBean property with a getter such as
  204   	 * <code>getFoo()</code> translates to an attribute called <code>Foo</code>.
  205   	 * With strict casing disabled, <code>getFoo()</code> would translate to just
  206   	 * <code>foo</code>.
  207   	 */
  208   	public void setUseStrictCasing(boolean useStrictCasing) {
  209   		this.useStrictCasing = useStrictCasing;
  210   	}
  211   
  212   	/**
  213   	 * Set the management interface of the target MBean, exposing bean property
  214   	 * setters and getters for MBean attributes and conventional Java methods
  215   	 * for MBean operations.
  216   	 */
  217   	public void setManagementInterface(Class managementInterface) {
  218   		this.managementInterface = managementInterface;
  219   	}
  220   
  221   	/**
  222   	 * Return the management interface of the target MBean,
  223   	 * or <code>null</code> if none specified.
  224   	 */
  225   	protected final Class getManagementInterface() {
  226   		return this.managementInterface;
  227   	}
  228   
  229   	public void setBeanClassLoader(ClassLoader beanClassLoader) {
  230   		this.beanClassLoader = beanClassLoader;
  231   	}
  232   
  233   
  234   	/**
  235   	 * Prepares the <code>MBeanServerConnection</code> if the "connectOnStartup"
  236   	 * is turned on (which it is by default).
  237   	 */
  238   	public void afterPropertiesSet() {
  239   		if (this.server != null && this.refreshOnConnectFailure) {
  240   			throw new IllegalArgumentException("'refreshOnConnectFailure' does not work when setting " +
  241   					"a 'server' reference. Prefer 'serviceUrl' etc instead.");
  242   		}
  243   		if (this.connectOnStartup) {
  244   			prepare();
  245   		}
  246   	}
  247   
  248   	/**
  249   	 * Ensures that an <code>MBeanServerConnection</code> is configured and attempts
  250   	 * to detect a local connection if one is not supplied.
  251   	 */
  252   	public void prepare() {
  253   		synchronized (this.preparationMonitor) {
  254   			if (this.server != null) {
  255   				this.serverToUse = this.server;
  256   			}
  257   			else {
  258   				this.serverToUse = null;
  259   				this.serverToUse = this.connector.connect(this.serviceUrl, this.environment, this.agentId);
  260   			}
  261   			this.invocationHandler = null;
  262   			if (this.useStrictCasing) {
  263   				// Use the JDK's own MBeanServerInvocationHandler,
  264   				// in particular for native MXBean support on Java 6.
  265   				if (JdkVersion.isAtLeastJava16()) {
  266   					this.invocationHandler =
  267   							new MBeanServerInvocationHandler(this.serverToUse, this.objectName,
  268   									(this.managementInterface != null && JMX.isMXBeanInterface(this.managementInterface)));
  269   				}
  270   				else {
  271   					this.invocationHandler = new MBeanServerInvocationHandler(this.serverToUse, this.objectName);
  272   				}
  273   			}
  274   			else {
  275   				// Non-strict casing can only be achieved through custom
  276   				// invocation handling. Only partial MXBean support available!
  277   				retrieveMBeanInfo();
  278   			}
  279   		}
  280   	}
  281   	/**
  282   	 * Loads the management interface info for the configured MBean into the caches.
  283   	 * This information is used by the proxy when determining whether an invocation matches
  284   	 * a valid operation or attribute on the management interface of the managed resource.
  285   	 */
  286   	private void retrieveMBeanInfo() throws MBeanInfoRetrievalException {
  287   		try {
  288   			MBeanInfo info = this.serverToUse.getMBeanInfo(this.objectName);
  289   
  290   			MBeanAttributeInfo[] attributeInfo = info.getAttributes();
  291   			this.allowedAttributes = new HashMap(attributeInfo.length);
  292   			for (int x = 0; x < attributeInfo.length; x++) {
  293   				this.allowedAttributes.put(attributeInfo[x].getName(), attributeInfo[x]);
  294   			}
  295   
  296   			MBeanOperationInfo[] operationInfo = info.getOperations();
  297   			this.allowedOperations = new HashMap(operationInfo.length);
  298   			for (int x = 0; x < operationInfo.length; x++) {
  299   				MBeanOperationInfo opInfo = operationInfo[x];
  300   				Class[] paramTypes = JmxUtils.parameterInfoToTypes(opInfo.getSignature(), this.beanClassLoader);
  301   				this.allowedOperations.put(new MethodCacheKey(opInfo.getName(), paramTypes), opInfo);
  302   			}
  303   		}
  304   		catch (ClassNotFoundException ex) {
  305   			throw new MBeanInfoRetrievalException("Unable to locate class specified in method signature", ex);
  306   		}
  307   		catch (IntrospectionException ex) {
  308   			throw new MBeanInfoRetrievalException("Unable to obtain MBean info for bean [" + this.objectName + "]", ex);
  309   		}
  310   		catch (InstanceNotFoundException ex) {
  311   			// if we are this far this shouldn't happen, but...
  312   			throw new MBeanInfoRetrievalException("Unable to obtain MBean info for bean [" + this.objectName +
  313   					"]: it is likely that this bean was unregistered during the proxy creation process",
  314   					ex);
  315   		}
  316   		catch (ReflectionException ex) {
  317   			throw new MBeanInfoRetrievalException("Unable to read MBean info for bean [ " + this.objectName + "]", ex);
  318   		}
  319   		catch (IOException ex) {
  320   			throw new MBeanInfoRetrievalException("An IOException occurred when communicating with the " +
  321   					"MBeanServer. It is likely that you are communicating with a remote MBeanServer. " +
  322   					"Check the inner exception for exact details.", ex);
  323   		}
  324   	}
  325   
  326   	/**
  327   	 * Return whether this client interceptor has already been prepared,
  328   	 * i.e. has already looked up the server and cached all metadata.
  329   	 */
  330   	protected boolean isPrepared() {
  331   		synchronized (this.preparationMonitor) {
  332   			return (this.serverToUse != null);
  333   		}
  334   	}
  335   
  336   
  337   	/**
  338   	 * Route the invocation to the configured managed resource..
  339   	 * @param invocation the <code>MethodInvocation</code> to re-route
  340   	 * @return the value returned as a result of the re-routed invocation
  341   	 * @throws Throwable an invocation error propagated to the user
  342   	 * @see #doInvoke
  343   	 * @see #handleConnectFailure
  344   	 */
  345   	public Object invoke(MethodInvocation invocation) throws Throwable {
  346   		// Lazily connect to MBeanServer if necessary.
  347   		synchronized (this.preparationMonitor) {
  348   			if (!isPrepared()) {
  349   				prepare();
  350   			}
  351   		}
  352   		try {
  353   			return doInvoke(invocation);
  354   		}
  355   		catch (MBeanConnectFailureException ex) {
  356   			return handleConnectFailure(invocation, ex);
  357   		}
  358   		catch (IOException ex) {
  359   			return handleConnectFailure(invocation, ex);
  360   		}
  361   	}
  362   
  363   	/**
  364   	 * Refresh the connection and retry the MBean invocation if possible.
  365   	 * <p>If not configured to refresh on connect failure, this method
  366   	 * simply rethrows the original exception.
  367   	 * @param invocation the invocation that failed
  368   	 * @param ex the exception raised on remote invocation
  369   	 * @return the result value of the new invocation, if succeeded
  370   	 * @throws Throwable an exception raised by the new invocation,
  371   	 * if it failed as well
  372   	 * @see #setRefreshOnConnectFailure
  373   	 * @see #doInvoke
  374   	 */
  375   	protected Object handleConnectFailure(MethodInvocation invocation, Exception ex) throws Throwable {
  376   		if (this.refreshOnConnectFailure) {
  377   			String msg = "Could not connect to JMX server - retrying";
  378   			if (logger.isDebugEnabled()) {
  379   				logger.warn(msg, ex);
  380   			}
  381   			else if (logger.isWarnEnabled()) {
  382   				logger.warn(msg);
  383   			}
  384   			prepare();
  385   			return doInvoke(invocation);
  386   		}
  387   		else {
  388   			throw ex;
  389   		}
  390   	}
  391   
  392   	/**
  393   	 * Route the invocation to the configured managed resource. Correctly routes JavaBean property
  394   	 * access to <code>MBeanServerConnection.get/setAttribute</code> and method invocation to
  395   	 * <code>MBeanServerConnection.invoke</code>.
  396   	 * @param invocation the <code>MethodInvocation</code> to re-route
  397   	 * @return the value returned as a result of the re-routed invocation
  398   	 * @throws Throwable an invocation error propagated to the user
  399   	 */
  400   	protected Object doInvoke(MethodInvocation invocation) throws Throwable {
  401   		Method method = invocation.getMethod();
  402   		try {
  403   			Object result = null;
  404   			if (this.invocationHandler != null) {
  405   				result = this.invocationHandler.invoke(invocation.getThis(), method, invocation.getArguments());
  406   			}
  407   			else {
  408   				PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method);
  409   				if (pd != null) {
  410   					result = invokeAttribute(pd, invocation);
  411   				}
  412   				else {
  413   					result = invokeOperation(method, invocation.getArguments());
  414   				}
  415   			}
  416   			return convertResultValueIfNecessary(result, method.getReturnType());
  417   		}
  418   		catch (MBeanException ex) {
  419   			throw ex.getTargetException();
  420   		}
  421   		catch (RuntimeMBeanException ex) {
  422   			throw ex.getTargetException();
  423   		}
  424   		catch (RuntimeErrorException ex) {
  425   			throw ex.getTargetError();
  426   		}
  427   		catch (RuntimeOperationsException ex) {
  428   			// This one is only thrown by the JMX 1.2 RI, not by the JDK 1.5 JMX code.
  429   			RuntimeException rex = ex.getTargetException();
  430   			if (rex instanceof RuntimeMBeanException) {
  431   				throw ((RuntimeMBeanException) rex).getTargetException();
  432   			}
  433   			else if (rex instanceof RuntimeErrorException) {
  434   				throw ((RuntimeErrorException) rex).getTargetError();
  435   			}
  436   			else {
  437   				throw rex;
  438   			}
  439   		}
  440   		catch (OperationsException ex) {
  441   			if (ReflectionUtils.declaresException(method, ex.getClass())) {
  442   				throw ex;
  443   			}
  444   			else {
  445   				throw new InvalidInvocationException(ex.getMessage());
  446   			}
  447   		}
  448   		catch (JMException ex) {
  449   			if (ReflectionUtils.declaresException(method, ex.getClass())) {
  450   				throw ex;
  451   			}
  452   			else {
  453   				throw new InvocationFailureException("JMX access failed", ex);
  454   			}
  455   		}
  456   		catch (IOException ex) {
  457   			if (ReflectionUtils.declaresException(method, ex.getClass())) {
  458   				throw ex;
  459   			}
  460   			else {
  461   				throw new MBeanConnectFailureException("I/O failure during JMX access", ex);
  462   			}
  463   		}
  464   	}
  465   
  466   	private Object invokeAttribute(PropertyDescriptor pd, MethodInvocation invocation)
  467   			throws JMException, IOException {
  468   
  469   		String attributeName = JmxUtils.getAttributeName(pd, this.useStrictCasing);
  470   		MBeanAttributeInfo inf = (MBeanAttributeInfo) this.allowedAttributes.get(attributeName);
  471   		// If no attribute is returned, we know that it is not defined in the
  472   		// management interface.
  473   		if (inf == null) {
  474   			throw new InvalidInvocationException(
  475   					"Attribute '" + pd.getName() + "' is not exposed on the management interface");
  476   		}
  477   		if (invocation.getMethod().equals(pd.getReadMethod())) {
  478   			if (inf.isReadable()) {
  479   				return this.serverToUse.getAttribute(this.objectName, attributeName);
  480   			}
  481   			else {
  482   				throw new InvalidInvocationException("Attribute '" + attributeName + "' is not readable");
  483   			}
  484   		}
  485   		else if (invocation.getMethod().equals(pd.getWriteMethod())) {
  486   			if (inf.isWritable()) {
  487   				this.serverToUse.setAttribute(this.objectName, new Attribute(attributeName, invocation.getArguments()[0]));
  488   				return null;
  489   			}
  490   			else {
  491   				throw new InvalidInvocationException("Attribute '" + attributeName + "' is not writable");
  492   			}
  493   		}
  494   		else {
  495   			throw new IllegalStateException(
  496   					"Method [" + invocation.getMethod() + "] is neither a bean property getter nor a setter");
  497   		}
  498   	}
  499   
  500   	/**
  501   	 * Routes a method invocation (not a property get/set) to the corresponding
  502   	 * operation on the managed resource.
  503   	 * @param method the method corresponding to operation on the managed resource.
  504   	 * @param args the invocation arguments
  505   	 * @return the value returned by the method invocation.
  506   	 */
  507   	private Object invokeOperation(Method method, Object[] args) throws JMException, IOException {
  508   		MethodCacheKey key = new MethodCacheKey(method.getName(), method.getParameterTypes());
  509   		MBeanOperationInfo info = (MBeanOperationInfo) this.allowedOperations.get(key);
  510   		if (info == null) {
  511   			throw new InvalidInvocationException("Operation '" + method.getName() +
  512   					"' is not exposed on the management interface");
  513   		}
  514   		String[] signature = null;
  515   		synchronized (this.signatureCache) {
  516   			signature = (String[]) this.signatureCache.get(method);
  517   			if (signature == null) {
  518   				signature = JmxUtils.getMethodSignature(method);
  519   				this.signatureCache.put(method, signature);
  520   			}
  521   		}
  522   		return this.serverToUse.invoke(this.objectName, method.getName(), args, signature);
  523   	}
  524   
  525   	/**
  526   	 * Convert the given result object (from attribute access or operation invocation)
  527   	 * to the specified target class for returning from the proxy method.
  528   	 * @param result the result object as returned by the <code>MBeanServer</code>
  529   	 * @param targetClass the result type of the proxy method that's been invoked
  530   	 * @return the converted result object, or the passed-in object if no conversion
  531   	 * is necessary
  532   	 */
  533   	protected Object convertResultValueIfNecessary(Object result, Class targetClass) {
  534   		try {
  535   			if (result == null) {
  536   				return null;
  537   			}
  538   			if (ClassUtils.isAssignableValue(targetClass, result)) {
  539   				return result;
  540   			}
  541   			if (result instanceof CompositeData) {
  542   				Method fromMethod = targetClass.getMethod("from", new Class[] {CompositeData.class});
  543   				return ReflectionUtils.invokeMethod(fromMethod, null, new Object[] {result});
  544   			}
  545   			else if (result instanceof TabularData) {
  546   				Method fromMethod = targetClass.getMethod("from", new Class[] {TabularData.class});
  547   				return ReflectionUtils.invokeMethod(fromMethod, null, new Object[] {result});
  548   			}
  549   			else {
  550   				throw new InvocationFailureException(
  551   						"Incompatible result value [" + result + "] for target type [" + targetClass.getName() + "]");
  552   			}
  553   		}
  554   		catch (NoSuchMethodException ex) {
  555   			throw new InvocationFailureException(
  556   					"Could not obtain 'find(CompositeData)' / 'find(TabularData)' method on target type [" +
  557   							targetClass.getName() + "] for conversion of MXBean data structure [" + result + "]");
  558   		}
  559   	}
  560   
  561   	public void destroy() {
  562   		this.connector.close();
  563   	}
  564   
  565   
  566   	/**
  567   	 * Simple wrapper class around a method name and its signature.
  568   	 * Used as the key when caching methods.
  569   	 */
  570   	private static class MethodCacheKey {
  571   
  572   		private final String name;
  573   
  574   		private final Class[] parameterTypes;
  575   
  576   		/**
  577   		 * Create a new instance of <code>MethodCacheKey</code> with the supplied
  578   		 * method name and parameter list.
  579   		 * @param name the name of the method
  580   		 * @param parameterTypes the arguments in the method signature
  581   		 */
  582   		public MethodCacheKey(String name, Class[] parameterTypes) {
  583   			this.name = name;
  584   			this.parameterTypes = (parameterTypes != null ? parameterTypes : new Class[0]);
  585   		}
  586   
  587   		public boolean equals(Object other) {
  588   			if (other == this) {
  589   				return true;
  590   			}
  591   			MethodCacheKey otherKey = (MethodCacheKey) other;
  592   			return (this.name.equals(otherKey.name) && Arrays.equals(this.parameterTypes, otherKey.parameterTypes));
  593   		}
  594   
  595   		public int hashCode() {
  596   			return this.name.hashCode();
  597   		}
  598   	}
  599   
  600   }

Save This Page
Home » spring-framework-2.5.6-with-dependencies » org.springframework » jmx » access » [javadoc | source]