Пользовательский кандидат Autwire beans в Spring 3

Скажем, у меня есть следующая структура с сервисным интерфейсом ServiceInterface и несколькими компонентами, реализующими ее: ProductAService и ProductBService У меня также есть RequestContext bean, у которого есть свойство qualifying, которое говорит, что мы в настоящее время обрабатывают 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();
}

Ответ 1

Spring Источник ссылался на вашу проблему, когда они создали ServiceLocatorFactoryBean в версии 1.1.4. Чтобы использовать его, вам необходимо добавить интерфейс, аналогичный приведенному ниже:

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. Я выполнил следующий unit test для вышеуказанного

@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 указано, что "Такие локаторы сервисов... обычно будут использоваться для прототипа beans, то есть для factory методов, которые должны возвращать новый экземпляр для каждого вызова... Для singleton beans, прямой установщик или инсталляция конструктора целевого bean является предпочтительным".

Я не понимаю, почему это может вызвать проблему. В моем коде он возвращает одну и ту же услугу на двух последовательных вызовах serviceThatNeedsServiceInterface.useService();

Вы можете найти исходный код для моего примера в GitHub

Ответ 2

Единственный способ, с помощью которого я могу сделать что-то вроде того, что вы ищете, - создать нечто вроде 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, чтобы увидеть другую.

Ответ 3

Я предполагаю, что вы пропустили аннотацию, которая сообщает 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"); 
  }
}

И затем вы можете автоматически подключить его, но для того, чтобы использовать конкретную услугу, вы должны добавить классификатор аннотаций() следующим образом:

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

Или, может быть, вам нужно добавить только классификатор аннотаций ( "имя вашего bean" ):)

Ответ 5

Я не думаю, что вы можете сделать это с помощью аннотации, причина в том, что вам нужен bean, который является динамическим во время выполнения (возможно, служба или служба 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();
  }
}

Вы можете поместить логику else иначе в класс как отдельную функцию:

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

      }

Это динамично, и это может быть то, что вы хотите.

Ответ 6

Вы можете использовать аннотацию @Qualifier в сочетании с псевдонимами. См. Пример того, как он используется для загрузки bean на основе свойства здесь. Вы можете изменить этот подход и изменить свойство/псевдоним в requestcontext...