Autowiring dwóch beanów z tym samym interfejsem - jak ustawić domyślny bean na autowire?

138

Tło:

Mam aplikację Spring 2.5 / Java / Tomcat. Oto następująca fasola, która jest używana w wielu miejscach w aplikacji

public class HibernateDeviceDao implements DeviceDao

oraz następujące ziarna, które są nowe:

public class JdbcDeviceDao implements DeviceDao

Pierwsza fasola jest skonfigurowana w ten sposób (wszystkie ziarna w pakiecie są uwzględnione)

<context:component-scan base-package="com.initech.service.dao.hibernate" />

Drugie (nowe) ziarno jest konfigurowane osobno

<bean id="jdbcDeviceDao" class="com.initech.service.dao.jdbc.JdbcDeviceDao">
    <property name="dataSource" ref="jdbcDataSource">
</bean>

Powoduje to (oczywiście) wyjątek podczas uruchamiania serwera:

Wyjątek zagnieżdżony to org.springframework.beans.factory.NoSuchBeanDefinitionException: Nie zdefiniowano żadnego unikalnego komponentu bean typu [com.sevenp.mobile.samplemgmt.service.dao.DeviceENAULT]: spodziewano się pojedynczego pasującego komponentu bean, ale znaleziono 2: [device replace, jdbcDeviceBIZ]

z klasy próbującej autopowielać fasolę w ten sposób

@Autowired
private DeviceDao hibernateDevicDao;

ponieważ istnieją dwie fasole implementujące ten sam interfejs.

Pytanie:

Czy można tak skonfigurować ziarna?

1. Nie muszę wprowadzać zmian w istniejących klasach, które mają już HibernateDeviceDaoautowired

2. nadal można używać drugiej (nowej) fasoli w następujący sposób:

@Autowired
@Qualifier("jdbcDeviceDao")

Tzn. HibernateDeviceDaoPotrzebowałbym sposobu na skonfigurowanie komponentu bean jako domyślnego komponentu bean do autowiredowania, jednocześnie umożliwiając użycie a, JdbcDeviceDaogdy wyraźnie to określono w @Qualifieradnotacji.

Czego już próbowałem:

Próbowałem ustawić nieruchomość

autowire-candidate="false"

w konfiguracji bean dla JdbcDevice_action:

<bean id="jdbcDeviceDao" class="com.initech.service.dao.jdbc.JdbcDeviceDao" autowire-candidate="false">
    <property name="dataSource" ref="jdbcDataSource"/>
</bean>

ponieważ dokumentacja Spring tak mówi

Wskazuje, czy to ziarno powinno być brane pod uwagę podczas wyszukiwania pasujących kandydatów, aby spełnić wymagania dotyczące automatycznego podłączenia innego ziarna. Należy pamiętać, że nie ma to wpływu na jawne odwołania według nazwy, które zostaną rozwiązane, nawet jeśli określony komponent bean nie zostanie oznaczony jako kandydat na autowire. *

co zinterpretowałem jako oznaczające, że nadal mogę autopowire JdbcDeviceDaoprzy użyciu @Qualifieradnotacji i mieć HibernateDeviceDaojako domyślną fasolę. Najwyraźniej moja interpretacja nie była jednak poprawna, ponieważ powoduje to następujący komunikat o błędzie podczas uruchamiania serwera:

Niezadowalająca zależność typu [klasa com.sevenp.mobile.samplemgmt.service.dao.jdbc.JdbcDeviceENAULT]: oczekiwano co najmniej 1 pasującego komponentu bean

pochodzący z klasy, w której próbowałem automatycznie podłączyć fasolę z kwalifikatorem:

@Autowired
@Qualifier("jdbcDeviceDao")

Rozwiązanie:

Sugestia skaffmana, aby wypróbować adnotację @Resource, zadziałała. W związku z tym konfiguracja ma ustawioną wartość „false” dla opcji autowire-kandydat dla jdbcDevice_action i podczas korzystania z jdbcDevice zamiar odwołuję się do niej za pomocą adnotacji @Resource (zamiast @Qualifier):

@Resource(name = "jdbcDeviceDao")
private JdbcDeviceListItemDao jdbcDeviceDao;
Szymon
źródło
Jeśli używam tego interfejsu w 100 miejscach w kodzie i chcę przełączyć wszystko na inną implementację, nie chcę zmieniać kwalifikatora ani adnotacji zasobów we wszystkich miejscach. Nie chcę też zmieniać kodu żadnej z implementacji. Dlaczego nie ma jednoznacznych możliwości wiązania, jak w Guice?
Daniel Hári,

Odpowiedzi:

134

Sugeruję oznaczenie klasy DAO z Hibernate @Primary, czyli (zakładając, że stosowane @Repositoryna HibernateDeviceDao):

