KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > beans > factory > config > ServiceLocatorFactoryBean


1 /*
2  * Copyright 2002-2006 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.beans.factory.config;
18
19 import java.lang.reflect.Constructor JavaDoc;
20 import java.lang.reflect.InvocationHandler JavaDoc;
21 import java.lang.reflect.InvocationTargetException JavaDoc;
22 import java.lang.reflect.Method JavaDoc;
23 import java.lang.reflect.Proxy JavaDoc;
24 import java.util.Properties JavaDoc;
25
26 import org.springframework.beans.BeanUtils;
27 import org.springframework.beans.BeansException;
28 import org.springframework.beans.FatalBeanException;
29 import org.springframework.beans.factory.BeanFactory;
30 import org.springframework.beans.factory.BeanFactoryAware;
31 import org.springframework.beans.factory.BeanFactoryUtils;
32 import org.springframework.beans.factory.FactoryBean;
33 import org.springframework.beans.factory.InitializingBean;
34 import org.springframework.beans.factory.ListableBeanFactory;
35 import org.springframework.util.StringUtils;
36
37 /**
38  * A {@link org.springframework.beans.factory.FactoryBean} implementation that
39  * takes an interface which must have one or more methods with
40  * the signatures <code>MyType xxx()</code> or <code>MyType xxx(MyIdType id)</code>
41  * (typically, <code>MyService getService()</code> or <code>MyService getService(String id)</code>)
42  * and creates a dynamic proxy which implements that interface, delegating to an
43  * underlying {@link org.springframework.beans.factory.BeanFactory}.
44  *
45  * <p>Such service locators permit the decoupling of calling code from
46  * the {@link org.springframework.beans.factory.BeanFactory} API, by using an
47  * appropriate custom locator interface. They will typically be used for
48  * <b>prototype beans</b>, i.e. for factory methods that are supposed to
49  * return a new instance for each call. The client receives a reference to the
50  * service locator via setter or constructor injection, to be able to invoke
51  * the locator's factory methods on demand. <b>For singleton beans, direct
52  * setter or constructor injection of the target bean is preferable.</b>
53  *
54  * <p>On invocation of the no-arg factory method, or the single-arg factory
55  * method with a String id of <code>null</code> or empty String, if exactly
56  * <b>one</b> bean in the factory matches the return type of the factory
57  * method, that bean is returned, otherwise a
58  * {@link org.springframework.beans.factory.NoSuchBeanDefinitionException}
59  * is thrown.
60  *
61  * <p>On invocation of the single-arg factory method with a non-null (and
62  * non-empty) argument, the proxy returns the result of a
63  * {@link org.springframework.beans.factory.BeanFactory#getBean(String)} call,
64  * using a stringified version of the passed-in id as bean name.
65  *
66  * <p>A factory method argument will usually be a String, but can also be an
67  * int or a custom enumeration type, for example, stringified via
68  * <code>toString</code>. The resulting String can be used as bean name as-is,
69  * provided that corresponding beans are defined in the bean factory.
70  * Alternatively, {@link #setServiceMappings(java.util.Properties) a custom mapping}
71  * between service ids and bean names can be defined.
72  *
73  * <p>By way of an example, consider the following service locator interface.
74  * Note that this interface is not dependant on any Spring APIs.
75  *
76  * <pre class="code">package a.b.c;
77  *
78  *public interface ServiceFactory {
79  *
80  * public MyService getService ();
81  *}</pre>
82  *
83  * <p>A sample config in an XML-based
84  * {@link org.springframework.beans.factory.BeanFactory} might look as follows:
85  *
86  * <pre class="code">&lt;beans>
87  *
88  * &lt;!-- Prototype bean since we have state -->
89  * &lt;bean id="myService" class="a.b.c.MyService" singleton="false"/>
90  *
91  * &lt;!-- will lookup the above 'myService' bean by *TYPE* -->
92  * &lt;bean id="myServiceFactory"
93  * class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean">
94  * &lt;property name="serviceLocatorInterface" value="a.b.c.ServiceFactory"/>
95  * &lt;/bean>
96  *
97  * &lt;bean id="clientBean" class="a.b.c.MyClientBean">
98  * &lt;property name="myServiceFactory" ref="myServiceFactory"/>
99  * &lt;/bean>
100  *
101  *&lt;/beans></pre>
102  *
103  * <p>The attendant <code>MyClientBean</code> 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 dependant 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">&lt;beans>
139  *
140  * &lt;!-- Prototype beans since we have state (both extend MyService) -->
141  * &lt;bean id="specialService" class="a.b.c.SpecialService" singleton="false"/>
142  * &lt;bean id="anotherService" class="a.b.c.AnotherService" singleton="false"/>
143  *
144  * &lt;bean id="myServiceFactory"
145  * class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean">
146  * &lt;property name="serviceLocatorInterface" value="a.b.c.ServiceFactory"/>
147  * &lt;/bean>
148  *
149  * &lt;bean id="clientBean" class="a.b.c.MyClientBean">
150  * &lt;property name="myServiceFactory" ref="myServiceFactory"/>
151  * &lt;/bean>
152  *
153  *&lt;/beans></pre>
154  *
155  * <p>The attendant <code>MyClientBean</code> 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  */

