Search code examples
javagenericscdi

Inject InjectionPoint in Generic Class to get the actual class type?


in my JSF 2.3 project(wildfly 19 app. server) I have a generic EntityService class which takes two type parameters. The EntityService class also injects another class GenericEntityService. GenericEntityService is a @Stateless bean which uses the EntityManager to do the real work (find entity, update entity, delete entity, insert entity...)

@Dependent
public class EntityService<T, ID> {

    @Inject
    private GenericEntityService<T, ID> entityService;
    private Class<T> entityClass;
    
    @Inject
    private void entityClass(InjectionPoint injectionPoint) {
        ParameterizedType type = (ParameterizedType) injectionPoint.getType();
        Class<T> entityClass = (Class<T>) type.getActualTypeArguments()[0];
        this.entityClass = entityClass;
    }
    
    public T find(ID id) {
        return entityService.find(entityClass, id);
    }
    
    public List<T> findAll() {
        return entityService.findAll(entityClass);
    }

  [...]
}

Because the find method of the EntityManager needs a class type as an argument, I injected an InjectionPoint in the entityClass method to get the actual "entity class type" and pass this as an argument to the GenericEntityService methods.

Is there anything wrong with this approach to get the class type from the type argument ?


Solution

  • As your entityClass function does not get called, it does not fill your entityClass field, so it will always be null when called.

    You may use a similar approach, avoiding the need of an injection point, using getter method that uses the parameterized type. Your code would change to:

    @Dependent
    public abstract class EntityService<T, ID> {
    
        @Inject
        private GenericEntityService<T, ID> entityService;
    
        private Class<T> entityClass;
    
        public synchronized Class<T> getEntityClass() {
            if (entityClass == null) {
                entityClass = inferEntityClass(getClass());
            }
            return entityClass;
        }
    
        private Class<?> inferEntityClass(Class<?> clazz) {
            // This code should be safer
            ParameterizedType parameterizedType = (ParameterizedType) clazz.getGenericSuperclass();
            return (Class<T>) parameterizedType.getActualTypeArguments()[0];
        }
    
        public T find(ID id) {
            return entityService.find(getEntityClass(), id);
        }
        // ...
    }
    

    On a side note, unrelated: I didn't get the reason why you should create another layer of beans instead of calling EntityManager directly from your EntityService. Please note that some features that would justify this architecture, such as transaction control, are already present on CDI.