27 votes

Spring 3.2 Types génériques Autowire

J'ai donc un certain nombre de génériques dans Spring 3.2 et, idéalement, mon architecture devrait ressembler à ceci.

class GenericDao<T>{}

class GenericService<T, T_DAO extends GenericDao<T>>
{
    // FAILS
    @Autowired
    T_DAO;
}

@Component
class Foo{}

@Repository
class FooDao extends GenericDao<Foo>{}

@Service
FooService extends GenericService<Foo, FooDao>{}

Malheureusement, avec de multiples implémentations des génériques, le câblage automatique jette une erreur à propos de multiples définitions de haricots correspondants. Je suppose que c'est parce que @Autowired avant l'effacement des caractères. Toutes les solutions que j'ai trouvées ou imaginées me semblent laides ou refusent inexplicablement de fonctionner. Quelle est la meilleure façon de contourner ce problème ?

22voto

matsev Points 6761

Que diriez-vous d'ajouter un constructeur à la GenericService et déplacez le câblage automatique vers la classe d'extension, par ex.

class GenericService<T, T_DAO extends GenericDao<T>> {
    private final T_DAO tDao;

    GenericService(T_DAO tDao) {
        this.tDao = tDao;
    }
}

@Service
FooService extends GenericService<Foo, FooDao> {

    @Autowired
    FooService(FooDao fooDao) {
        super(fooDao);
    }
}

Mise à jour :

A partir de Spring 4.0 RC1 il est possible de réaliser un câblage automatique basé sur le type générique, ce qui signifie que vous pouvez puede écrire un service générique comme

class GenericService<T, T_DAO extends GenericDao<T>> {

    @Autowired
    private T_DAO tDao;
}

et créer de multiples beans Spring différents comme :

@Service
class FooService extends GenericService<Foo, FooDao> {
}

4voto

Haim Points 1483

Vous pouvez supprimer l'annotation @autowire et effectuer un "autowire" différé en utilisant @PostConstruct et ServiceLocatorFactoryBean.
Votre GenericService ressemblera à ceci

    public class GenericService<T, T_DAO extends GenericDao<T>>{

        @Autowired
        private DaoLocator daoLocatorFactoryBean;

        //No need to autowried, autowireDao() will do this for you 
        T_DAO dao;

        @SuppressWarnings("unchecked")
        @PostConstruct
        protected void autowireDao(){
        //Read the actual class at run time
        final Type type; 
        type = ((ParameterizedType) getClass().getGenericSuperclass())
                                              .getActualTypeArguments()[1]; 
        //figure out the class of the fully qualified class name
        //this way you can know the bean name to look for
        final String typeClass = type.toString();      
        String daoName = typeClass.substring(typeClass.lastIndexOf('.')+1
                                            ,typeClass.length());
        daoName = Character.toLowerCase(daoName.charAt(0)) + daoName.substring(1);
        this.dao = (T_DAO) daoLocatorFactoryBean.lookup(daoName);
       }

daoLocatorFactoryBean fait la magie pour vous.
Pour l'utiliser, vous devez ajouter une interface similaire à celle ci-dessous :

 public interface DaoLocator {
        public GenericDao<?> lookup(String serviceName);           
 }    

Vous devez ajouter l'extrait suivant à votre applicationContext.xml

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

Il s'agit d'une astuce intéressante qui vous permettra d'économiser de petites classes passe-partout.
B.T.W. Je ne considère pas ce code passe-partout comme un gros problème et le projet pour lequel je travaille utilise l'approche matsev.

3voto

maggu Points 584

Voici la solution la plus proche. Les DAO spécialisés sont annotés au niveau de la couche métier. Comme dans la question de l'OP, le meilleur effort serait d'avoir un DAO annoté dans le modèle générique EntityDAO lui-même. L'effacement des types semble ne pas permettre aux informations sur les types spécialisés d'être transmises aux fabriques de printemps [ce qui a pour résultat de rapporter les beans correspondants de tous les DAO spécialisés].

Le modèle DAO d'entité générique

public class EntityDAO<T> 
{
    @Autowired
    SessionFactory factory;

    public Session getCurrentSession()
    {
        return factory.getCurrentSession();
    }

    public void create(T record)
    {
        getCurrentSession().save(record);
    }

    public void update(T record)
    {
        getCurrentSession().update(record);
    }

    public void delete(T record)
    {
        getCurrentSession().delete(record);
    }

    public void persist(T record)
    {
        getCurrentSession().saveOrUpdate(record);
    }

    public T get(Class<T> clazz, Integer id)
    {
        return (T) getCurrentSession().get(clazz, id);
    }
}

Le modèle générique de couche métier basé sur les entités

public abstract class EntityBusinessService<T>
implements Serializable
{
    public abstract EntityDAO<T> getDAO();

    //Rest of code.
}

Un exemple de DAO d'entité spécialisée

@Transactional
@Repository
public class UserDAO
extends EntityDAO<User>
{
}

Un exemple de classe d'affaires d'entité spécialisée

@Transactional
@Service
@Scope("prototype")
public class UserBusinessService
extends EntityBusinessService<User>
{
    @Autowired
    UserDAO dao;

    @Override
    public EntityDAO<User> getDAO() 
    {
        return dao;
    }

    //Rest of code
}

3voto

NimChimpsky Points 20263

Pourquoi voulez-vous un service générique ? Les classes de service sont destinées à des unités de travail spécifiques impliquant plusieurs entités. Vous pouvez simplement injecter un référentiel directement dans un contrôleur.

Voici un exemple de dépôt générique avec un argument de constructeur, vous pourriez également rendre chaque méthode générique à la place et ne pas avoir d'argument de constructeur. Mais chaque appel de méthode nécessiterait la classe comme paramètre :

public class DomainRepository<T> {

   @Resource(name = "sessionFactory")
   protected SessionFactory sessionFactory;

   public DomainRepository(Class genericType) {
        this.genericType = genericType;
   }

   @Transactional(readOnly = true)
   public T get(final long id) {
       return (T) sessionFactory.getCurrentSession().get(genericType, id);
   }

Exemple de définition d'un bean pour le référentiel générique - vous pouvez avoir plusieurs beans différents, utilisant des arcs de contstructeur différents.

<bean id="tagRepository" class="com.yourcompnay.data.DomainRepository">
        <constructor-arg value="com.yourcompnay.domain.Tag"/>
</bean>

Injection d'un bean à l'aide d'une annotation de ressource.

@Resource(name = "tagRepository")
private DomainRepository<Tag> tagRepository;

Et cela permet de sous-classer le répertoire de domaine pour des entités/méthodes spécifiques, ce qui permettrait le câblage automatique :

public class PersonRepository extends DomainRepository<Person> {
    public PersonRepository(){
        super(Person.class);
    }
    ...

1voto

Abhishek Points 50

Pour répondre à cette question, il faut comprendre ce qu'est le câblage automatique. En termes courants, nous pouvons dire que grâce à autowire, nous créons une instance d'objet/bean au moment du déploiement de l'application Web. Maintenant, si vous déclarez un autowire à plusieurs endroits avec le même nom. Alors cette erreur apparaît. L'autowiring peut être fait de plusieurs façons, donc si vous utilisez plusieurs types de techniques d'autowiring, alors vous pouvez également obtenir cette erreur.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X