Pertanyaan Bagaimana cara mendapatkan kelas bidang tipe T?


Saya menulis tes JUnit untuk beberapa Spring MVC Controllers. Inisialisasi tes JUnit adalah umum untuk semua tes Controllers saya, jadi saya ingin membuat kelas abstrak yang melakukan inisialisasi ini.

Jadi, saya membuat kode berikut:

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

}

Idenya adalah untuk membuat, untuk setiap pengontrol saya ingin menguji kelas tes JUnit yang memanjang saya AbstractSpringMVCControllerTest. Jenis yang diberikan dalam extends deklarasi adalah kelas dari Kontroler.

Misalnya, jika saya ingin menguji saya AccountController, Saya akan membuat AccountControllerTest kelas seperti itu:

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

}

Masalah saya terletak di baris terakhir initContext() metode kelas abstrak. Kelas abstrak ini mendeklarasikan controller objek sebagai T objek, tapi bagaimana bisa mengatakan ke Spring Application Context untuk mengembalikan kacang tipe T?

Saya sudah mencoba sesuatu seperti itu:

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

tapi controllerClass mengembalikan java.lang.Object.class kelas, tidak AccountController.class.

Tentu saja, saya bisa membuat public abstract Class<?> getControllerClass(); metode, yang akan ditimpa oleh setiap kelas tes JUnit Controller, tetapi saya lebih memilih untuk menghindari solusi ini.

Jadi, ada ide apa?


7
2018-06-22 15:05


asal


Jawaban:


Ini mungkin jika subclass Anda dari AbstractSpringMVCControllerTest mengikat T pada waktu kompilasi. Artinya, Anda memiliki sesuatu seperti itu

public class DerpControllerTest extends AbstractSpringMVCControllerTest<DerpController> { }

daripada

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

Saya menduga Anda mungkin memiliki yang pertama. Dalam hal ini, jenis T dihapus dari Class objek untuk AbstractSpringMVCControllerTest saat runtime, tetapi Class objek untuk DerpControllerTest  tidak menyediakan cara untuk mengetahui apa T adalah karena terikat T pada waktu kompilasi.

Kelas-kelas berikut menunjukkan cara mengakses jenis 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"
    }
}

4
2018-06-22 15:28



Ini berbeda dengan jenis penghapusan yang biasanya kita lihat. Dengan penghapusan jenis, kita tidak tahu parameter kelas saat ini (yang Anda dapatkan dengan getClass()), tetapi Anda bisa mendapatkannya di antarmuka super kelas / super (yang Anda dapatkan dengan getGenericSuperxxxxx()) karena ini adalah bagian dari deklarasi tipe.

Ini tidak akan memberi Anda tipe controller lapangan, tapi saya harap ini cukup untuk tujuan Anda.

Kode:

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

Keluaran:

class java.lang.String

Dalam kasus Anda, itu akan terjadi

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

Sesuatu untuk dicatat:

Jika kelas B juga parameter seperti ini:

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

Ini tidak akan berhasil. Atau jika Anda memiliki kelas lain memperluas B, itu akan memiliki masalah juga. Saya tidak akan membahas semua kasus itu, tetapi Anda harus mendapatkan ide itu.


3
2018-06-22 15:25



Anda tidak bisa karena saat runtime, karena ERASURE, JVM tidak dapat mengetahui kelas atribut "pengontrol" Anda. Ini dianggap sebagai Objek ...


1
2018-06-22 15:15