【问题标题】:How to get the class of a field of type T?如何获取类型为 T 的字段的类?
【发布时间】:2012-06-22 15:05:52
【问题描述】:

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

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

@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 声明中给出的类型是 Controller 的类。

例如,如果我想测试我的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&lt;?&gt; getControllerClass(); 方法,该方法将被每个 JUnit Controller 测试类覆盖,但我更愿意避免这种解决方案。

那么,有什么想法吗?

【问题讨论】:

  • 如果您对问题标题有更好的想法,请不要犹豫:)
  • 欢迎来到java类型擦除噩梦。
  • 是的,我完全忘记了我(再次)遇到了这种类型的擦除。
  • 不工作吗?我猜不是,如果它是运行时的事情....实际上,现在我想起来了,当我遇到类似的问题时,我最终做了一些与您的public abstract Class&lt;?&gt; getControllerClass(); 解决方案非常相似的事情,尽管我所做的是将类名存储为字符串并使用Class.forName() 获取类。

标签: java spring generics spring-mvc


【解决方案1】:

这是可能的如果您的AbstractSpringMVCControllerTest 的子类在编译时绑定T。也就是说,你有类似的东西

public class DerpControllerTest extends AbstractSpringMVCControllerTest<DerpController> { }

而不是

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

我猜你可能有前者。在这种情况下,T 的类型会在运行时从 AbstractSpringMVCControllerTestClass 对象中删除,但 DerpControllerTestClass 对象确实提供了一种方法来了解 @ 987654330@ 是,因为它在编译时绑定了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"
    }
}

【讨论】:

  • 很好,我没有意识到你可以得到这样的泛型类参数。即使测试类不直接继承泛型超类,我相信您也可以编写一些代码来遍历类层次结构并获取您所追求的类或接口。
  • 是的,这绝对是可能的,但我不想参与其中,因为处理Sub&lt;T&gt; extends Super&lt;T&gt;; SubSub extends Sub&lt;Double&gt;Sub extends Super&lt;T&gt;; SubSub extends Sub 不同。这仍然没有解决引入其他泛型参数的可能性,例如Sub&lt;S, T&gt; extends Super&lt;T&gt;; SubSub&lt;S&gt; extends Sub&lt;S, Double&gt;
【解决方案2】:

这与我们通常看到的类型擦除不同。使用类型擦除,我们不知道当前类的参数(你用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 标准库的设置方式,所以这可能是一开始的问题。
【解决方案3】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-13
    相关资源
    最近更新 更多