Reputation: 51421
Say I have the following structure with a service interface ServiceInterface
and a couple of components implementing it: ProductAService
and ProductBService
I also have a RequestContext
bean that has a qualifying property that says that we're say currently processing ProductA or ProductB. How can then automatically inject with autowiring or other annotation the correct implementation (ProductAService or ProductBService) into some service that needs it (ServiceThatNeedsServiceInterface
below).
public interface ServiceInterface {
void someMethod();
}
@Component(name="ProductAService")
public class ProductAService implements ServiceInterface {
@Override public void someMethod() {
System.out.println("Hello, A Service");
}
}
@Component(name="ProductBService")
public class ProductBService implements ServiceInterface {
@Override public void someMethod() {
System.out.println("Hello, B Service");
}
}
@Component
public class ServiceThatNeedsServiceInterface {
// What to do here???
@Autowired
ServiceInterface service;
public void useService() {
service.someMethod();
}
}
@Component
@Scope( value = WebApplicationContext.SCOPE_REQUEST )
public class RequestContext {
String getSomeQualifierProperty();
}
Upvotes: 10
Views: 12216
Reputation: 12023
Spring Source were referring to your issue when they created the ServiceLocatorFactoryBean back in version 1.1.4. In order to use it you need to add an interface similar to the one below:
public interface ServiceLocator {
//ServiceInterface service name is the one
//set by @Component
public ServiceInterface lookup(String serviceName);
}
You need to add the following snippet to your applicationContext.xml
<bean id="serviceLocatorFactoryBean"
class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean">
<property name="serviceLocatorInterface"
value="org.haim.springframwork.stackoverflow.ServiceLocator" />
</bean>
Now your ServiceThatNeedsServiceInterface will look similar to the one below:
@Component
public class ServiceThatNeedsServiceInterface {
// What to do here???
// @Autowired
// ServiceInterface service;
/*
* ServiceLocator lookup returns the desired implementation
* (ProductAService or ProductBService)
*/
@Autowired
private ServiceLocator serviceLocatorFactoryBean;
//Let’s assume we got this from the web request
public RequestContext context;
public void useService() {
ServiceInterface service =
serviceLocatorFactoryBean.lookup(context.getQualifier());
service.someMethod();
}
}
ServiceLocatorFactoryBean will return the desired service based on the RequestContext qualifier. Apart from spring annotations your code is not depended on Spring. I executed the following unit test for the above
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:META-INF/spring/applicationContext.xml" })
public class ServiceThatNeedsServiceInterfaceTest {
@Autowired
ServiceThatNeedsServiceInterface serviceThatNeedsServiceInterface;
@Test
public void testUseService() {
//As we are not running from a web container
//so we set the context directly to the service
RequestContext context = new RequestContext();
context.setQualifier("ProductAService");
serviceThatNeedsServiceInterface.context = context;
serviceThatNeedsServiceInterface.useService();
context.setQualifier("ProductBService");
serviceThatNeedsServiceInterface.context = context;
serviceThatNeedsServiceInterface.useService();
}
}
The console will display
Hello, A Service
Hello, B Service
A word of warning. The API documentation states that
“Such service locators … will typically be used for prototype beans, i.e. for factory methods that are supposed to return a new instance for each call… For singleton beans, direct setter or constructor injection of the target bean is preferable.”
I cannot understand why this may cause an issue. In my code it returns the same service on two sequence calls to serviceThatNeedsServiceInterface.useService();
You can find the source code for my example in GitHub
Upvotes: 11
Reputation: 1908
You could use the @Qualifier annotation in combination with aliases. See an example of how it is used to load a bean based on a property here. You could modify this approach and change the property/alias in the requestcontext...
Upvotes: 0
Reputation: 2980
This may help you:
Use
AutowireCapeableBeanFactory.autowireBean(Object existingBean)
OR
AutowireCapeableBeanFactory.autowireBeanProperties(Object existingBean, int autowireMode, boolean dependencyCheck)
AutowireCapeableBeanFactory.autowireBean(Object existingBean) AutowireCapeableBeanFactory.autowireBeanProperties(Object existingBean, int autowireMode, boolean dependencyCheck)
Upvotes: 1
Reputation: 128749
The only way I can think to do something like what you're looking for is to create something like a FactoryBean that returns the appropriate implementation based on the RequestContext property. Here's something I slapped together that has the behavior you want:
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.WebApplicationContext;
import javax.servlet.http.HttpServletRequest;
public class InjectionQualifiedByProperty {
@Controller
@Scope(WebApplicationContext.SCOPE_REQUEST)
public static class DynamicallyInjectedController {
@Autowired
@Qualifier("picker")
Dependency dependency;
@RequestMapping(value = "/sayHi", method = RequestMethod.GET)
@ResponseBody
public String sayHi() {
return dependency.sayHi();
}
}
public interface Dependency {
String sayHi();
}
@Configuration
public static class Beans {
@Bean
@Scope(WebApplicationContext.SCOPE_REQUEST)
@Qualifier("picker")
FactoryBean<Dependency> dependencyPicker(final RequestContext requestContext,
final BobDependency bob, final FredDependency fred) {
return new FactoryBean<Dependency>() {
@Override
public Dependency getObject() throws Exception {
if ("bob".equals(requestContext.getQualifierProperty())) {
return bob;
} else {
return fred;
}
}
@Override
public Class<?> getObjectType() {
return Dependency.class;
}
@Override
public boolean isSingleton() {
return false;
}
};
}
}
@Component
public static class BobDependency implements Dependency {
@Override
public String sayHi() {
return "Hi, I'm Bob";
}
}
@Component
public static class FredDependency implements Dependency {
@Override
public String sayHi() {
return "I'm not Bob";
}
}
@Component
@Scope(WebApplicationContext.SCOPE_REQUEST)
public static class RequestContext {
@Autowired HttpServletRequest request;
String getQualifierProperty() {
return request.getParameter("which");
}
}
}
I've put a working example using this code on Github. You can clone and run it with:
git clone git://github.com/zzantozz/testbed tmp
cd tmp/spring-mvc
mvn jetty:run
Then visit http://localhost:8080/dynamicallyInjected
to see the result of one dependency, and http://localhost:8080/dynamicallyInjected?which=bob
to see the other.
Upvotes: 3
Reputation: 3484
I do not think you can do this with annotation, the reason is you need a bean that is dynamic on runtime(maybe A service or B service), so @Autowire will be wired before the bean is being used in any place. One solution is get bean from context when you need.
@Component
public class ServiceThatNeedsServiceInterface {
ServiceInterface service;
public void useService() {
if(something is something){
service = applicationContext.getBean("Abean", ServiceInterface.class);
}else{
service = applicationContext.getBean("Bbean", ServiceInterface.class);
}
service.someMethod();
}
}
You can put is else logic somewhere in the class as a seperate function:
public void useService() {
service = findService();
service.someMethod();
}
public ServiceInterface findService() {
if(something is something){
return applicationContext.getBean("Abean", ServiceInterface.class);
}else{
return applicationContext.getBean("Bbean", ServiceInterface.class);
}
}
This is dynamic and this might be what you want.
Upvotes: 0
Reputation: 869
I guess, that you've missed the annotation, that tells spring, that you have a custom service. So your solution is to add this annotation before the class name:
@Service("ProductAService")
public class ProductAService implements ServiceInterface {
@Override public void someMethod() {
System.out.println("Hello, A Service");
}
}
@Service("ProductBService")
public class ProductBService implements ServiceInterface {
@Override public void someMethod() {
System.out.println("Hello, B Service");
}
}
And then you can auto wire it, but in order to use the specific service, you have to add the annotation Qualifier() like this:
@Autowired
@Qualifier("ProductBService") // or ProductAService
ServiceInterface service;
Or maybe you have to add just an annotation Qualifier("name of your bean") :)
Upvotes: 1