Blog
Zugriff auf generische Typen zur Laufzeit in Java

Ich war dabei, meine n-te Dao-Implementierung zu schreiben, diesmal mit JPA. Ich (und wahrscheinlich viele andere) erstelle normalerweise eine DAO pro Entität, wobei der Entitätstyp parametrisiert wird. Spezifische DAO-Instanzen für Entitäten implementieren die generische DAO unter Verwendung ihres Entitätstyps als Typparameter. Es gibt eine generische DAO-Implementierung, die gängige Operationen wie findById, persist, remove, etc. enthält. Dieses generische DAO verwendet den von den implementierenden DAO-Klassen angegebenen Klassentyp (z.B. eine Person), um die durch diesen Typ angegebene Entität zu manipulieren oder abzufragen. Das (leicht) lästige Problem für mich war immer, diesen Entitätstyp in der generischen DAO-Superklasse zu instanziieren. Ich habe dies immer getan, indem ich einfach einen Konstruktor in der generischen DAO erstellt habe, der ein Klassenargument mit der erforderlichen Klasse der Entität aufnimmt. Es gibt jedoch einen besseren Weg, den ich in diesem Beitrag zeigen werde.
Um mit einem Beispiel zu beginnen, sieht die generische DAO-Schnittstelle wie folgt aus:
public interface GenericEntityDao {
T findById(Serializable id);
Liste findAll();
... mehr Methoden weggelassen
}
Und seine generische DAO-Implementierungsklasse sieht in etwa so aus:
public abstract class GenericJpaDao implements GenericDao {
private Class entityBeanType;
@PersistenzKontext
private EntityManager entityManager;
public T findById(Serializable id) {
return entityManager.find(getEntityBeanType(), id);
}
public List findAll() {
return entityManager.createQuery("von " + getEntityBeanType().getName() )
.getResultList();
}
protected Klasse getEntityBeanType() {
return entityBeanType;
}
... mehr Methoden weggelassen
}
Die Frage ist, wie wir den parametrisierten Typ von T für das Feld entityBeanType zur Verwendung in unseren Abfragen erhalten. Früher habe ich diesen Typ einfach im Konstruktor instanziiert, etwa so:
public GenericJpaDao(Class entityBeanType) {
this.entityBeanType = entityBeanType;
}
public JpaPersonDao extends GenericJpaEntityDao implements PersonDao {
public PersonDao() {
super(Person.class);
}
}
Das ist jedoch etwas albern, da wir bereits aus dem Typ-Parameter wissen, dass die Entität, an der wir interessiert sind, den Typ Person hat. Es gibt jedoch einen einfacheren Ausweg: Wir können die Java-Klasse ParameterizedType verwenden, um Informationen über den deklarierten generischen Typ zu erhalten. Wie in der Javadoc angegeben:
/** * ParameterizedType repräsentiert einen parametrisierten Typ wie * Collection<String>. * * Ein parametrisierter Typ wird erstellt, wenn er zum ersten Mal von einer * reflektierenden Methode benötigt wird, wie in diesem Paket angegeben. Wenn ein * parametrisierter Typ p erstellt wird, wird die generische Typdeklaration, die * p instanziiert, aufgelöst, und alle Typargumente von p werden * rekursiv erstellt.
Es scheint also, dass ParameterizedType die Informationen enthält, die wir brauchen. Wie erhalten wir eine Instanz dieses Typs? Die Antwort liegt in der Java-Methode
* Wenn die Superklasse ein parametrisierter Typ ist, muss das zurückgegebene Objekt Type * die im Quellcode verwendeten Parameter des tatsächlichen Typs * genau wiedergeben. Der parametrisierte Typ *, der die Superklasse repräsentiert, wird erstellt, wenn er nicht zuvor * erstellt wurde.
Die Schnittstelle Type selbst ist nur eine Marker-Schnittstelle, die keine Methoden enthält. Je nach Fall wird ein Type zurückgegeben, der die Type-Schnittstelle erweitert. In unserem Fall verwenden wir die Methode getGenericSuperclass für eine Klasse, die unsere GenericJpaDao-Klasse erweitert. Die Superklasse ist also JpaGenericDao, die ein parametrisierter Typ ist. In diesem Fall ist das eigentliche Type-Objekt, das von getGenericSuperClass zurückgegeben wird, eine Instanz von ParameterizedType. Diese Klasse enthält eine Methode getActualTypeArguments, die ein Array mit allen im Quellcode verwendeten Parametern des generischen Typs zurückgibt. Das ist genau das, was wir wollen. Daher können wir im Konstruktor unserer Klasse GenericJpaDao das Argument Class weglassen und stattdessen Folgendes tun:
@SuppressWarnings("ungeprüft")
public GenericJpaDao() {
this.entityBeanType = ((Klasse) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0]);
}
Wir wissen, dass es genau ein Typ-Argument in unserer Klasse gibt, also können wir das erste Element des zurückgegebenen Arrays nehmen, das die Klasse unserer Entität liefert. Auf diese Weise können wir uns all der lästigen Konstruktoren mit Class-Argumenten entledigen und die Bestimmung des Entity-Typs an einer einzigen Stelle vornehmen. Dies mag nicht die größte Code-Reduzierung bieten, die Sie je erlebt haben, aber es ist auf jeden Fall ein netter Trick.
Verfasst von
Arjan Blokzijl
Unsere Ideen
Weitere Blogs
Contact



