001/* 002 * Copyright 2002-2020 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.assembler; 018 019import java.lang.reflect.Method; 020import java.lang.reflect.Modifier; 021import java.util.Arrays; 022import java.util.Enumeration; 023import java.util.HashMap; 024import java.util.Map; 025import java.util.Properties; 026 027import org.springframework.beans.factory.BeanClassLoaderAware; 028import org.springframework.beans.factory.InitializingBean; 029import org.springframework.lang.Nullable; 030import org.springframework.util.ClassUtils; 031import org.springframework.util.StringUtils; 032 033/** 034 * Subclass of {@code AbstractReflectiveMBeanInfoAssembler} that allows for 035 * the management interface of a bean to be defined using arbitrary interfaces. 036 * Any methods or properties that are defined in those interfaces are exposed 037 * as MBean operations and attributes. 038 * 039 * <p>By default, this class votes on the inclusion of each operation or attribute 040 * based on the interfaces implemented by the bean class. However, you can supply an 041 * array of interfaces via the {@code managedInterfaces} property that will be 042 * used instead. If you have multiple beans and you wish each bean to use a different 043 * set of interfaces, then you can map bean keys (that is the name used to pass the 044 * bean to the {@code MBeanExporter}) to a list of interface names using the 045 * {@code interfaceMappings} property. 046 * 047 * <p>If you specify values for both {@code interfaceMappings} and 048 * {@code managedInterfaces}, Spring will attempt to find interfaces in the 049 * mappings first. If no interfaces for the bean are found, it will use the 050 * interfaces defined by {@code managedInterfaces}. 051 * 052 * @author Rob Harrop 053 * @author Juergen Hoeller 054 * @since 1.2 055 * @see #setManagedInterfaces 056 * @see #setInterfaceMappings 057 * @see MethodNameBasedMBeanInfoAssembler 058 * @see SimpleReflectiveMBeanInfoAssembler 059 * @see org.springframework.jmx.export.MBeanExporter 060 */ 061public class InterfaceBasedMBeanInfoAssembler extends AbstractConfigurableMBeanInfoAssembler 062 implements BeanClassLoaderAware, InitializingBean { 063 064 @Nullable 065 private Class<?>[] managedInterfaces; 066 067 /** Mappings of bean keys to an array of classes. */ 068 @Nullable 069 private Properties interfaceMappings; 070 071 @Nullable 072 private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); 073 074 /** Mappings of bean keys to an array of classes. */ 075 @Nullable 076 private Map<String, Class<?>[]> resolvedInterfaceMappings; 077 078 079 /** 080 * Set the array of interfaces to use for creating the management info. 081 * These interfaces will be used for a bean if no entry corresponding to 082 * that bean is found in the {@code interfaceMappings} property. 083 * @param managedInterfaces an array of classes indicating the interfaces to use. 084 * Each entry <strong>MUST</strong> be an interface. 085 * @see #setInterfaceMappings 086 */ 087 public void setManagedInterfaces(@Nullable Class<?>... managedInterfaces) { 088 if (managedInterfaces != null) { 089 for (Class<?> ifc : managedInterfaces) { 090 if (!ifc.isInterface()) { 091 throw new IllegalArgumentException( 092 "Management interface [" + ifc.getName() + "] is not an interface"); 093 } 094 } 095 } 096 this.managedInterfaces = managedInterfaces; 097 } 098 099 /** 100 * Set the mappings of bean keys to a comma-separated list of interface names. 101 * <p>The property key should match the bean key and the property value should match 102 * the list of interface names. When searching for interfaces for a bean, Spring 103 * will check these mappings first. 104 * @param mappings the mappings of bean keys to interface names 105 */ 106 public void setInterfaceMappings(@Nullable Properties mappings) { 107 this.interfaceMappings = mappings; 108 } 109 110 @Override 111 public void setBeanClassLoader(@Nullable ClassLoader beanClassLoader) { 112 this.beanClassLoader = beanClassLoader; 113 } 114 115 116 @Override 117 public void afterPropertiesSet() { 118 if (this.interfaceMappings != null) { 119 this.resolvedInterfaceMappings = resolveInterfaceMappings(this.interfaceMappings); 120 } 121 } 122 123 /** 124 * Resolve the given interface mappings, turning class names into Class objects. 125 * @param mappings the specified interface mappings 126 * @return the resolved interface mappings (with Class objects as values) 127 */ 128 private Map<String, Class<?>[]> resolveInterfaceMappings(Properties mappings) { 129 Map<String, Class<?>[]> resolvedMappings = new HashMap<>(mappings.size()); 130 for (Enumeration<?> en = mappings.propertyNames(); en.hasMoreElements();) { 131 String beanKey = (String) en.nextElement(); 132 String[] classNames = StringUtils.commaDelimitedListToStringArray(mappings.getProperty(beanKey)); 133 Class<?>[] classes = resolveClassNames(classNames, beanKey); 134 resolvedMappings.put(beanKey, classes); 135 } 136 return resolvedMappings; 137 } 138 139 /** 140 * Resolve the given class names into Class objects. 141 * @param classNames the class names to resolve 142 * @param beanKey the bean key that the class names are associated with 143 * @return the resolved Class 144 */ 145 private Class<?>[] resolveClassNames(String[] classNames, String beanKey) { 146 Class<?>[] classes = new Class<?>[classNames.length]; 147 for (int x = 0; x < classes.length; x++) { 148 Class<?> cls = ClassUtils.resolveClassName(classNames[x].trim(), this.beanClassLoader); 149 if (!cls.isInterface()) { 150 throw new IllegalArgumentException( 151 "Class [" + classNames[x] + "] mapped to bean key [" + beanKey + "] is no interface"); 152 } 153 classes[x] = cls; 154 } 155 return classes; 156 } 157 158 159 /** 160 * Check to see if the {@code Method} is declared in 161 * one of the configured interfaces and that it is public. 162 * @param method the accessor {@code Method}. 163 * @param beanKey the key associated with the MBean in the 164 * {@code beans} {@code Map}. 165 * @return {@code true} if the {@code Method} is declared in one of the 166 * configured interfaces, otherwise {@code false}. 167 */ 168 @Override 169 protected boolean includeReadAttribute(Method method, String beanKey) { 170 return isPublicInInterface(method, beanKey); 171 } 172 173 /** 174 * Check to see if the {@code Method} is declared in 175 * one of the configured interfaces and that it is public. 176 * @param method the mutator {@code Method}. 177 * @param beanKey the key associated with the MBean in the 178 * {@code beans} {@code Map}. 179 * @return {@code true} if the {@code Method} is declared in one of the 180 * configured interfaces, otherwise {@code false}. 181 */ 182 @Override 183 protected boolean includeWriteAttribute(Method method, String beanKey) { 184 return isPublicInInterface(method, beanKey); 185 } 186 187 /** 188 * Check to see if the {@code Method} is declared in 189 * one of the configured interfaces and that it is public. 190 * @param method the operation {@code Method}. 191 * @param beanKey the key associated with the MBean in the 192 * {@code beans} {@code Map}. 193 * @return {@code true} if the {@code Method} is declared in one of the 194 * configured interfaces, otherwise {@code false}. 195 */ 196 @Override 197 protected boolean includeOperation(Method method, String beanKey) { 198 return isPublicInInterface(method, beanKey); 199 } 200 201 /** 202 * Check to see if the {@code Method} is both public and declared in 203 * one of the configured interfaces. 204 * @param method the {@code Method} to check. 205 * @param beanKey the key associated with the MBean in the beans map 206 * @return {@code true} if the {@code Method} is declared in one of the 207 * configured interfaces and is public, otherwise {@code false}. 208 */ 209 private boolean isPublicInInterface(Method method, String beanKey) { 210 return Modifier.isPublic(method.getModifiers()) && isDeclaredInInterface(method, beanKey); 211 } 212 213 /** 214 * Checks to see if the given method is declared in a managed 215 * interface for the given bean. 216 */ 217 private boolean isDeclaredInInterface(Method method, String beanKey) { 218 Class<?>[] ifaces = null; 219 220 if (this.resolvedInterfaceMappings != null) { 221 ifaces = this.resolvedInterfaceMappings.get(beanKey); 222 } 223 224 if (ifaces == null) { 225 ifaces = this.managedInterfaces; 226 if (ifaces == null) { 227 ifaces = ClassUtils.getAllInterfacesForClass(method.getDeclaringClass()); 228 } 229 } 230 231 for (Class<?> ifc : ifaces) { 232 for (Method ifcMethod : ifc.getMethods()) { 233 if (ifcMethod.getName().equals(method.getName()) && 234 ifcMethod.getParameterCount() == method.getParameterCount() && 235 Arrays.equals(ifcMethod.getParameterTypes(), method.getParameterTypes())) { 236 return true; 237 } 238 } 239 } 240 241 return false; 242 } 243 244}