001/* 002 * Copyright 2002-2017 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.beans.factory.config; 018 019import java.lang.reflect.Constructor; 020import java.lang.reflect.InvocationHandler; 021import java.lang.reflect.Method; 022import java.lang.reflect.Proxy; 023import java.util.Properties; 024 025import org.springframework.beans.BeanUtils; 026import org.springframework.beans.BeansException; 027import org.springframework.beans.FatalBeanException; 028import org.springframework.beans.factory.BeanFactory; 029import org.springframework.beans.factory.BeanFactoryAware; 030import org.springframework.beans.factory.FactoryBean; 031import org.springframework.beans.factory.InitializingBean; 032import org.springframework.beans.factory.ListableBeanFactory; 033import org.springframework.lang.Nullable; 034import org.springframework.util.Assert; 035import org.springframework.util.ReflectionUtils; 036import org.springframework.util.StringUtils; 037 038/** 039 * A {@link FactoryBean} implementation that takes an interface which must have one or more 040 * methods with the signatures {@code MyType xxx()} or {@code MyType xxx(MyIdType id)} 041 * (typically, {@code MyService getService()} or {@code MyService getService(String id)}) 042 * and creates a dynamic proxy which implements that interface, delegating to an 043 * underlying {@link org.springframework.beans.factory.BeanFactory}. 044 * 045 * <p>Such service locators permit the decoupling of calling code from 046 * the {@link org.springframework.beans.factory.BeanFactory} API, by using an 047 * appropriate custom locator interface. They will typically be used for 048 * <b>prototype beans</b>, i.e. for factory methods that are supposed to 049 * return a new instance for each call. The client receives a reference to the 050 * service locator via setter or constructor injection, to be able to invoke 051 * the locator's factory methods on demand. <b>For singleton beans, direct 052 * setter or constructor injection of the target bean is preferable.</b> 053 * 054 * <p>On invocation of the no-arg factory method, or the single-arg factory 055 * method with a String id of {@code null} or empty String, if exactly 056 * <b>one</b> bean in the factory matches the return type of the factory 057 * method, that bean is returned, otherwise a 058 * {@link org.springframework.beans.factory.NoSuchBeanDefinitionException} 059 * is thrown. 060 * 061 * <p>On invocation of the single-arg factory method with a non-null (and 062 * non-empty) argument, the proxy returns the result of a 063 * {@link org.springframework.beans.factory.BeanFactory#getBean(String)} call, 064 * using a stringified version of the passed-in id as bean name. 065 * 066 * <p>A factory method argument will usually be a String, but can also be an 067 * int or a custom enumeration type, for example, stringified via 068 * {@code toString}. The resulting String can be used as bean name as-is, 069 * provided that corresponding beans are defined in the bean factory. 070 * Alternatively, {@linkplain #setServiceMappings(java.util.Properties) a custom 071 * mapping} between service IDs and bean names can be defined. 072 * 073 * <p>By way of an example, consider the following service locator interface. 074 * Note that this interface is not dependent on any Spring APIs. 075 * 076 * <pre class="code">package a.b.c; 077 * 078 *public interface ServiceFactory { 079 * 080 * public MyService getService(); 081 *}</pre> 082 * 083 * <p>A sample config in an XML-based 084 * {@link org.springframework.beans.factory.BeanFactory} might look as follows: 085 * 086 * <pre class="code"><beans> 087 * 088 * <!-- Prototype bean since we have state --> 089 * <bean id="myService" class="a.b.c.MyService" singleton="false"/> 090 * 091 * <!-- will lookup the above 'myService' bean by *TYPE* --> 092 * <bean id="myServiceFactory" 093 * class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean"> 094 * <property name="serviceLocatorInterface" value="a.b.c.ServiceFactory"/> 095 * </bean> 096 * 097 * <bean id="clientBean" class="a.b.c.MyClientBean"> 098 * <property name="myServiceFactory" ref="myServiceFactory"/> 099 * </bean> 100 * 101 *</beans></pre> 102 * 103 * <p>The attendant {@code MyClientBean} class implementation might then 104 * look something like this: 105 * 106 * <pre class="code">package a.b.c; 107 * 108 *public class MyClientBean { 109 * 110 * private ServiceFactory myServiceFactory; 111 * 112 * // actual implementation provided by the Spring container 113 * public void setServiceFactory(ServiceFactory myServiceFactory) { 114 * this.myServiceFactory = myServiceFactory; 115 * } 116 * 117 * public void someBusinessMethod() { 118 * // get a 'fresh', brand new MyService instance 119 * MyService service = this.myServiceFactory.getService(); 120 * // use the service object to effect the business logic... 121 * } 122 *}</pre> 123 * 124 * <p>By way of an example that looks up a bean <b>by name</b>, consider 125 * the following service locator interface. Again, note that this 126 * interface is not dependent on any Spring APIs. 127 * 128 * <pre class="code">package a.b.c; 129 * 130 *public interface ServiceFactory { 131 * 132 * public MyService getService (String serviceName); 133 *}</pre> 134 * 135 * <p>A sample config in an XML-based 136 * {@link org.springframework.beans.factory.BeanFactory} might look as follows: 137 * 138 * <pre class="code"><beans> 139 * 140 * <!-- Prototype beans since we have state (both extend MyService) --> 141 * <bean id="specialService" class="a.b.c.SpecialService" singleton="false"/> 142 * <bean id="anotherService" class="a.b.c.AnotherService" singleton="false"/> 143 * 144 * <bean id="myServiceFactory" 145 * class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean"> 146 * <property name="serviceLocatorInterface" value="a.b.c.ServiceFactory"/> 147 * </bean> 148 * 149 * <bean id="clientBean" class="a.b.c.MyClientBean"> 150 * <property name="myServiceFactory" ref="myServiceFactory"/> 151 * </bean> 152 * 153 *</beans></pre> 154 * 155 * <p>The attendant {@code MyClientBean} class implementation might then 156 * look something like this: 157 * 158 * <pre class="code">package a.b.c; 159 * 160 *public class MyClientBean { 161 * 162 * private ServiceFactory myServiceFactory; 163 * 164 * // actual implementation provided by the Spring container 165 * public void setServiceFactory(ServiceFactory myServiceFactory) { 166 * this.myServiceFactory = myServiceFactory; 167 * } 168 * 169 * public void someBusinessMethod() { 170 * // get a 'fresh', brand new MyService instance 171 * MyService service = this.myServiceFactory.getService("specialService"); 172 * // use the service object to effect the business logic... 173 * } 174 * 175 * public void anotherBusinessMethod() { 176 * // get a 'fresh', brand new MyService instance 177 * MyService service = this.myServiceFactory.getService("anotherService"); 178 * // use the service object to effect the business logic... 179 * } 180 *}</pre> 181 * 182 * <p>See {@link ObjectFactoryCreatingFactoryBean} for an alternate approach. 183 * 184 * @author Colin Sampaleanu 185 * @author Juergen Hoeller 186 * @since 1.1.4 187 * @see #setServiceLocatorInterface 188 * @see #setServiceMappings 189 * @see ObjectFactoryCreatingFactoryBean 190 */ 191public class ServiceLocatorFactoryBean implements FactoryBean<Object>, BeanFactoryAware, InitializingBean { 192 193 @Nullable 194 private Class<?> serviceLocatorInterface; 195 196 @Nullable 197 private Constructor<Exception> serviceLocatorExceptionConstructor; 198 199 @Nullable 200 private Properties serviceMappings; 201 202 @Nullable 203 private ListableBeanFactory beanFactory; 204 205 @Nullable 206 private Object proxy; 207 208 209 /** 210 * Set the service locator interface to use, which must have one or more methods with 211 * the signatures {@code MyType xxx()} or {@code MyType xxx(MyIdType id)} 212 * (typically, {@code MyService getService()} or {@code MyService getService(String id)}). 213 * See the {@link ServiceLocatorFactoryBean class-level Javadoc} for 214 * information on the semantics of such methods. 215 */ 216 public void setServiceLocatorInterface(Class<?> interfaceType) { 217 this.serviceLocatorInterface = interfaceType; 218 } 219 220 /** 221 * Set the exception class that the service locator should throw if service 222 * lookup failed. The specified exception class must have a constructor 223 * with one of the following parameter types: {@code (String, Throwable)} 224 * or {@code (Throwable)} or {@code (String)}. 225 * <p>If not specified, subclasses of Spring's BeansException will be thrown, 226 * for example NoSuchBeanDefinitionException. As those are unchecked, the 227 * caller does not need to handle them, so it might be acceptable that 228 * Spring exceptions get thrown as long as they are just handled generically. 229 * @see #determineServiceLocatorExceptionConstructor 230 * @see #createServiceLocatorException 231 */ 232 public void setServiceLocatorExceptionClass(Class<? extends Exception> serviceLocatorExceptionClass) { 233 this.serviceLocatorExceptionConstructor = 234 determineServiceLocatorExceptionConstructor(serviceLocatorExceptionClass); 235 } 236 237 /** 238 * Set mappings between service ids (passed into the service locator) 239 * and bean names (in the bean factory). Service ids that are not defined 240 * here will be treated as bean names as-is. 241 * <p>The empty string as service id key defines the mapping for {@code null} and 242 * empty string, and for factory methods without parameter. If not defined, 243 * a single matching bean will be retrieved from the bean factory. 244 * @param serviceMappings mappings between service ids and bean names, 245 * with service ids as keys as bean names as values 246 */ 247 public void setServiceMappings(Properties serviceMappings) { 248 this.serviceMappings = serviceMappings; 249 } 250 251 @Override 252 public void setBeanFactory(BeanFactory beanFactory) throws BeansException { 253 if (!(beanFactory instanceof ListableBeanFactory)) { 254 throw new FatalBeanException( 255 "ServiceLocatorFactoryBean needs to run in a BeanFactory that is a ListableBeanFactory"); 256 } 257 this.beanFactory = (ListableBeanFactory) beanFactory; 258 } 259 260 @Override 261 public void afterPropertiesSet() { 262 if (this.serviceLocatorInterface == null) { 263 throw new IllegalArgumentException("Property 'serviceLocatorInterface' is required"); 264 } 265 266 // Create service locator proxy. 267 this.proxy = Proxy.newProxyInstance( 268 this.serviceLocatorInterface.getClassLoader(), 269 new Class<?>[] {this.serviceLocatorInterface}, 270 new ServiceLocatorInvocationHandler()); 271 } 272 273 274 /** 275 * Determine the constructor to use for the given service locator exception 276 * class. Only called in case of a custom service locator exception. 277 * <p>The default implementation looks for a constructor with one of the 278 * following parameter types: {@code (String, Throwable)} 279 * or {@code (Throwable)} or {@code (String)}. 280 * @param exceptionClass the exception class 281 * @return the constructor to use 282 * @see #setServiceLocatorExceptionClass 283 */ 284 @SuppressWarnings("unchecked") 285 protected Constructor<Exception> determineServiceLocatorExceptionConstructor(Class<? extends Exception> exceptionClass) { 286 try { 287 return (Constructor<Exception>) exceptionClass.getConstructor(String.class, Throwable.class); 288 } 289 catch (NoSuchMethodException ex) { 290 try { 291 return (Constructor<Exception>) exceptionClass.getConstructor(Throwable.class); 292 } 293 catch (NoSuchMethodException ex2) { 294 try { 295 return (Constructor<Exception>) exceptionClass.getConstructor(String.class); 296 } 297 catch (NoSuchMethodException ex3) { 298 throw new IllegalArgumentException( 299 "Service locator exception [" + exceptionClass.getName() + 300 "] neither has a (String, Throwable) constructor nor a (String) constructor"); 301 } 302 } 303 } 304 } 305 306 /** 307 * Create a service locator exception for the given cause. 308 * Only called in case of a custom service locator exception. 309 * <p>The default implementation can handle all variations of 310 * message and exception arguments. 311 * @param exceptionConstructor the constructor to use 312 * @param cause the cause of the service lookup failure 313 * @return the service locator exception to throw 314 * @see #setServiceLocatorExceptionClass 315 */ 316 protected Exception createServiceLocatorException(Constructor<Exception> exceptionConstructor, BeansException cause) { 317 Class<?>[] paramTypes = exceptionConstructor.getParameterTypes(); 318 Object[] args = new Object[paramTypes.length]; 319 for (int i = 0; i < paramTypes.length; i++) { 320 if (String.class == paramTypes[i]) { 321 args[i] = cause.getMessage(); 322 } 323 else if (paramTypes[i].isInstance(cause)) { 324 args[i] = cause; 325 } 326 } 327 return BeanUtils.instantiateClass(exceptionConstructor, args); 328 } 329 330 331 @Override 332 @Nullable 333 public Object getObject() { 334 return this.proxy; 335 } 336 337 @Override 338 public Class<?> getObjectType() { 339 return this.serviceLocatorInterface; 340 } 341 342 @Override 343 public boolean isSingleton() { 344 return true; 345 } 346 347 348 /** 349 * Invocation handler that delegates service locator calls to the bean factory. 350 */ 351 private class ServiceLocatorInvocationHandler implements InvocationHandler { 352 353 @Override 354 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 355 if (ReflectionUtils.isEqualsMethod(method)) { 356 // Only consider equal when proxies are identical. 357 return (proxy == args[0]); 358 } 359 else if (ReflectionUtils.isHashCodeMethod(method)) { 360 // Use hashCode of service locator proxy. 361 return System.identityHashCode(proxy); 362 } 363 else if (ReflectionUtils.isToStringMethod(method)) { 364 return "Service locator: " + serviceLocatorInterface; 365 } 366 else { 367 return invokeServiceLocatorMethod(method, args); 368 } 369 } 370 371 private Object invokeServiceLocatorMethod(Method method, Object[] args) throws Exception { 372 Class<?> serviceLocatorMethodReturnType = getServiceLocatorMethodReturnType(method); 373 try { 374 String beanName = tryGetBeanName(args); 375 Assert.state(beanFactory != null, "No BeanFactory available"); 376 if (StringUtils.hasLength(beanName)) { 377 // Service locator for a specific bean name 378 return beanFactory.getBean(beanName, serviceLocatorMethodReturnType); 379 } 380 else { 381 // Service locator for a bean type 382 return beanFactory.getBean(serviceLocatorMethodReturnType); 383 } 384 } 385 catch (BeansException ex) { 386 if (serviceLocatorExceptionConstructor != null) { 387 throw createServiceLocatorException(serviceLocatorExceptionConstructor, ex); 388 } 389 throw ex; 390 } 391 } 392 393 /** 394 * Check whether a service id was passed in. 395 */ 396 private String tryGetBeanName(@Nullable Object[] args) { 397 String beanName = ""; 398 if (args != null && args.length == 1 && args[0] != null) { 399 beanName = args[0].toString(); 400 } 401 // Look for explicit serviceId-to-beanName mappings. 402 if (serviceMappings != null) { 403 String mappedName = serviceMappings.getProperty(beanName); 404 if (mappedName != null) { 405 beanName = mappedName; 406 } 407 } 408 return beanName; 409 } 410 411 private Class<?> getServiceLocatorMethodReturnType(Method method) throws NoSuchMethodException { 412 Assert.state(serviceLocatorInterface != null, "No service locator interface specified"); 413 Class<?>[] paramTypes = method.getParameterTypes(); 414 Method interfaceMethod = serviceLocatorInterface.getMethod(method.getName(), paramTypes); 415 Class<?> serviceLocatorReturnType = interfaceMethod.getReturnType(); 416 417 // Check whether the method is a valid service locator. 418 if (paramTypes.length > 1 || void.class == serviceLocatorReturnType) { 419 throw new UnsupportedOperationException( 420 "May only call methods with signature '<type> xxx()' or '<type> xxx(<idtype> id)' " + 421 "on factory interface, but tried to call: " + interfaceMethod); 422 } 423 return serviceLocatorReturnType; 424 } 425 } 426 427}