@Primary
@Repository
public class HibernateDeviceDao implements DeviceDao

W ten sposób zostanie wybrany jako domyślny kandydat na autoprzewód, bez potrzeby autowire-candidatena drugim ziarnie .

Poza tym, zamiast używać @Autowired @Qualifier, uważam, że bardziej eleganckie jest używanie go @Resourcedo zbierania określonych ziaren, tj

@Resource(name="jdbcDeviceDao")
DeviceDao deviceDao;
skaffman
źródło
Zapomniałem wspomnieć w pytaniu, że używam Springa 2.5 (teraz zredagowałem pytanie), więc @Primary nie wchodzi w grę.
simon
1
@simon: Tak, to było dość ważne. Wypróbuj @Resourceadnotację, jak również zasugerowałem.
skaffman
1
Dzięki, adnotacja zasobu rozwiązała problem - teraz właściwość autowire-kandydat działa zgodnie z oczekiwaniami.
simon
Dzięki! Jaka jest różnica między określeniem nazwy ziarna przez @Resourcea @Qualifier, poza tym, że ta pierwsza jest stosunkowo nowsza od drugiej?
asgs
1
@asgs Korzystanie z adnotacji zasobów upraszcza rzeczy. Zamiast mieć kombinację autowired / kwalifikator, możesz oznaczyć ją do wstrzyknięcia zależności i określić nazwę w jednej linii. Zauważ, że rozwiązanie Simona jest zbędne, można usunąć automatyczne przypisanie.
The Gilbert Arenas Dagger
37

O co chodzi @Primary?

Wskazuje, że ziarno powinno mieć pierwszeństwo, gdy wielu kandydatów jest uprawnionych do automatycznego połączenia zależności jednowartościowej. Jeśli wśród kandydatów istnieje dokładnie jedna fasola „podstawowa”, będzie to wartość autowired. Ta adnotacja jest semantycznie równoważna <bean>z primaryatrybutem elementu w Spring XML.

@Primary
public class HibernateDeviceDao implements DeviceDao

Lub jeśli chcesz, aby Twoja wersja Jdbc była używana domyślnie:

<bean id="jdbcDeviceDao" primary="true" class="com.initech.service.dao.jdbc.JdbcDeviceDao">

@Primary jest również świetny do testowania integracji, kiedy można łatwo zastąpić komponent bean produkcyjną wersją, dodając do niej adnotacje.

Tomasz Nurkiewicz
źródło
Zapomniałem wspomnieć w pytaniu, że używam Springa 2.5 (teraz zredagowałem pytanie), więc @Primary nie wchodzi w grę.
simon
1
@simon: Myślę, że primary=""atrybut był dostępny wcześniej. Wystarczy zadeklarować HibernateDeviceDaow formacie XML i wykluczyć go ze skanowania komponentów / adnotacji.
Tomasz Nurkiewicz
1
Zgodnie z dokumentacją, która jest dostępna od 3.0: static.springsource.org/spring/docs/3.1.x/javadoc-api/org/ ... Dobra wskazówka i tak zapamiętam adnotację Primary dla następnego projektu, kiedy będę mógł używać Spring 3.x
simon
8

W przypadku wersji Spring 2.5 nie ma @Primary. Jedynym sposobem jest użycie @Qualifier.

blang
źródło
2
The use of @Qualifier will solve the issue.
Explained as below example : 
public interface PersonType {} // MasterInterface

@Component(value="1.2") 
public class Person implements  PersonType { //Bean implementing the interface
@Qualifier("1.2")
    public void setPerson(PersonType person) {
        this.person = person;
    }
}

@Component(value="1.5")
public class NewPerson implements  PersonType { 
@Qualifier("1.5")
    public void setNewPerson(PersonType newPerson) {
        this.newPerson = newPerson;
    }
}

Now get the application context object in any component class :

Object obj= BeanFactoryAnnotationUtils.qualifiedBeanOfType((ctx).getAutowireCapableBeanFactory(), PersonType.class, type);//type is the qualifier id

you can the object of class of which qualifier id is passed.
Sandeep Jain
źródło
0

Powodem, dla którego @Resource (name = „{nazwa Twojej klasy podrzędnej}”) działa, ale @Autowired czasami nie działa, jest różnica w ich kolejności dopasowywania

Pasująca sekwencja @Autowire
Type, Qualifier, Name

Pasująca sekwencja @Resource
Name, Type, Qualifier

Bardziej szczegółowe wyjaśnienie można znaleźć tutaj:
Inject and Resource and Autowired adnotations

W tym przypadku różne klasy potomne dziedziczone z klasy nadrzędnej lub interfejsu dezorientują @Autowire, ponieważ są tego samego typu; Ponieważ @Resource używa nazwy jako pierwszego pasującego priorytetu, działa.

PROMIEŃ
źródło