简体   繁体   English

CGLIB春季:为什么不能代理泛型类?

[英]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): 当我们让Spring为类生成代理时(即在事务管理器上设置proxy-target-class="true" ,这会在Spring中发生:

<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! 例如,可以在以下问题中阅读全文: 抽象DAO模式和Spring的“代理不能转换为...”问题!

Question: why can't Spring proxy this class? 问题: Spring为什么不能代理此类? 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). 如果SomeDAOImpl不是通用类,它将成功(我检查过)。

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! 这看起来像一个Spring / CGLIB问题,但问题实际上出在您的构造函数中! 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? 我还猜到这样做是要调用getGenericSuperclass()并将其强制转换为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). 事实证明,并非所有Class对象都实现ParameterizedType接口(我同意,这很奇怪)。 The generated CGLIB proxies do not keep the generics information. 生成的CGLIB代理不保留泛型信息。 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. 我实际上不知道为什么CGLIB代理不保留通用名称,因为CGLIB代理的细节非常复杂。 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. 这是一个简单的实验,它使Spring和CGLIB脱颖而出,但仍然出现该错误。

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 : 由于CGLIB子类化了您的类以生成代理,因此您必须获取超类的通用超类以获取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: 我们使用的解决方案与James的答案类似,但恕我直言有点笼统:

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. 我认为这是一个更干净的解决方案,因为它不使用CGLIB的内部结构。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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