/* * Copyright 2002-2004 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.beans.factory.config; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; import org.springframework.beans.FatalBeanException; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; /** * <p><code>FactoryBean</code> implementation that creates proxy objects that route calls * to methods on a service creation interface to corresponding constructors on a service implementation * type. </p> * <p/> * Using this class, applications can create services using runtime-determined configuration without * knowing the exact implementation type of the service and without requiring client code to populate * beans with configuration data via setters. * </p> * <p/> * Usage of this class involves the definition of a service creation interface with methods such as * MyService xxx([args]). This class will create a proxy of the service creation interface that will * re-route calls to the methods on the service creation interface to constructors on some service * implementation type. * </p> * <p/> * For example, consider this interface: * <pre> * public interface ServiceCreator { * MyService createMyService(String name, int age); * } * </pre> * Here, the <code>ServiceCreator</code> interface is used to create an instance of some implementation * of the <code>MyService</code> interface. At runtime, calls to <code>createMyService(String, int)</code> * on the <code>ServiceCreator</code> proxy generated by this class will be routed to a constructor on * the implementation type with args (String, int), for example consider the following implementation type: * <pre> * public class MyServiceImpl implements MyService { * <p/> * public MyServiceImpl(String name, int age) { * // do something * } * } * </pre> * Here you can see the constructor <code>MyServiceImpl(String name, int age)</code> which corresponds to * the <code>createMyService(String name, int age)</code> method on the <code>ServiceCreator</code> interface. * Obviously, the service implementation type must be compatible with the return type of all methods declared * in the service creation interface and all methods in the service creation interface must have parameter lists * that can be matched to constructors on the service implementation type. * </p> * <p/> * The service creation interface is specified via <code>setServiceCreatorInterface</code> and the service * implementation type is specified via <code>setServiceImplementationType</code>. Both properties are required. * * @author Rob Harrop * @see ServiceLocatorFactoryBean * @see #setServiceCreatorInterface(Class) * @see #setServiceImplementationType(Class) * @since 1.2 */ public class ServiceCreationFactoryBean implements FactoryBean, InitializingBean { /** * Stores the interface that the proxy should implement. */ private Class serviceCreatorInterface; /** * Stores the service implementation type. */ private Class serviceImplementationType; /** * Stores the proxy. */ private Object proxy; /** * Retreives the service creation proxy. */ public Object getObject() throws Exception { return this.proxy; } /** * Returns the type specified by the service implementation type. * * @return the service implementation type. */ public Class getObjectType() { return this.serviceCreatorInterface; } /** * Always returns <code>true</code>. */ public boolean isSingleton() { return true; } /** * Sets the service creator interface. * * @param serviceCreatorInterface the interface that the proxy should implement. */ public void setServiceCreatorInterface(Class serviceCreatorInterface) { if (!serviceCreatorInterface.isInterface()) { throw new FatalBeanException("Class [" + serviceCreatorInterface.getName() + "] provided for service creator interface must be an interface."); } this.serviceCreatorInterface = serviceCreatorInterface; } /** * Sets the service implementation type. * * @param serviceImplementationType */ public void setServiceImplementationType(Class serviceImplementationType) { if (serviceImplementationType.isInterface() || Modifier.isAbstract(serviceImplementationType.getModifiers())) { throw new FatalBeanException("Class [" + serviceImplementationType.getName() + "] provided for service implementation type must be a concrete class."); } this.serviceImplementationType = serviceImplementationType; } public void afterPropertiesSet() throws Exception { if (serviceCreatorInterface == null) { throw new FatalBeanException("Property [serviceCreatorInterface] of [" + ServiceCreationFactoryBean.class.getName() + "] is required."); } if (serviceImplementationType == null) { throw new FatalBeanException("Property [serviceImplementationType] of [" + ServiceCreationFactoryBean.class.getName() + "] is required."); } validateCreationMethods(); createProxy(); } /** * Creates the actual proxy to the service creator interface. */ private void createProxy() { ClassLoader loader = Thread.currentThread().getContextClassLoader(); InvocationHandler handler = new ServiceCreationInvocationHandler(this.serviceImplementationType); this.proxy = Proxy.newProxyInstance(loader, new Class[]{this.serviceCreatorInterface}, handler); } /** * Validates that all the creation methods on the service creation * interface have return types that are compatible with the service implementation * type, and parameters lists that match a constructor on the service implementation. * * @throws FatalBeanException if validation fails. */ private void validateCreationMethods() throws FatalBeanException { Method[] methods = serviceCreatorInterface.getMethods(); for (int x = 0; x < methods.length; x++) { Method method = methods[x]; validateReturnType(method); validateConstructorMatch(method); } } /** * Validates that the given <code>Method</code> can be matched to a constructor on * the service implementation type that has the same parameter list. * * @param method the <code>Method</code> to validate. * @throws FatalBeanException if the <code>Method</code> does not having a matching constructor. */ private void validateConstructorMatch(Method method) throws FatalBeanException { // check that the method has a corresponding constructor try { Constructor ctor = serviceImplementationType.getConstructor(method.getParameterTypes()); } catch (NoSuchMethodException ex) { throw new FatalBeanException("Method [" + method + "] has no corresponding constructor on the service implementation type [" + serviceImplementationType.getName() + "]."); } } /** * Validates that the return type of the supplied <code>Method</code> is compatible with the * service implementation type. * * @param method the <code>Method</code> to validate * @throws FatalBeanException if the return type is not compatible. */ private void validateReturnType(Method method) throws FatalBeanException { Class returnType = method.getReturnType(); // test return type is compatible with // service implementation type if (!returnType.isAssignableFrom(serviceImplementationType)) { throw new FatalBeanException("Return type of method [" + method + "] is incompatible with service implementation type [" + serviceImplementationType.getName() + "]."); } } /** * <code>InvocationHandler</code> implementation that routes method calls to the corresponding * constructor on the service implementation type and returns the resultant instance of the * service implementation type. */ private class ServiceCreationInvocationHandler implements InvocationHandler { /** * Stores the service implementation type. */ private Class serviceImplementationType; /** * Creates a new <code>ServiceCreationMethodInterceptor</code> for the supplied * service implementation type. * * @param serviceImplementationType the implementation type of the service. */ public ServiceCreationInvocationHandler(Class serviceImplementationType) { this.serviceImplementationType = serviceImplementationType; } /** * Re-routes method calls to the correponding constructor on the service implementation type. */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Constructor ctor = serviceImplementationType.getConstructor(method.getParameterTypes()); return ctor.newInstance(args); } } }