简体   繁体   中英

Spring, CGLIB: why can't a generic class be proxied?

I'd like to ask about the root cause of the exception:

Caused by: java.lang.ClassCastException: java.lang.Class cannot be cast to  
java.lang.reflect.ParameterizedType

which happens in Spring when we make Spring generate proxies for classes (ie setting proxy-target-class="true" on a transaction manager):

<tx:annotation-driven transaction-manager="transactionManager" 
proxy-target-class="true"/>

and when the class to proxy is a parameterized class, ie

public class SomeDAOImpl<SomeEntity> implements SomeDAO ...

The full story can be, for example, read in this question: Abstract DAO pattern and Spring's "Proxy cannot be cast to ..." problem!

Question: why can't Spring proxy this class? Is it because it uses old code generation libraries? Because of type erasure? If the SomeDAOImpl was not a generic class, it would succeed (I checked that).

Please don't answer like: "you should proxy interfaces, not classes". I know that.

This looks like a Spring/CGLIB problem but the issue is actually in your constructor! I'm guessing you are trying to get the parameter type so that you can supply it to the entity manager (or whatever persistence framework you're trying to use). I'm also guessing that to do that you are calling getGenericSuperclass() and casting it to a ParameterizedType?

The way that method behaves depends on the class. It turns out that not all Class objects implement the ParameterizedType interface (which I agree, is bizarre). The generated CGLIB proxies do not keep the generics information. It looks something like:

public class SomeDAOImplProxy extends SomeDAOImpl

I don't actually know why CGLIB proxies don't keep the generics as the details of CGLIB proxies are very complex. It could be that it is simply impossible to do so. It could also be that doing so is not a priority to them.

Here is a simple experiment which takes Spring and CGLIB out of the picture but still gets that error.

public static void main(String [] args) {
    List<Object> fooList = new ArrayList<Object>();
    String fooString = "";
    System.out.println((ParameterizedType)fooList.getClass().getGenericSuperclass());
    System.out.println((ParameterizedType)fooString.getClass().getGenericSuperclass());
}

Since CGLIB subclasses your class to generate the proxy, you have to get the generic superclass of the superclass to get the ParameterizedType :

public class MyCglibProxiedBean<T> {

    private final Class<T> paramClass;

    public MyCglibProxiedBean() {
        Type genericSuperClass = getClass().getGenericSuperclass();

        // Get the generic super class of the super class if it's a cglib proxy
        if (getClass().getName().contains("$$EnhancerByCGLIB$$")) {
            genericSuperClass = getClass().getSuperclass().getGenericSuperclass();
        }

        this.paramClass = (Class<T>) ((ParameterizedType) genericSuperClass).getActualTypeArguments()[0];
    }
}

We use a solution similar to James's answer but IMHO a little bit more general:

Type genericSuperClass = repositoryClass.getGenericSuperclass();
ParameterizedType parametrizedType;
if (genericSuperClass instanceof ParameterizedType) { // class
    parametrizedType = (ParameterizedType) genericSuperClass;
} else if (genericSuperClass instanceof Class) { // in case of CGLIB proxy
    parametrizedType = (ParameterizedType) ((Class<?>) genericSuperClass).getGenericSuperclass();
} else {
    throw new IllegalStateException("class " + repositoryClass + " is not subtype of ParametrizedType.");
}

@SuppressWarnings("unchecked")
Class<T> entityClass = (Class<T>) parametrizedType.getActualTypeArguments()[0];
return entityClass;

I managed to solve the problem with this code:

Type t = getClass();
do {
  t = ((Class<T>) t).getGenericSuperclass();
} while (!(t instanceof ParameterizedType));
ParameterizedType pt = (ParameterizedType) t;
actualGenericType = (Class<T>) pt.getActualTypeArguments()[0];

I think it's a bit cleaner solution because it doesn't use internal structure of CGLIB.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM