10

假设我有以下结构,其中有一个服务接口ServiceInterface和几个实现它的组件:我还有一个ProductAServicebean ,它有一个限定属性,表示我们说当前正在处理 ProductA 或 ProductB。然后如何使用自动装配或其他注释将正确的实现(ProductAService 或 ProductBService)自动注入到需要它的某些服务中(如下)。ProductBServiceRequestContextServiceThatNeedsServiceInterface

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();
}
4

6 回答 6

11

Spring Source在版本 1.1.4 中创建ServiceLocatorFactoryBean时指的是您的问题。为了使用它,您需要添加一个类似于下面的接口:

public interface ServiceLocator {
    //ServiceInterface service name is the one 
      //set by @Component
    public ServiceInterface lookup(String serviceName);
}

您需要将以下代码段添加到您的 applicationContext.xml

<bean id="serviceLocatorFactoryBean"
    class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean">
    <property name="serviceLocatorInterface"
              value="org.haim.springframwork.stackoverflow.ServiceLocator" />
</bean>

现在您的 ServiceThatNeedsServiceInterface 将类似于以下内容:

@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 将根据 RequestContext 限定符返回所需的服务。除了 spring 注释之外,您的代码不依赖于 Spring。我对上述内容执行了以下单元测试

@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();
    }

}

控制台会显示
Hello, A Service
Hello, B Service

一句警告。API 文档指出,
“这样的服务定位器……通常用于原型 bean,即用于每次调用都应该返回一个新实例的工厂方法……对于单例 bean,直接注入目标 bean 的 setter 或构造函数是更可取的。 ”</p>

我不明白为什么这可能会导致问题。在我的代码中,它在对 serviceThatNeedsServiceInterface.useService() 的两次序列调用中返回相同的服务;

您可以在GitHub中找到我的示例的源代码

于 2013-03-04T14:17:48.050 回答
3

我能想到做类似你正在寻找的事情的唯一方法是创建类似 FactoryBean 的东西,它根据 RequestContext 属性返回适当的实现。这是我拍打在一起的东西,具有您想要的行为:

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");
        }
    }
}

在 Github 上使用此代码放置了一个工作示例。您可以使用以下命令克隆并运行它:

git clone git://github.com/zzantozz/testbed tmp
cd tmp/spring-mvc
mvn jetty:run

然后访问http://localhost:8080/dynamicallyInjected查看一个依赖项的结果,并http://localhost:8080/dynamicallyInjected?which=bob查看另一个。

于 2013-03-02T05:32:36.830 回答
1

我想,你错过了告诉 spring 的注释,你有一个自定义服务。所以你的解决方案是在类名之前添加这个注释:

@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"); 
  }
}

然后你可以自动连接它,但是为了使用特定的服务,你必须像这样添加注释 Qualifier() :

  @Autowired
  @Qualifier("ProductBService") // or ProductAService
  ServiceInterface service;

或者您可能只需要添加一个注释 Qualifier("name of your bean") :)

于 2013-02-21T01:11:37.127 回答
1

这可能会帮助您:

采用

AutowireCapeableBeanFactory.autowireBean(Object existingBean)  

或者

AutowireCapeableBeanFactory.autowireBeanProperties(Object existingBean, int autowireMode, boolean dependencyCheck)

AutowireCapeableBeanFactory.autowireBean(Object existingBean) AutowireCapeableBeanFactory.autowireBeanProperties(Object existingBean, int autowireMode, boolean dependencyCheck)

于 2013-03-02T10:07:10.983 回答
0

我不认为你可以用注释来做到这一点,原因是你需要一个在运行时动态的 bean(可能是 A 服务或 B 服务),所以 @Autowire 将在 bean 在任何地方使用之前连接。一种解决方案是在需要时从上下文中获取 bean。

    @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();
  }
}

您可以将其他逻辑作为单独的函数放在类中的某个位置:

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

      }

这是动态的,这可能是您想要的。

于 2013-03-01T00:28:30.757 回答
0

您可以将 @Qualifier 注释与别名结合使用。在此处查看如何使用它基于属性加载 bean 的示例。您可以修改此方法并更改请求上下文中的属性/别名...

于 2013-03-02T11:59:31.430 回答