2013-02-21 7 views
10

Dire che ho la seguente struttura con un'interfaccia di servizio ServiceInterface e un paio di componenti che la implementano: ProductAService e ProductBService Ho anche un bean RequestContext che ha una proprietà qualificante che dice che stiamo dicendo attualmente processando ProdottoA o ProdottoB. In che modo quindi è possibile iniettare automaticamente all'autowiring o altre annotazioni l'implementazione corretta (ProductAService o ProductBService) in alcuni servizi che ne hanno bisogno (ServiceThatNeedsServiceInterface di seguito).Fagioli candidati Autowire personalizzati nella primavera 3

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

risposta

10

Spring Source faceva riferimento al tuo problema quando hanno creato il ServiceLocatorFactoryBean nella versione 1.1.4. Per utilizzarlo è necessario aggiungere un'interfaccia simile a quella riportata di seguito:

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

è necessario aggiungere il seguente frammento al applicationContext.xml

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

Ora il vostro ServiceThatNeedsServiceInterface avrà un aspetto simile a quello qui sotto:

@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 tornerà il servizio desiderato in base alla qualificazione RequestContext. Oltre alle annotazioni primaverili, il tuo codice non dipende da Spring. Ho eseguito il seguente test unità per quanto sopra

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

} 

La console visualizzerà
Ciao, un servizio di
Ciao, B Servizio

Una parola di avvertimento. La documentazione dell'API afferma che
"Tali locatori di servizio ... saranno tipicamente usati per i bean prototipo, cioè per i metodi di produzione che dovrebbero restituire una nuova istanza per ogni chiamata ... Per i bean singleton, il setter diretto o l'iniezione del costruttore del bean di destinazione è preferibile. "

Non riesco a capire perché questo potrebbe causare un problema. Nel mio codice restituisce lo stesso servizio su due sequenze di chiamate a serviceThatNeedsServiceInterface.useService();

È possibile trovare il codice sorgente per il mio esempio in GitHub

+0

Bingo! Questa è la risposta corretta. Non mi interessa quel pezzettino di configurazione XML. – Strelok

1

Immagino che tu abbia perso l'annotazione, che dice a primavera, che hai un servizio personalizzato. Quindi la soluzione è quella di aggiungere questa annotazione prima che il nome della classe:

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

e poi si può legare auto, ma al fine di utilizzare il servizio specifico, è necessario aggiungere l'annotazione Qualifier() in questo modo:

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

O forse è necessario aggiungere solo un'annotazione Qualifier ("nome del fagiolo") :)

+0

So che posso fare questo, ma avrei dovuto avere tutti i diversi servizi come campi separati nella classe che lo utilizza. Voglio che la proprietà in RequestContext rappresenti il ​​fattore decisivo che l'implementazione del "prodotto" deve iniettare durante la fase di autowiring. – Strelok

0

non credo che si può fare questo con annotazione, il motivo è necessario un fagiolo che è dinamico su runtime (forse un servizio o un servizio B), quindi @Autowire w essere cablato prima che il bean venga utilizzato in qualsiasi posto. Una soluzione è ottenere il bean dal contesto quando è necessario.

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

Si può mettere una logica altra cosa da qualche parte nella classe come una funzione separata:

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

     } 

Questo è dinamico e questo potrebbe essere ciò che si desidera.

3

L'unico modo in cui posso pensare a qualcosa di simile a quello che stai cercando è creare qualcosa come un FactoryBean che restituisce l'implementazione appropriata in base alla proprietà RequestContext. Qui è qualcosa che ho schiaffeggiato insieme che è il comportamento desiderato:

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

ho messo un esempio di lavoro utilizzando questo codice on Github. È possibile clonare ed eseguirlo con:

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

Poi visita http://localhost:8080/dynamicallyInjected per vedere il risultato di una dipendenza, e http://localhost:8080/dynamicallyInjected?which=bob di vedere l'altro.

0

È possibile utilizzare l'annotazione @Qualifier in combinazione con gli alias. Guarda un esempio di come viene utilizzato per caricare un bean basato su una proprietà here. È possibile modificare questo approccio e modificare la proprietà/alias nel requestcontext ...