如何获得T类字段的类?

时间:2021-10-09 22:45:59

I write JUnit tests for some Spring MVC Controllers. The initialization of the JUnit test is common for all my Controllers tests, so I wanted to create an abstract class that does this initialization.

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

Thus, I created the following code:

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

@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);
    }

}

The idea is to create, for each controller I want to test a JUnit test class that extends my AbstractSpringMVCControllerTest. The type given in the extends declaration is the class of the Controller.

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

For example, if I want to test my AccountController, I will create the AccountControllerTest class like that:

例如,如果我想测试我的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);
        ...
    }

}

My problem is located in the last line of the initContext() method of the abstract class. This abstract class declares the controller object as a T object, but how can say to the Spring Application Context to return the bean of type T?

我的问题位于抽象类的initContext()方法的最后一行。这个抽象类将控制器对象声明为T对象,但是怎么能说Spring Application Context返回类型为T的bean呢?

I've tried something like that:

我尝试过类似的东西:

    Class<?> controllerClass = this.getClass().getSuperclass().getDeclaredField("controller").getType();
    controller = (T) applicationContext.getBean(controllerClass);

but controllerClass returns the java.lang.Object.class class, not AccountController.class.

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

Of course, I can create a public abstract Class<?> getControllerClass(); method, which will be overriden by each JUnit Controller test class, but I prefer to avoid this solution.

当然,我可以创建一个公共抽象类 getControllerClass();方法,将被每个JUnit Controller测试类覆盖,但我更愿意避免使用此解决方案。

So, any idea?

那么,任何想法?

3 个解决方案

#1


4  

This is possible if your subclasses of AbstractSpringMVCControllerTest bind T at compile time. That is, you have something like

如果AbstractSpringMVCControllerTest的子类在编译时绑定T,则可以执行此操作。也就是说,你有类似的东西

public class DerpControllerTest extends AbstractSpringMVCControllerTest<DerpController> { }

rather than

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.

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

The following classes demonstrate how to access the type of 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"
    }
}

#2


3  

This is different from the type erasure we normally see. With type erasure, we don't know the parameter of the current class (the one you get with getClass()), but you can get those in super class / super interface (those you get with getGenericSuperxxxxx()) because this is part of the type declaration.

这与我们通常看到的类型擦除不同。对于类型擦除,我们不知道当前类的参数(使用getClass()得到的参数),但是你可以在超类/超级接口(你用getGenericSuperxxxxx()得到的那些)中得到那些参数,因为这是声明的类型。

This won't give your the type of controller field, but I hope this is enough for your purpose.

这不会给你控制器字段的类型,但我希望这足以达到你的目的。

Code:

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]);
    }
}

Output:

class java.lang.String

In your case, it would be

在你的情况下,它会

Class controllerClass = (Class)( ((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0]);

Something to notes:

需要注意的事项:

If the class B is also parameterized like this:

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

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

This won't work. Or if you have another class extends B, it will have problem too. I won't go into all those cases, but you should get the idea.

这不行。或者,如果你有另一个类扩展B,它也会有问题。我不会涉及所有这些案例,但你应该明白这一点。

#3


1  

You can't because at runtime, due to ERASURE, the JVM cannot know the class of your "controller" attribute. It is considered as Object...

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

#1


4  

This is possible if your subclasses of AbstractSpringMVCControllerTest bind T at compile time. That is, you have something like

如果AbstractSpringMVCControllerTest的子类在编译时绑定T,则可以执行此操作。也就是说,你有类似的东西

public class DerpControllerTest extends AbstractSpringMVCControllerTest<DerpController> { }

rather than

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.

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

The following classes demonstrate how to access the type of 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"
    }
}

#2


3  

This is different from the type erasure we normally see. With type erasure, we don't know the parameter of the current class (the one you get with getClass()), but you can get those in super class / super interface (those you get with getGenericSuperxxxxx()) because this is part of the type declaration.

这与我们通常看到的类型擦除不同。对于类型擦除,我们不知道当前类的参数(使用getClass()得到的参数),但是你可以在超类/超级接口(你用getGenericSuperxxxxx()得到的那些)中得到那些参数,因为这是声明的类型。

This won't give your the type of controller field, but I hope this is enough for your purpose.

这不会给你控制器字段的类型,但我希望这足以达到你的目的。

Code:

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]);
    }
}

Output:

class java.lang.String

In your case, it would be

在你的情况下,它会

Class controllerClass = (Class)( ((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0]);

Something to notes:

需要注意的事项:

If the class B is also parameterized like this:

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

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

This won't work. Or if you have another class extends B, it will have problem too. I won't go into all those cases, but you should get the idea.

这不行。或者,如果你有另一个类扩展B,它也会有问题。我不会涉及所有这些案例,但你应该明白这一点。

#3


1  

You can't because at runtime, due to ERASURE, the JVM cannot know the class of your "controller" attribute. It is considered as Object...

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