In a spring based project I am working on, there's a layer of functionality for calling web service. For each web service operation, a method is created with almost same code but with some different, operation specific, information(e.g. service name, operation name, namespaces, etc).
I am replacing this layer with interfaces and annotated methods. For example, below code is provided for operation "fetchBar" of web service("foo").
package a.b.c.webservices;
@WebService(service="foo", namespace="...")
public interface FooWebService {
@WebServiceOperation(operation="fetchBar")
BarRespons fetchBar(BarRequest request) throws WebServiceException;
}
Now I want, with some mechanism, spring allow me to create dynamic proxy beans from some specified package(s) and I can use following code to call web service.
package a.b.c.business;
import a.b.c.webservices.FooWebService;
public class FooBusiness {
@Autowired
FooWebService fooWebService;
public Bar getBar() {
Bar bar = null;
BarRequest request;
//create request
BarResponse response = fooWebService.fetchBar(request);
//extrac bar from response
return bar;
}
}
To achieve this I have created dynamic beans instances using java.lang.reflect.Proxy.newProxyInstance
by providing it implementation of InvocationHandler
. But Autowiring doesn't work in provided implementation of invocationHandler
and in its further dependencies.
I tried following ways to achieve this.
BeanFactoryPostProcessor.postProcessBeanFactory
and registered beans using ConfigurableListableBeanFactory.registerSingleton
method.ImportBeanDefinitionRegistrar.registerBeanDefinitions
and tried to use BeanDefinitionRegistry.registerBeanDefinition
but I am confused how to provide correct Bean definition that supports Autowiring.Can any one tell me what is missing? Please guide me if I am not going in right direction.
Spring @Autowired annotation is used for automatic dependency injection. Spring framework is built on dependency injection and we inject the class dependencies through spring bean configuration file.
The Spring framework enables automatic dependency injection. In other words, by declaring all the bean dependencies in a Spring configuration file, Spring container can autowire relationships between collaborating beans. This is called Spring bean autowiring.
Autowiring happens by placing an instance of one bean into the desired field in an instance of another bean. Both classes should be beans, i.e. they should be defined to live in the application context. What is "living" in the application context? This means that the context instantiates the objects, not you.
Autowiring feature of spring framework enables you to inject the object dependency implicitly. It internally uses setter or constructor injection. Autowiring can't be used to inject primitive and string values.
Here's how I implemented all the functionality that creates beans of 'WebService' annotated interfaces and also supports Autowiring inside proxy implementation. (package declaration and import statements are omitted in below code)
First of all I created WebService
and WebServiceOperation
annotation.
WebService Annotation
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface WebService {
String service();
String namespace();
}
WebService Operation Annotation
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface WebServiceOperation {
String operation();
}
Next step is to scan all WebService
annotated interfaces from specified packages. Spring provides ClassPathScanningCandidateComponentProvider
for package scanning but it does not detect interfaces. Please see this question and it's answer for more details. So I extended ClassPathScanningCandidateComponentProvider
and overrode isCandidateComponent
method.
ClassPathScanner
public class ClassPathScanner extends ClassPathScanningCandidateComponentProvider {
public ClassPathScanner(final boolean useDefaultFilters) {
super(useDefaultFilters);
}
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isIndependent();
}
}
At this point I created EnableWebServices
annotation to enable web services and to provide web service packages that contain WebService
annotated interfaces.
EnableWebServices Annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({
WebServiceProxyConfig.class,
WebServiceProxyBeansRegistrar.class
})
public @interface EnableWebServices {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
}
This annotation can be applied to some Configuration
annotated class with packages to scan interfaces, as below.
@EnableWebServices({
"a.b.c.webservices",
"x.y.z.webservices"
})
It's time to think about dynamic proxy creation that will invoke actual web service from information given in WebService
and WebServiceOperation
annotations. Java provides a mechanism to create dynamic proxy which requires to provide implementation of InvocationHandler
interface and provide logic in its invoke
method. I named this implementaiton as WebServiceProxy
Suppose a bean of type 'TheWebServiceCaller' contains all nasty logic to call a web service. I just have inject it and to invoke it's call
method with a TheWebServiceInfo
(extracted from WebService
and WebServiceOperation
annotations) and request object.
TheWebServiceInfo(Suppose all fields have getters and setters)
public class TheWebServiceInfo {
private String service;
private String namespace;
private String operation;
}
WebServiceProxy
public class WebServiceProxy implements InvocationHandler {
@Autowired
private TheWebServiceCaller caller;
@Override
public Object invoke(Object target, Method method, Object[] args) throws Exception {
Object request = (null != args && args.length > 0) ? args[0] : null;
WebService webService = method.getDeclaringClass().getAnnotation(WebService.class);
WebServiceOperation webServiceOperation = method.getAnnotation(WebServiceOperation.class);
TheWebServiceInfo theInfo = createTheWebServiceInfo(webService, webServiceOperation);
return caller.call(theInfo, request);
}
private TheWebServiceInfo createTheWebServiceInfo(WebService webService, WebServiceOperation webServiceOperation) {
TheWebServiceInfo theInfo = new TheWebServiceInfo();
theInfo.setService(webService.service());
theInfo.setNamespace(webService.namespace());
theInfo.setOperation(webServiceOperation.operation());
return theInfo;
}
}
Implementaion of InvocationHandler
is passed to Proxy.newProxyInstance
(along with some other information) to create proxy objects. I need separat proxy objectes for each WebService
annotated interface. I will now create a factory to proxy instances creation and name is as 'WebServiceProxyBeanFactory'. Instances created by this factory will become beans for corresponding WebService
annotated interfaces.
A bit later, I will expose 'WebServiceProxy' and WebServiceProxyBeanFactory
as beans. In 'WebServiceProxyBeanFactory', I will inject WebServiceProxy
and used it. Please note that createWebServiceProxyBean
uses generics. This is important.
WebServiceProxyBeanFactory
public class WebServiceProxyBeanFactory {
@Autowired
WebServiceProxy webServiceProxy;
@SuppressWarnings("unchecked")
public <WS> WS createWebServiceProxyBean(ClassLoader classLoader, Class<WS> clazz) {
return (WS) Proxy.newProxyInstance(classLoader, new Class[] {clazz}, webServiceProxy);
}
}
If you remember, earlier I have imported WebServiceProxyConfig
in EnableWebServices
annotations. WebServiceProxyConfig
is used to expose WebServiceProxy
and WebServiceProxyBeanFactory
as beans.
WebServiceProxyConfig
@Configuration
public class WebServiceProxyConfig {
@Bean
public WebServiceProxy webServiceProxy() {
return new WebServiceProxy();
}
@Bean(name = "webServiceProxyBeanFactory")
public WebServiceProxyBeanFactory webServiceProxyBeanFactory() {
return new WebServiceProxyBeanFactory();
}
}
Now everything is in place. it's time to write a hook to start scanning Web service packages and register dynamic proxies as beans. I will provide implementation of ImportBeanDefinitionRegistrar
.
WebServiceProxyBeansRegistrar
@Configuration
public class WebServiceProxyBeansRegistrar implements ImportBeanDefinitionRegistrar, BeanClassLoaderAware {
private ClassPathScanner classpathScanner;
private ClassLoader classLoader;
public WebServiceProxyBeansRegistrar() {
classpathScanner = new ClassPathScanner(false);
classpathScanner.addIncludeFilter(new AnnotationTypeFilter(WebService.class));
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
String[] basePackages = getBasePackages(importingClassMetadata);
if (ArrayUtils.isNotEmpty(basePackages)) {
for (String basePackage : basePackages) {
createWebServicProxies(basePackage, registry);
}
}
}
private String[] getBasePackages(AnnotationMetadata importingClassMetadata) {
String[] basePackages = null;
MultiValueMap<String, Object> allAnnotationAttributes =
importingClassMetadata.getAllAnnotationAttributes(EnableWebServices.class.getName());
if (MapUtils.isNotEmpty(allAnnotationAttributes)) {
basePackages = (String[]) allAnnotationAttributes.getFirst("basePackages");
}
return basePackages;
}
private void createWebServicProxies(String basePackage, BeanDefinitionRegistry registry) {
try {
for (BeanDefinition beanDefinition : classpathScanner.findCandidateComponents(basePackage)) {
Class<?> clazz = Class.forName(beanDefinition.getBeanClassName());
WebService webService = clazz.getAnnotation(WebService.class);
String beanName = StringUtils.isNotEmpty(webService.bean())
? webService.bean() : ClassUtils.getShortNameAsProperty(clazz);
GenericBeanDefinition proxyBeanDefinition = new GenericBeanDefinition();
proxyBeanDefinition.setBeanClass(clazz);
ConstructorArgumentValues args = new ConstructorArgumentValues();
args.addGenericArgumentValue(classLoader);
args.addGenericArgumentValue(clazz);
proxyBeanDefinition.setConstructorArgumentValues(args);
proxyBeanDefinition.setFactoryBeanName("webServiceProxyBeanFactory");
proxyBeanDefinition.setFactoryMethodName("createWebServiceProxyBean");
registry.registerBeanDefinition(beanName, proxyBeanDefinition);
}
} catch (Exception e) {
System.out.println("Exception while createing proxy");
e.printStackTrace();
}
}
}
In this class, I extracted all packages provided in EnableWebServices
annotation. for each extracted package, I used ClassPathScanner
to scan. (Here logic can be refined to filter only WebService
annotated interfaces). For each detected interface, I have registered a bean definitions. Please note I have used webServiceProxyBeanFactory
and called its createWebServiceProxyBean
with classLoader and type of interface. This factory method, when invoked by spring later, will return bean of same type as that of interface, so bean with correct type is registered. This bean can be injected anywhere with interface type. Moreover, WebServiceProxy
can inject and use any other bean. So autowiring will also work as expected.
Is your InvocationHandler a bean? You should create it as a bean, not just a simple object to get Autowired working
I was thinking about the same problem but in a slightly more lightweight context. I don't need to load dynamicaly all the webservice clients. So instead I used a FactoryBean and within this factory bean I constructed the dynamic proxy. Here is one example where Autowiring of the service works:
public class CurrencyServiceWithDynamicProxy extends AbstractFactoryBean<CurrencyService> {
ServiceClientConfiguration clientConfiguration;
Object proxy;
@Autowired
public CurrencySyncFactoryDynamicProxy(ServiceClientConfigurationProvider serviceClientConfigurationProvider) {
this.clientConfiguration = serviceClientConfigurationProvider.createClientConfig("currency");
proxy = Proxy.newProxyInstance(getClass().getClassLoader(), new Class<?>[] { getObjectType() }, new MyInvocationHandler());
}
@Override
public Class<CurrencySync> getObjectType() {
// TODO Auto-generated method stub
return CurrencyService.class;
}
@Override
public CurrencySync createInstance() throws Exception {
// do some creational logic
return (CurrencySync)proxy;
}
public CurrencySync createService() {
JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
factory.setServiceClass(getObjectType());
factory.getFeatures().add(som features);
return getObjectType().cast(factory.create());
}
}
With respect of the accepted answer this factory example can easily be extended into a more dynamic version.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With