/*
* 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);
}
}
}