001/* 002 * Copyright 2002-2019 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.io.IOException; 020import java.net.MalformedURLException; 021import java.util.HashMap; 022import java.util.Map; 023import java.util.Properties; 024 025import javax.management.MBeanServerConnection; 026import javax.management.remote.JMXConnector; 027import javax.management.remote.JMXConnectorFactory; 028import javax.management.remote.JMXServiceURL; 029 030import org.springframework.aop.TargetSource; 031import org.springframework.aop.framework.ProxyFactory; 032import org.springframework.aop.target.AbstractLazyCreationTargetSource; 033import org.springframework.beans.factory.BeanClassLoaderAware; 034import org.springframework.beans.factory.DisposableBean; 035import org.springframework.beans.factory.FactoryBean; 036import org.springframework.beans.factory.InitializingBean; 037import org.springframework.lang.Nullable; 038import org.springframework.util.Assert; 039import org.springframework.util.ClassUtils; 040import org.springframework.util.CollectionUtils; 041 042/** 043 * {@link FactoryBean} that creates a JMX 1.2 {@code MBeanServerConnection} 044 * to a remote {@code MBeanServer} exposed via a {@code JMXServerConnector}. 045 * Exposes the {@code MBeanServer} for bean references. 046 * 047 * @author Rob Harrop 048 * @author Juergen Hoeller 049 * @since 1.2 050 * @see MBeanServerFactoryBean 051 * @see ConnectorServerFactoryBean 052 * @see org.springframework.jmx.access.MBeanClientInterceptor#setServer 053 * @see org.springframework.jmx.access.NotificationListenerRegistrar#setServer 054 */ 055public class MBeanServerConnectionFactoryBean 056 implements FactoryBean<MBeanServerConnection>, BeanClassLoaderAware, InitializingBean, DisposableBean { 057 058 @Nullable 059 private JMXServiceURL serviceUrl; 060 061 private Map<String, Object> environment = new HashMap<>(); 062 063 private boolean connectOnStartup = true; 064 065 @Nullable 066 private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); 067 068 @Nullable 069 private JMXConnector connector; 070 071 @Nullable 072 private MBeanServerConnection connection; 073 074 @Nullable 075 private JMXConnectorLazyInitTargetSource connectorTargetSource; 076 077 078 /** 079 * Set the service URL of the remote {@code MBeanServer}. 080 */ 081 public void setServiceUrl(String url) throws MalformedURLException { 082 this.serviceUrl = new JMXServiceURL(url); 083 } 084 085 /** 086 * Set the environment properties used to construct the {@code JMXConnector} 087 * as {@code java.util.Properties} (String key/value pairs). 088 */ 089 public void setEnvironment(Properties environment) { 090 CollectionUtils.mergePropertiesIntoMap(environment, this.environment); 091 } 092 093 /** 094 * Set the environment properties used to construct the {@code JMXConnector} 095 * as a {@code Map} of String keys and arbitrary Object values. 096 */ 097 public void setEnvironmentMap(@Nullable Map<String, ?> environment) { 098 if (environment != null) { 099 this.environment.putAll(environment); 100 } 101 } 102 103 /** 104 * Set whether to connect to the server on startup. 105 * <p>Default is {@code true}. 106 * <p>Can be turned off to allow for late start of the JMX server. 107 * In this case, the JMX connector will be fetched on first access. 108 */ 109 public void setConnectOnStartup(boolean connectOnStartup) { 110 this.connectOnStartup = connectOnStartup; 111 } 112 113 @Override 114 public void setBeanClassLoader(ClassLoader classLoader) { 115 this.beanClassLoader = classLoader; 116 } 117 118 119 /** 120 * Creates a {@code JMXConnector} for the given settings 121 * and exposes the associated {@code MBeanServerConnection}. 122 */ 123 @Override 124 public void afterPropertiesSet() throws IOException { 125 if (this.serviceUrl == null) { 126 throw new IllegalArgumentException("Property 'serviceUrl' is required"); 127 } 128 129 if (this.connectOnStartup) { 130 connect(); 131 } 132 else { 133 createLazyConnection(); 134 } 135 } 136 137 /** 138 * Connects to the remote {@code MBeanServer} using the configured service URL and 139 * environment properties. 140 */ 141 private void connect() throws IOException { 142 Assert.state(this.serviceUrl != null, "No JMXServiceURL set"); 143 this.connector = JMXConnectorFactory.connect(this.serviceUrl, this.environment); 144 this.connection = this.connector.getMBeanServerConnection(); 145 } 146 147 /** 148 * Creates lazy proxies for the {@code JMXConnector} and {@code MBeanServerConnection}. 149 */ 150 private void createLazyConnection() { 151 this.connectorTargetSource = new JMXConnectorLazyInitTargetSource(); 152 TargetSource connectionTargetSource = new MBeanServerConnectionLazyInitTargetSource(); 153 154 this.connector = (JMXConnector) 155 new ProxyFactory(JMXConnector.class, this.connectorTargetSource).getProxy(this.beanClassLoader); 156 this.connection = (MBeanServerConnection) 157 new ProxyFactory(MBeanServerConnection.class, connectionTargetSource).getProxy(this.beanClassLoader); 158 } 159 160 161 @Override 162 @Nullable 163 public MBeanServerConnection getObject() { 164 return this.connection; 165 } 166 167 @Override 168 public Class<? extends MBeanServerConnection> getObjectType() { 169 return (this.connection != null ? this.connection.getClass() : MBeanServerConnection.class); 170 } 171 172 @Override 173 public boolean isSingleton() { 174 return true; 175 } 176 177 178 /** 179 * Closes the underlying {@code JMXConnector}. 180 */ 181 @Override 182 public void destroy() throws IOException { 183 if (this.connector != null && 184 (this.connectorTargetSource == null || this.connectorTargetSource.isInitialized())) { 185 this.connector.close(); 186 } 187 } 188 189 190 /** 191 * Lazily creates a {@code JMXConnector} using the configured service URL 192 * and environment properties. 193 * @see MBeanServerConnectionFactoryBean#setServiceUrl(String) 194 * @see MBeanServerConnectionFactoryBean#setEnvironment(java.util.Properties) 195 */ 196 private class JMXConnectorLazyInitTargetSource extends AbstractLazyCreationTargetSource { 197 198 @Override 199 protected Object createObject() throws Exception { 200 Assert.state(serviceUrl != null, "No JMXServiceURL set"); 201 return JMXConnectorFactory.connect(serviceUrl, environment); 202 } 203 204 @Override 205 public Class<?> getTargetClass() { 206 return JMXConnector.class; 207 } 208 } 209 210 211 /** 212 * Lazily creates an {@code MBeanServerConnection}. 213 */ 214 private class MBeanServerConnectionLazyInitTargetSource extends AbstractLazyCreationTargetSource { 215 216 @Override 217 protected Object createObject() throws Exception { 218 Assert.state(connector != null, "JMXConnector not initialized"); 219 return connector.getMBeanServerConnection(); 220 } 221 222 @Override 223 public Class<?> getTargetClass() { 224 return MBeanServerConnection.class; 225 } 226 } 227 228}