J'ai donc un certain nombre de génériques au printemps 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 plusieurs implémentations des génériques, le câblage automatique génère une erreur concernant plusieurs définitions de beans correspondantes. Je suppose que c'est parce que @Autowired
traite avant l'effacement du type. Chaque solution que j'ai trouvée ou que j'ai trouvée me semble laide ou refuse inexplicablement de travailler. Quel est le meilleur moyen de contourner ce problème?
Pourquoi ne pas ajouter un constructeur à la GenericService
et déplacer le câblage automatique vers la classe extensible, 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:
À partir de Spring 4.0 RC1 , il est possible d’autowire sur la base d’un type générique, ce qui signifie que vous pouvez écrivez un service générique comme
class GenericService<T, T_DAO extends GenericDao<T>> {
@Autowired
private T_DAO tDao;
}
et créer plusieurs haricots de printemps différents comme:
@Service
class FooService extends GenericService<Foo, FooDao> {
}
Vous pouvez supprimer l'annotation @autowire et effectuer un «autowire» différé à l'aide de @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 de code suivant à votre applicationContext.xml.
<bean id="daoLocatorFactoryBean"
class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean">
<property name="serviceLocatorInterface"
value="org.haim.springframwork.stackoverflow.DaoLocator" />
</bean>
C'est une astuce intéressante qui vous épargnera de petites classes standard.
B.T.W Je ne vois pas ce code passe-partout comme un gros problème et le projet pour lequel je travaille utilise la même approche.
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 référentiel générique avec un argument constructeur, vous pouvez également créer chaque méthode générique à la place et ne pas avoir d’argument constructeur. Mais chaque appel de méthode nécessiterait la classe en tant que 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 de bean pour le référentiel générique - vous pouvez avoir plusieurs beans différents, en utilisant différents arguments de convertisseur.
<bean id="tagRepository" class="com.yourcompnay.data.DomainRepository">
<constructor-arg value="com.yourcompnay.domain.Tag"/>
</bean>
Depdncy injection de haricot en utilisant une annotation de ressource
@Resource(name = "tagRepository")
private DomainRepository<Tag> tagRepository;
Et cela permet à Domainreposiroty d’être sous-classé pour des entités/méthodes spécifiques, ce qui permet un autowiring:
public class PersonRepository extends DomainRepository<Person> {
public PersonRepository(){
super(Person.class);
}
...
Voici une solution la plus proche. Les DAO spécialisés sont annotés au niveau de la couche de gestion. Comme dans la question de OP, le mieux serait de faire figurer un DAO annoté dans le modèle générique EntityDAO lui-même. L'effacement des types ne semble pas permettre que les informations de types spécialisés soient transmises aux usines de printemps (ce qui entraîne la création de rapports sur les beans correspondants de toutes les DAO spécialisées).
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);
}
}
public abstract class EntityBusinessService<T>
implements Serializable
{
public abstract EntityDAO<T> getDAO();
//Rest of code.
}
@Transactional
@Repository
public class UserDAO
extends EntityDAO<User>
{
}
@Transactional
@Service
@Scope("prototype")
public class UserBusinessService
extends EntityBusinessService<User>
{
@Autowired
UserDAO dao;
@Override
public EntityDAO<User> getDAO()
{
return dao;
}
//Rest of code
}
Pour cette question, il faut comprendre ce qu'est autowire. En termes courants, nous pouvons dire que via autowire, nous créons une instance d'objet/un haricot au moment du déploiement de l'application Web. Alors maintenant, allez à la question si vous déclarez l'auto-câblage dans plusieurs endroits avec le même nom. Alors cette erreur vient. Le câblage automatique peut être effectué de différentes manières. Par conséquent, si vous utilisez plusieurs types de technique de transfert automatique, vous risquez également d'obtenir cette erreur.
Vous devez utiliser le câblage automatique dans les classes qui développent ces génériques.
Solution générique complète using Spring 4 :
@Component
class Foo{
}
@Component
class Bar{
}
interface GenericDao<T>{
//list of methods
}
class GenericDaoImpl<T> implements GenericDao<T>{
@Autowired
SessionFactory factory;
private Class<T> domainClass; // Get Class Type of <T>
public Session getCurrentSession(){
return factory.getCurrentSession();
}
public DaoImpl() {
this.domainClass = (Class<T>) GenericTypeResolver.resolveTypeArgument(getClass(), DaoImpl.class);
}
//implementation of methods
}
interface FooDao extends GenericDao<Foo>{
//Define extra methods if required
}
interface BarDao extends GenericDao<Bar>{
//Define extra methods if required
}
@Repository
class FooDao extends GenericDaoImpl<Foo> implements FooDao{
//implementation of extra methods
}
@Repository
class BarDao extends GenericDaoImpl<Bar> implements BarDao{
//implementation of extra methods
}
interface GenericService<T>{
//List of methods
}
class GenericServiceImpl<T> implements GenericService<T>{
@Autowire
protected GenericDao<T> dao; //used to access DAO layer
}
class FooService extends GenericService<Foo>{
//Add extra methods of required
}
class BarService extends GenericService<Bar>{
//Add extra methods of required
}
@Service
class FooServiceImpl extends GenericServiceImpl<Foo> implements GenericService<Foo>{
//implementation of extra methods
}
@Service
class BarServiceImpl extends GenericServiceImpl<Bar> implements GenericService<Bar>{
//implementation of extra methods
}