2012-06-22 17 views
7

Escribo pruebas de JUnit para algunos controladores Spring MVC. La inicialización de la prueba JUnit es común para todas mis pruebas de Controladores, así que quería crear una clase abstracta que realice esta inicialización.¿Cómo obtener la clase de un campo de tipo T?

Por lo tanto, he creado el siguiente código:

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

} 

La idea es crear, para cada controlador Quiero probar una clase de prueba unitaria que se extiende mi AbstractSpringMVCControllerTest. El tipo proporcionado en la declaración extends es la clase del controlador .

Por ejemplo, si quiero probar mi AccountController, voy a crear la clase AccountControllerTest así:

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

} 

Mi problema se encuentra en la última línea del método de la clase abstracta initContext(). Esta clase abstracta declara el objeto controller como un objeto T, pero ¿cómo puede decir al Contexto de la aplicación Spring que devuelva el bean del tipo T?

He intentado algo así:

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

pero controllerClass devuelve la clase java.lang.Object.class, no AccountController.class.

Por supuesto, puedo crear un método public abstract Class<?> getControllerClass();, que será reemplazado por cada clase de prueba de JUnit Controller, pero prefiero evitar esta solución.

¿Alguna idea?

+0

Si tiene una mejor idea para el título de la pregunta, no vacile :) – romaintaz

+1

Bienvenido a la pesadilla de borrado tipo java. –

+2

Es imposible. Duplicado de [Obtener nombre de tipo para el parámetro genérico de clase genérica] (http://stackoverflow.com/questions/6624113/get-type-name-for-generic-parameter-of-generic-class) –

Respuesta

4

Esto es posible si sus subclases de AbstractSpringMVCControllerTest se unen T en tiempo de compilación. Es decir, usted tiene algo así como

public class DerpControllerTest extends AbstractSpringMVCControllerTest<DerpController> { } 

en lugar de

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

supongo es probable que tenga la primera. En este caso, el tipo de T se borra del objeto Class para AbstractSpringMVCControllerTest en tiempo de ejecución, pero el objeto de ClassDerpControllerTesthace proporciona una manera de saber lo que es T, ya que une T en tiempo de compilación.

las clases siguientes demuestran cómo acceder al tipo de 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" 
    } 
} 
+0

Bien, no me di cuenta de que podría obtener parámetros de clase genéricos como este. Incluso si la clase de prueba no subclase directamente la superclase genérica, estoy seguro de que podría escribir un código para ir a la jerarquía de clases y obtener la clase o la interfaz que busca. – Alex

+0

Sí, eso es definitivamente posible, pero no quería entrar en eso, porque el manejo 'Sub se extiende a Super ; SubSub extends Sub 'sería diferente de' Sub extends Super ; SubSub extiende Sub'. Y eso aún no aborda la posibilidad de introducir otros parámetros genéricos, como 'Sub se extiende Super ; SubSub extiende Sub '. – matts

+0

¡Gracias, funcionó! – romaintaz

1

No se puede porque en tiempo de ejecución, debido a ERASURE, la JVM no puede conocer la clase de su atributo "controller". Se considera como Objeto ...

3

Esto es diferente del tipo de borrado que normalmente vemos. Con borrado de tipo, no conocemos el parámetro de la clase actual (el que obtienes con getClass()), pero puedes obtenerlos en la superclase/súper interfaz (los que obtienes con getGenericSuperxxxxx()) porque esto es parte de la declaración de tipo .

Esto no dará su tipo del campo controller, pero espero que esto sea suficiente para su propósito.

Código:

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

Salida:

class java.lang.String 

En su caso, sería

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

Algo de notas:

Si la clase B es al tan parametrizado como este:

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

Esto no va a funcionar. O si tienes otra clase extiende B, también tendrá un problema. No entraré en todos esos casos, pero debes entender la idea.

+0

Hombre, la reflexión es increíble, pero odio cómo lo implementa Java. Supongo que eso es lo que sucede cuando implementa la reflexión para un idioma que originalmente no lo soportaba y que no estaba diseñado teniendo en cuenta la reflexión. ... Por otra parte, hay bastantes cosas que no me gustan sobre la forma en que se configura la biblioteca estándar de Java, por lo que ese podría ser el problema para empezar. – JAB

Cuestiones relacionadas