如何获得T类字段的类?

时间:2012-06-22 15:05:52

标签: java spring generics spring-mvc

我为一些Spring MVC控制器编写了JUnit测试。 JUnit测试的初始化对于我的所有Controllers测试都是通用的,所以我想创建一个执行此初始化的抽象类。

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

@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 Application Context返回类型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 Controller测试类覆盖,但我更愿意避免这种解决方案。

那么,任何想法?

3 个答案:

答案 0 :(得分:4)

如果你的AbstractSpringMVCControllerTest的子类在编译时绑定T,那么这是可能的。也就是说,你有像

这样的东西
public class DerpControllerTest extends AbstractSpringMVCControllerTest<DerpController> { }

而不是

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

我猜你可能有前者。在这种情况下,T的类型在运行时从Class对象AbstractSpringMVCControllerTest中删除,但Class DerpControllerTest对象执行< / em>提供了一种了解T是什么的方法,因为它在编译时绑定了T

以下课程演示了如何访问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"
    }
}

答案 1 :(得分: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,它也会有问题。我不会讨论所有这些情况,但你应该明白这一点。

答案 2 :(得分:1)

你不能因为在运行时由于ERASURE,JVM无法知道你的“controller”属性的类。它被视为对象......