Ligação automática de dois beans implementando a mesma interface - como definir o bean padrão para ligação automática?

138

Fundo:

Eu tenho um aplicativo Spring 2.5 / Java / Tomcat. Existe o seguinte bean, que é usado em todo o aplicativo em muitos lugares

public class HibernateDeviceDao implements DeviceDao

e o seguinte bean que é novo:

public class JdbcDeviceDao implements DeviceDao

O primeiro bean está configurado para isso (todos os beans do pacote estão incluídos)

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

O segundo (novo) bean é configurado separadamente

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

Isso resulta (é claro) em uma exceção ao iniciar o servidor:

a exceção aninhada é org.springframework.beans.factory.NoSuchBeanDefinitionException: nenhum bean exclusivo do tipo [com.sevenp.mobile.samplemgmt.service.dao.DeviceDao] é definido: bean correspondente único esperado, mas localizado 2: [deviceDao, jdbcDeviceDao]

de uma classe tentando autowire o bean como este

@Autowired
private DeviceDao hibernateDevicDao;

porque existem dois beans implementando a mesma interface.

A questão:

É possível configurar os beans para que

1. Não preciso fazer alterações nas classes existentes, que já possuem o HibernateDeviceDaocabeamento automático

2. ainda sendo capaz de usar o segundo (novo) bean como este:

@Autowired
@Qualifier("jdbcDeviceDao")

Ou seja, eu precisaria de uma maneira de configurar o HibernateDeviceDaobean como o padrão a ser conectado automaticamente, permitindo simultaneamente o uso de um JdbcDeviceDaoquando especificando explicitamente isso com a @Qualifieranotação.

O que eu já tentei:

Eu tentei definir a propriedade

autowire-candidate="false"

na configuração do bean para JdbcDeviceDao:

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

porque a documentação do Spring diz que

Indica se esse bean deve ou não ser considerado ao procurar candidatos correspondentes para satisfazer os requisitos de conexão automática de outro bean. Observe que isso não afeta as referências explícitas por nome, que serão resolvidas mesmo que o bean especificado não esteja marcado como candidato a fio automático. *

o que eu interpretei como significando que eu ainda podia fazer a ligação automática JdbcDeviceDaousando a @Qualifieranotação e ter o HibernateDeviceDaobean como padrão. Aparentemente, minha interpretação não estava correta, pois isso resulta na seguinte mensagem de erro ao iniciar o servidor:

Dependência não satisfeita do tipo [classe com.sevenp.mobile.samplemgmt.service.dao.jdbc.JdbcDeviceDao]: esperado pelo menos 1 bean correspondente

vindo da classe em que tentei fazer a fiação automática do bean com um qualificador:

@Autowired
@Qualifier("jdbcDeviceDao")

Solução:

A sugestão de skaffman de tentar a anotação @Resource funcionou. Portanto, a configuração tem candidato a fio automático definido como false para jdbcDeviceDao e, ao usar o jdbcDeviceDao, refiro-me a ela usando a anotação @Resource (em vez de @Qualifier):

@Resource(name = "jdbcDeviceDao")
private JdbcDeviceListItemDao jdbcDeviceDao;
simon
fonte
Se eu usar essa interface em 100 locais no código e quiser mudar tudo para outra implementação, não quero alterar anotações de qualificador ou recurso em todos os lugares. E também não quero alterar o código de nenhuma das implementações. Por que não há possibilidades explícitas de ligação como no Guice?
Daniel Hári 14/03

Respostas:

134

Eu sugiro marcação da classe Hibernate DAO com @Primary, ou seja, (supondo que você usou @Repositoryem HibernateDeviceDao):

@Primary
@Repository
public class HibernateDeviceDao implements DeviceDao

Dessa forma, ele será selecionado como o candidato a arame automático padrão, sem necessidade autowire-candidatedo outro bean.

Além disso, em vez de usar @Autowired @Qualifier, acho mais elegante usar @Resourcepara escolher feijões específicos, ou seja,

@Resource(name="jdbcDeviceDao")
DeviceDao deviceDao;
skaffman
fonte
Esqueci de mencionar na pergunta que estou usando o Spring 2.5 (agora editei a pergunta), portanto, o @Primary não é uma opção.
10242 Simon
1
@ Simon: Sim, isso foi bastante importante. Tente a @Resourceanotação, como também sugeri.
Skaffman
1
Obrigado, a anotação de recurso resolveu o problema - agora a propriedade candidato a fio automático funciona como eu esperava.
10242 Simon
Obrigado! Qual é a diferença entre especificar o nome do bean via @Resourcee @Qualifier, além do fato de que o primeiro é relativamente mais novo que o último?
ASGs
1
@asgs O uso da anotação de recursos simplifica as coisas. Em vez de ter o combo com fio / qualificador automático, você pode marcá-lo para injeção de dependência e especificar o nome em uma linha. Observe que a solução do simon é redundante, a anotação com fio automático pode ser removida.
The Gilbert Arenas Dagger
37

Que tal @Primary?

Indica que um bean deve ter preferência quando vários candidatos estiverem qualificados para autorizar automaticamente uma dependência de valor único. Se existir exatamente um bean 'primário' entre os candidatos, será o valor conectado automaticamente. Esta anotação é semanticamente equivalente ao atributo <bean>do elemento primaryno Spring XML.

@Primary
public class HibernateDeviceDao implements DeviceDao

Ou se você deseja que sua versão do Jdbc seja usada por padrão:

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

@Primary também é excelente para testes de integração quando você pode substituir facilmente o bean de produção pela versão stubbed anotando-o.

Tomasz Nurkiewicz
fonte
Esqueci de mencionar na pergunta que estou usando o Spring 2.5 (agora editei a pergunta), portanto, o @Primary não é uma opção.
10242 Simon
1
@ Simon: Eu acredito que o primary=""atributo estava disponível anteriormente. Apenas declare HibernateDeviceDaoem XML e exclua-o da varredura de componente / anotação.
Tomasz Nurkiewicz
1
De acordo com a documentação, está disponível desde o 3.0: static.springsource.org/spring/docs/3.1.x/javadoc-api/org/… Boa dica de qualquer maneira, lembrarei da anotação Primária do próximo projeto quando puder usar o Spring 3.x
simon
8

Para a Primavera 2.5, não há @Primary. A única maneira é usar @Qualifier.

blang
fonte
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
fonte
0

O motivo pelo qual @Resource (name = "{nome da sua classe filha}") funciona, mas @Autowired às vezes não funciona, é devido à diferença de sua sequência de correspondência

Sequência de correspondência de @Autowire
Type, Qualifier, Name

Sequência correspondente de @Resource
Name, Type, Qualifier

A explicação mais detalhada pode ser encontrada aqui:
Injetar e Recurso e anotações com conexão automática

Nesse caso, uma classe filha diferente herdada da classe ou interface pai confunde @Autowire, porque são do mesmo tipo; Como o @Resource usa Name como primeira prioridade correspondente, ele funciona.

RAIO
fonte