itsfoobar
itsfoobar

Reputation: 52

Get type of a generic parameter in SpringBoot with reflection

I have a spring project with several classes "IceCream", "Chocolate" that implements an interface "FoodInterface". FoodInterface:

public interface FoodInterface{
    String getType();
}

IceCream:

public class IceCream implements FoodInterface{
    @Override
    public String getType() { return "ice-cream"; }
}

I also have repositories for these classes:

public interface IceCreamRepository extends JpaRepository<IceCream, String>, FoodRepositoryInterface<IceCream> {
    IceCream findAllById(String id)
}

To select the correct repository when having the type string (e.g. ice-cream) and the string id i have created a FoodRepositoryInterface:

public interface FoodRepositoryInterface<T extends FoodInterface> {
    String getType() {}
}

and I changed my repositories to this:

public interface IceCreamRepository extends JpaRepository<IceCream, String>, FoodRepositoryInterface<IceCream> {
    String getType() { return "ice-cream"; }
    IceCream findAllById(String id)
}

But I want to be able to have ice-cream in one place (in the IceCream class and the same for the Chocolate class). Therefore I want to get the class of the generic type, call the constructor and call getType() all in the FoodRepositoryInterface. return new T().getType() does not work so I wanted to use reflections. And here i followed Get type of a generic parameter in Java with reflection which resulted in this mess:

public interface FoodRepositoryInterface<T extends FoodInterface> {

   default String getType() {
        var genericTypeClass = getClass();
        var foodRepositoryInterfaceClass = genericTypeClass.getGenericSuperclass();
        var castedFoodRepositoryInterfaceClass = (ParameterizedType) foodRepositoryInterfaceClass;
        var arguments = castedFoodRepositoryInterfaceClass.getActualTypeArguments();
        Class<T> persistentClass = (Class<T>) arguments[0];
        try {
            return persistentClass.getConstructor().newInstance().getType();
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    };

}

Running that, I got java.lang.ClassCastException: class java.lang.Class cannot be cast to class java.lang.reflect.ParameterizedType (java.lang.Class and java.lang.reflect.ParameterizedType are in module java.base of loader 'bootstrap') in the last code block. So I looked onto the classes that were actually in the variables: actual variables

I expected IceCream and FoodRepositoryInterface but not Proxy and Proxy151. It explains why the casting fails. I think this has something to do with Spring and Spring AOP. In the Proxy variable I couldn't determine any hint to the IceCream class or the FoodRepositoryInterface class.

What do I need to do here to get my IceCream.class?

Upvotes: -1

Views: 139

Answers (1)

itsfoobar
itsfoobar

Reputation: 52

I solved it with another workaround:

    Class<? extends FoodRepositoryInterface> genericTypeClass = getClass();
    Class<T> persistentClass = (Class<T>) GenericTypeResolver.resolveType(FoodRepositoryInterface.class.getTypeParameters()[0], genericTypeClass);

The GenericTypeResolver is from org.springframework.core. The workaround is based on this post: https://stackoverflow.com/a/9202329/18427492

I still don't understand the proxy-thing but this workaround is fine for me. Maybe someone can provide a solution for others that can't use the GenericTypeResolver.

Upvotes: 0

Related Questions