191 public class ServiceLocatorFactoryBean implements FactoryBean, BeanFactoryAware, InitializingBean {
192
193     private Class JavaDoc serviceLocatorInterface;
194
195     private Constructor JavaDoc serviceLocatorExceptionConstructor;
196
197     private Properties JavaDoc serviceMappings;
198
199     private ListableBeanFactory beanFactory;
200
201     private Object JavaDoc proxy;
202
203
204     /**
205      * Set the service locator interface to use, which must have one or more methods with
206      * the signatures <code>MyType xxx()</code> or <code>MyType xxx(MyIdType id)</code>
207      * (typically, <code>MyService getService()</code> or <code>MyService getService(String id)</code>).
208      * See the {@link ServiceLocatorFactoryBean class-level Javadoc} for
209      * information on the semantics of such methods.
210      * @param interfaceType the {@link java.lang.Class} of the interface to be used for the service locator
211      */

212     public void setServiceLocatorInterface(Class JavaDoc interfaceType) {
213         this.serviceLocatorInterface = interfaceType;
214     }
215
216     /**
217      * Set the exception class that the service locator should throw if service
218      * lookup failed. The specified exception class must have a constructor
219      * with one of the following parameter types: <code>(String, Throwable)</code>
220      * or <code>(Throwable)</code> or <code>(String)</code>.
221      * <p>If not specified, subclasses of Spring's BeansException will be thrown,
222      * for example NoSuchBeanDefinitionException. As those are unchecked, the
223      * caller does not need to handle them, so it might be acceptable that
224      * Spring exceptions get thrown as long as they are just handled generically.
225      * @see #determineServiceLocatorExceptionConstructor(Class)
226      * @see #createServiceLocatorException(java.lang.reflect.Constructor, org.springframework.beans.BeansException)
227      * @see org.springframework.beans.BeansException
228      * @see org.springframework.beans.factory.NoSuchBeanDefinitionException
229      */

230     public void setServiceLocatorExceptionClass(Class JavaDoc serviceLocatorExceptionClass) {
231         if (serviceLocatorExceptionClass != null && !Exception JavaDoc.class.isAssignableFrom(serviceLocatorExceptionClass)) {
232             throw new IllegalArgumentException JavaDoc(
233                     "serviceLocatorException [" + serviceLocatorExceptionClass.getName() + "] is not a subclass of Exception");
234         }
235         this.serviceLocatorExceptionConstructor =
236                 determineServiceLocatorExceptionConstructor(serviceLocatorExceptionClass);
237     }
238
239     /**
240      * Set mappings between service ids (passed into the service locator)
241      * and bean names (in the bean factory). Service ids that are not defined
242      * here will be treated as bean names as-is.
243      * <p>The empty string as service id key defines the mapping for <code>null</code> and
244      * empty string, and for factory methods without parameter. If not defined,
245      * a single matching bean will be retrieved from the bean factory.
246      * @param serviceMappings mappings between service ids and bean names,
247      * with service ids as keys as bean names as values
248      */

249     public void setServiceMappings(Properties JavaDoc serviceMappings) {
250         this.serviceMappings = serviceMappings;
251     }
252
253     public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
254         if (!(beanFactory instanceof ListableBeanFactory)) {
255             throw new FatalBeanException(
256                     "ServiceLocatorFactoryBean needs to run in a BeanFactory that is a ListableBeanFactory");
257         }
258         this.beanFactory = (ListableBeanFactory) beanFactory;
259     }
260
261     public void afterPropertiesSet() {
262         if (this.serviceLocatorInterface == null) {
263             throw new IllegalArgumentException JavaDoc("serviceLocatorInterface is required");
264         }
265
266         // Create service locator proxy.
267
this.proxy = Proxy.newProxyInstance(
268                 this.serviceLocatorInterface.getClassLoader(),
269                 new Class JavaDoc[] {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)</code>
279      * or <code>(Throwable)</code> or <code>(String)</code>.
280      * @param exceptionClass the exception class
281      * @return the constructor to use
282      * @see #setServiceLocatorExceptionClass
283      */

284     protected Constructor JavaDoc determineServiceLocatorExceptionConstructor(Class JavaDoc exceptionClass) {
285         try {
286             return exceptionClass.getConstructor(new Class JavaDoc[] {String JavaDoc.class, Throwable JavaDoc.class});
287         }
288         catch (NoSuchMethodException JavaDoc ex) {
289             try {
290                 return exceptionClass.getConstructor(new Class JavaDoc[] {Throwable JavaDoc.class});
291             }
292             catch (NoSuchMethodException JavaDoc ex2) {
293                 try {
294                     return exceptionClass.getConstructor(new Class JavaDoc[] {String JavaDoc.class});
295                 }
296                 catch (NoSuchMethodException JavaDoc ex3) {
297                     throw new IllegalArgumentException JavaDoc(
298                             "serviceLocatorException [" + exceptionClass.getName() +
299                             "] neither has a (String, Throwable) constructor nor a (String) constructor");
300                 }
301             }
302         }
303     }
304
305     /**
306      * Create a service locator exception for the given cause.
307      * Only called in case of a custom service locator exception.
308      * <p>The default implementation can handle all variations of
309      * message and exception arguments.
310      * @param exceptionConstructor the constructor to use
311      * @param cause the cause of the service lookup failure
312      * @return the service locator exception to throw
313      * @see #setServiceLocatorExceptionClass
314      */

315     protected Exception JavaDoc createServiceLocatorException(Constructor JavaDoc exceptionConstructor, BeansException cause) {
316         Class JavaDoc[] paramTypes = exceptionConstructor.getParameterTypes();
317         Object JavaDoc[] args = new Object JavaDoc[paramTypes.length];
318         for (int i = 0; i < paramTypes.length; i++) {
319             if (paramTypes[i].equals(String JavaDoc.class)) {
320                 args[i] = cause.getMessage();
321             }
322             else if (paramTypes[i].isInstance(cause)) {
323                 args[i] = cause;
324             }
325         }
326         return (Exception JavaDoc) BeanUtils.instantiateClass(exceptionConstructor, args);
327     }
328
329
330     public Object JavaDoc getObject() {
331         return this.proxy;
332     }
333
334     public Class JavaDoc getObjectType() {
335         return this.serviceLocatorInterface;
336     }
337
338     public boolean isSingleton() {
339         return true;
340     }
341
342
343     /**
344      * Invocation handler that delegates service locator calls to the bean factory.
345      */

346     private class ServiceLocatorInvocationHandler implements InvocationHandler JavaDoc {
347
348         public Object JavaDoc invoke(Object JavaDoc proxy, Method JavaDoc method, Object JavaDoc[] args) throws Throwable JavaDoc {
349             Object JavaDoc service;
350             if (Object JavaDoc.class.equals(method.getDeclaringClass())) {
351                 service = invokeStandardObjectMethod(method, args);
352             } else {
353                 service = invokeServiceLocatorMethod(method, args);
354             }
355             return service;
356         }
357
358         public String JavaDoc toString() {
359             return "Service locator: " + serviceLocatorInterface.getName();
360         }
361
362
363         private Object JavaDoc invokeServiceLocatorMethod(Method JavaDoc method, Object JavaDoc[] args) throws Exception JavaDoc {
364             Class JavaDoc serviceLocatorMethodReturnType = getServiceLocatorMethodReturnType(method);
365             try {
366                 String JavaDoc beanName = tryGetBeanName(args);
367                 if (StringUtils.hasLength(beanName)) {
368                     // Service locator for a specific bean name.
369
return beanFactory.getBean(beanName, serviceLocatorMethodReturnType);
370                 }
371                 else {
372                     // Service locator for a bean type.
373
return BeanFactoryUtils.beanOfTypeIncludingAncestors(beanFactory, serviceLocatorMethodReturnType);
374                 }
375             }
376             catch (BeansException ex) {
377                 if (serviceLocatorExceptionConstructor != null) {
378                     throw createServiceLocatorException(serviceLocatorExceptionConstructor, ex);
379                 }
380                 throw ex;
381             }
382         }
383
384         /**
385          * Check whether a service id was passed in.
386          */

387         private String JavaDoc tryGetBeanName(Object JavaDoc[] args) {
388             String JavaDoc beanName = "";
389             if (args != null && args.length == 1 && args[0] != null) {
390                 beanName = args[0].toString();
391             }
392             // Look for explicit serviceId-to-beanName mappings.
393
if (serviceMappings != null) {
394                 String JavaDoc mappedName = serviceMappings.getProperty(beanName);
395                 if (mappedName != null) {
396                     beanName = mappedName;
397                 }
398             }
399             return beanName;
400         }
401
402         private Class JavaDoc getServiceLocatorMethodReturnType(Method JavaDoc method) throws NoSuchMethodException JavaDoc {
403             Class JavaDoc[] paramTypes = method.getParameterTypes();
404             Method JavaDoc interfaceMethod = serviceLocatorInterface.getMethod(method.getName(), paramTypes);
405             Class JavaDoc serviceLocatorReturnType = interfaceMethod.getReturnType();
406
407             // Check whether the method is a valid service locator.
408
if (paramTypes.length > 1 || void.class.equals(serviceLocatorReturnType)) {
409                 throw new UnsupportedOperationException JavaDoc(
410                         "May only call methods with signature '<type> xxx()' or '<type> xxx(<idtype> id)' " +
411                         "on factory interface, but tried to call: " + interfaceMethod);
412             }
413             return serviceLocatorReturnType;
414         }
415
416         /**
417          * It's normal to get here for non service locator interface method calls
418          *(toString, equals, etc.). Simply apply the call to the invocation handler object.
419          */

420         private Object JavaDoc invokeStandardObjectMethod(Method JavaDoc method, Object JavaDoc[] args) throws Throwable JavaDoc {
421             try {
422                 return method.invoke(this, args);
423             }
424             catch (InvocationTargetException JavaDoc invEx) {
425                 throw invEx.getTargetException();
426             }
427         }
428
429     }
430
431 }
432
Popular Tags