如何获取类型为T的字段的类?

7

我为一些Spring MVC Controllers编写JUnit测试。JUnit测试的初始化对于所有控制器测试都是相同的,因此我想创建一个可以完成此初始化的抽象类。

因此,我创建了以下代码:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath*:spring/applicationContext-test.xml", "classpath*:spring/spring-mvc-test.xml" })
@Transactional
public abstract class AbstractSpringMVCControllerTest<T> {

    @Autowired
    protected ApplicationContext applicationContext;

    protected MockHttpServletRequest request;

    protected MockHttpServletResponse response;

    protected HandlerAdapter handlerAdapter;

    protected T controller;

    @SuppressWarnings("unchecked")
    @Before
    public void initContext() throws SecurityException, NoSuchFieldException {
        request = new MockHttpServletRequest();
        response = new MockHttpServletResponse();
        handlerAdapter = applicationContext.getBean(AnnotationMethodHandlerAdapter.class);
        // Does not work, the problem is here...    
        controller = applicationContext.getBean(T);
    }

}

我的想法是为我要测试的每个控制器创建一个扩展了我的 AbstractSpringMVCControllerTest 的JUnit测试类。在extends声明中给定的类型是控制器的类。

例如,如果我想测试我的AccountController,我将创建以下AccountControllerTest类:

public class AccountControllerTest extends AbstractSpringMVCControllerTest<AccountController> {

    @Test
    public void list_accounts() throws Exception {
        request.setRequestURI("/account/list.html");
        ModelAndView mav = handlerAdapter.handle(request, response, controller);
        ...
    }

}

我的问题出现在抽象类的initContext()方法的最后一行。这个抽象类声明了controller对象作为一个T对象,但是我该如何告诉Spring应用程序上下文返回类型为T的bean呢?
我尝试了类似于这样的代码:
    Class<?> controllerClass = this.getClass().getSuperclass().getDeclaredField("controller").getType();
    controller = (T) applicationContext.getBean(controllerClass);

但是controllerClass返回的是java.lang.Object.class类,而不是AccountController.class

当然,我可以创建一个public abstract Class<?> getControllerClass();方法,每个JUnit控制器测试类都将重写该方法,但我更喜欢避免这种解决方案。

那么,你有什么想法吗?


如果您对问题标题有更好的想法,请毫不犹豫地提出 :) - Romain Linsolas
1
欢迎来到Java类型擦除的噩梦。 - J-16 SDiZ
2
这是不可能的。重复的获取泛型类的泛型参数类型名称 - JB Nizet
是的,我确实忘记了我再次遇到这种类型擦除。 - Romain Linsolas
好的。这有点不同,这是超类型中的一个参数。请看我的答案。 - J-16 SDiZ
显示剩余2条评论
3个回答

4
如果您的AbstractSpringMVCControllerTest子类在编译时绑定了T,那么这是可能的。也就是说,您需要像以下这样写代码:
public class DerpControllerTest extends AbstractSpringMVCControllerTest<DerpController> { }

而不是

public class AnyControllerTest<T> extends AbstractSpringMVCControllerTest<T> { }

I'm guessing you probably have the former. In this case, the type of T is erased from the Class object for AbstractSpringMVCControllerTest at runtime, but the Class object for DerpControllerTest does provide a way to know what T is, since it bound T at compile time.
The following classes demonstrate how to access the type of T: Super.java
import java.lang.reflect.ParameterizedType;
public abstract class Super<T> {

    protected T object;

    @SuppressWarnings("unchecked")
    public Class<T> getObjectType() {
        // This only works if the subclass directly subclasses this class
        return (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }

}

Sub.java

public class Sub extends Super<Double> {
}

Test.java

public class Test {
    public static void main(String...args) {
        Sub s = new Sub();
        System.out.println(s.getObjectType()); // prints "Class java.lang.Double"
    }
}

不错,我没想到你可以像这样获取通用类参数。即使测试类没有直接继承通用超类,我相信你可以编写一些代码来遍历类层次结构并获取你想要的类或接口。 - Alex
是的,这绝对是可能的,但我不想深入讨论,因为处理 Sub<T> extends Super<T>; SubSub extends Sub<Double>Sub extends Super<T>; SubSub extends Sub 是不同的。而且这仍然没有解决引入其他泛型参数的可能性,比如 Sub<S, T> extends Super<T>; SubSub<S> extends Sub<S, Double> - matts

3
这与我们通常看到的类型擦除不同。在类型擦除中,我们不知道当前类的参数(使用getClass()获取的类),但是您可以在超类/超接口中获取它们(使用getGenericSuperxxxxx()获取的类),因为这是类型声明的一部分。
这不会给出controller字段的类型,但我希望这对您的目的足够了。
代码:
public class A<P> {
}

import java.lang.reflect.ParameterizedType;

public class B extends A<String> {

    public  static void main(String[] arg) {
        System.out.println(
                ((ParameterizedType)B.class.getGenericSuperclass()).getActualTypeArguments()[0]);
    }
}

输出:

class java.lang.String

在你的情况下,这将是:
Class controllerClass = (Class)( ((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0]);

需要注意的几点:

如果类 B 也像这样是参数化的:

public class B<X> extends A<X> {}

这个方法行不通。如果你有另一个继承自B的类,它也会遇到问题。虽然我不会涉及所有这些情况,但你应该能理解。


哎呀,反射真是太棒了,但我讨厌Java的实现方式。也许这就是为什么在一门最初不支持反射且没有考虑到反射的语言中实现反射时会出现的问题。...再说了,我对Java标准库的设置方式有很多不喜欢的地方,所以这可能才是问题的根源。 - JAB

1

由于类型擦除,在运行时JVM无法知道您的"controller"属性的类,因此您无法这样做。它被视为Object...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接