Announcement Announcement Module
Collapse
No announcement yet.
Autowiring and Generics Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Autowiring and Generics

    Hey everyone. I'm attempting make my DAO, Service and Controller classes all generic, as our data modal has about 15 entities and most of our transactions simply use the standard find, list, add, update, remove. I've been working on this for some time now and I'm stuck on one particular error. Before I give up I thought I'd post here.

    Spring is failing to create the controller beans, because it cannot instantiate the related service beans, and this in turn is due to an IllegalArgumentException: argument type mismatch.

    The abstract service class (EntityService) expects an IGenericDao<E, K> instance as a parameter to its constructor, and each service that extends EntityService passes in a specific DAO (e.g., ProjectDao). These specific DAOs extend the abstract class GenericDao, which implements IGenericDao.

    I cannot understand why Spring has an issue with this, and especially why, at the same time, Spring seems OK passing a specific implementation of IEntityService into each controller, which to me seems to be the same process.

    Is it possible for me to achieve this, and if so, what am I doing wrong?

    My architecture looks like this (stripped down as much as possible):

    Controllers:
    Code:
    public interface IGenericController<E, K> {
    
        HttpEntity<String> post(String entityJson);
    
        HttpEntity<String> delete(K key);
    }
    Code:
    public abstract class GenericController<E, K extends Serializable> implements IGenericController<E, K> {
        
        protected Gson gson;
        protected IEntityService<E, K> service;
    
        public GenericController(IEntityService<E, K> service, GsonBuilder gsonBuilder) {
            this.service = service;
            this.gson = gsonBuilder.create();
        }
    
        @RequestMapping(method = RequestMethod.POST)
        public final HttpEntity<String> post(@RequestBody final String entityJson) {
            E entity = gson.fromJson(entityJson, getEntityType());
            service.add(entity);
            return JsonEntity.create(gson.toJson(entity));
        }
    
        @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
        public final HttpEntity<String> delete(@PathVariable("id") final K id) {
            service.remove(id);
            HttpEntity<String> response = new ResponseEntity<String>(HttpStatus.OK);
            return response;
        }
        
        public abstract Class<E> getEntityType();
    }
    Code:
    @Controller
    @RequestMapping("/projects/")
    public class ProjectController extends GenericController<Project, Integer> {
    
        @Autowired
        public ProjectController(ProjectService service, GsonBuilder gsonBuilder) {
            super(service, gsonBuilder);
        }
    
        @Override
        public Class<Project> getEntityType() {
            return Project.class;
        }
    }

    Services:
    Code:
    public interface IEntityService<E, K> {
    
        void add(E entity);
    
        void remove(K key);
    }
    Code:
    @Transactional
    public abstract class EntityService<E, K extends Serializable> implements IEntityService<E, K> {
    
        protected IGenericDao<E, K> dao;
    
        public EntityService(IGenericDao<E, K> dao) {
            this.dao = dao;
        }
    
        public void add(E entity) {
           dao.add(entity);
        }
    
        public void remove(K key) {
            dao.remove(key);
        }
    }
    Code:
    @Service
    public class ProjectService extends EntityService<Project, Integer> {
    
        @Autowired
        public ProjectService(ProjectDao dao) {
            super(dao);
        }
    }
    DAOs:
    Code:
    public interface IGenericDao<E, K> {
    
        void add(E entity);
    
        void remove(K key);
    }
    Code:
    public abstract class GenericDao<E, K extends Serializable> implements IGenericDao<E, K> {
    
        @PersistenceContext
        protected EntityManager em;
    
        public void add(E e) {
            em.persist(e);
            em.flush();
        }
    
        public void remove(K k) {
            E entity = find(k);
            em.remove(entity);
            em.flush();
        }
    
        protected abstract String getListQuery();
        protected abstract Class<E> getEntityType();
    }
    Code:
    @Repository
    public class ProjectDao extends GenericDao<Project, Integer> {
    
        private static final String ALL_PROJECTS = "SELECT p from Project p";
    
        @Override
        protected String getListQuery() {
            return ALL_PROJECTS;
        }
    
        @Override
        protected Class<Project> getEntityType() {
            return Project.class;
        }
    }

  • #2
    Just thought I'd add some detail to this, after a final push I'm still hitting a wall, but have managed to narrow down the cause slightly. It seems that implementing an interface on my EntityService causes the IllegalArgumentException, while not implementing an interface causes Spring to try to proxy using CGLIB, which requires an empty constructor and prevents the auto-wiring.

    I've previously encountered a similar issue with Spring, and I think it boils down to the use of @Transactional, but I'm not sure at all how to fix the issue while maintaining the generic approach. I know that CGLIB wouldn't auto-wire through setters or fields for me when I 'tried' to use it.

    Comment


    • #3
      Need more details like stacktrace, config, environment/app-server type & versions etc. As of now seems like you're in the BEST position to help yourself with as much stripping already been done and you choosing not to share it.

      Comment

      Working...
      X