【问题标题】:Custom Autowire candidate beans in Spring 3Spring 3 中的自定义 Autowire 候选 bean
【发布时间】:2013-02-06 05:14:27
【问题描述】:

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

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

【问题讨论】:

    标签: java spring dependency-injection annotations


    【解决方案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") :)

    【讨论】:

    • 我知道我可以做到这一点,但我必须将所有不同的服务作为使用它的类中的单独字段。我希望 RequestContext 中的属性成为自动装配时注入“产品”实现的决策因素。
    【解决方案2】:

    我不认为你可以用注解来做到这一点,原因是你需要一个在运行时动态的 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);
             }
    
          }
    

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

    【讨论】:

      【解决方案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");
              }
          }
      }
      

      我已经使用此代码on 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查看另一个。

      【讨论】:

        【解决方案4】:

        这可能对你有帮助:

        使用

        AutowireCapeableBeanFactory.autowireBean(Object existingBean)  
        

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

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

        【讨论】:

          【解决方案5】:

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

          【讨论】:

            【解决方案6】:

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

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

            您需要将以下 sn-p 添加到您的 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();
                }
            
            }
            

            控制台将显示
            你好,服务
            你好,B服务

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

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

            你可以在GitHub找到我的例子的源代码

            【讨论】:

            • 宾果游戏!这是正确的答案。我不介意那一点点 XML 配置。
            猜你喜欢
            • 2019-12-18
            • 2012-05-26
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多