Neue Instanz mit Reflections

Hi ho,

eigentlich nur mal kurze Frage in Richtung best practice:

Class.newInstance() - scheidet aus bekannten Gründen aus
Constructor.newInstance() - wird empfohlen
Factory mit getInstance()-Methode und dann über Method.invoke()

Bevor Vorschläge kommen: Es führt leider auf Grund der Anforderung und dem was umgesetzt werden soll kein Weg um Reflections rum - muss leider sein (würde sonst Factory-Pattern nutzen).

Sen

zu den ersten beiden ist wohl nicht viel mehr zu sagen als in
https://docs.oracle.com/javase/tutorial/reflect/member/ctorInstance.html
schon steht

‘Factory mit getInstance()-Methode und dann über Method.invoke()’ klingt aber relativ mysteriös,
auf die Schnelle in Suchmaschinen nichts konkretes zu finden, hast du einen Link zu einem Design Pattern oder so?

Factorys zu haben und dennoch Reflection zu brauchen?, nun, du sprichst schon davon und wehrst Fragen ab,
aber vielleicht doch ein wenig mehr Ausführung wert, falls interessiert

freilich wäre jeder Weg, um etwa von einem String “KlasseXY” zu einem Objekt zu kommen, irgendeine Art von Reflection, auch wenn selber gebaut

Also ganz vorweg: Es gibt eigentlich immer Wege, die um Reflections herum führen, nur findet man sie immer erst als letztes, weil sie weniger offensichtlich sind. Wer Klassen mit Reflections drangsalieren muss, führt sicher etwas im Schilde - Alle Klassen in Java lassen sich zumindest dekorieren (final) wenn nicht sogar erweitern.

Bevor also von mir ein Vorschlag kommt, der konkret auf Reflections abzielt, muss ich erstmal fragen, ob die Klasse, die du drangsalieren willst in einer Fremd-API oder in einer eigenen steckt, denn rein zufällig dachte ich einst genau dasselbe, wie du - also dass man um Reflections nicht umher kommt - bis ich das ServiceProviderInterface für meine Zwecke (Dateiloader) endeckte.

Kurz gefragt: Wie wäre es, wenn du mal beschreibst, was du vor hast?

Hintergrund: Zur Runtime wird eine Config gelesen die Klassennamen als String liefert. Auch stehen diese Klassen erst zur Runtime zur Verfügung - also beim bauen gegen eine vorhandene Factory statisch linken geht nicht da sonst schlicht vom Compiler “Factory nicht gefunden” kommen würde.
Bleibt also, wie gesagt: auf Grund der Anforderungen, nur über URLClassLoader.loadClass(String).
Die Frage ist nun: Wie möglichst sauber nun aus dem Class-Objekt eine Instanz ziehen?

SPI scheidet aus - ServiceLoader - Class.newInstance() - und wird daher auch eigentlich von abgeraten - unabhängig dass es innerhalb der SE häufig genutzt wird.

@SlaterB
Grundsätzlich war die Überlegung dahinter, anstatt der eigentlichen Klasse deren Instanz nachher benötigt wird eine Factory vorzuschalten die eine static getInstance() beinhaltet die über .getDeclaredMethod().invoke() aufgerufen wird.

Bin für bessere Vorschläge gerne offen, da aber Java keine wirklich besseren Möglichkeiten bietet zur Runtime dynamisch Klassen nachzuladen dürfte wohl nicht viel mehr übrig bleiben.
War halt nur mal so die Frage ob halt direkt den Konstruktor callen oder halb ne Factory vor (was ich eher bevorzugen würde).

Konstruktor oder Factory, diese Entscheidung ist ja von Reflection unabhängig,
stellt sich im normalen Code genauso:

nehme ich new Xyz(a,b,c) oder XyzFactory.create(a,b,c)?
wenn für Factory entschieden, wofür es sicher genug Gründe gibt, dann eben Factory

im Reflection-Fall genauso, wobei sicher Reflection an sich bzw. von diesem Modewort unabhängig der Anwendungsfall ‘ich habe String und will gutes Objekt’ ein zusätzlicher Grund für Factory als Wrapper/ Zusatzoptionen sein kann, ja

Factory aber nur, falls es diese individuell zu jeder Klasse gibt,
wenn Grund dafür vorhanden und man alles entsprechend so einrichtet, ‘PlugIns bieten bestimmte Klasse an und jeweils Factory die so und so angesprochen werden kann’, dann kann man das so machen, ja

falls für die Factories aber bisher keine begründete Idee, und jede Methode dort auch nur ‘new Xyz()’ zurückgibt, dann erstmal kein großer Gewinn…, wobei immerhin noch erweiterbar

Würde ja lieber auf Reflections verzichten, nur ist mir kein weg bekannt wie Java sonst eine Klasse, die erst zur Runtime bekannt und verfügbar ist, nachladen kann bzw. eine Instanz dieser zu erzeugen.

Wer bitte rät denn davon ab, das SPI zu verwenden? Nur weil die Konstruktoren der zu ladenden Klassen öffentlich und parameterlos sein müssen? Nun ja… Die durch SPI geladenen Klassen können immerhin solche sein, über die man überhaupt erst an Klasseninstanzen kommt, die dann protegierte Konstruktoren haben - Service-Provider-Klassen halt. Solche Klassen haben im Idealfall überhaupt keinen Inhalt oder aber nur statischen und von daher ist es vollkommen egal, ob man sie - warum auch immer oder wie auch immer - händisch ohne das Interface instanziert.

Aber davon mal ab… man kann sich seinen eigenen URLClassLoader erweitern, welcher Anfangs auch leer sein kann und damit den ClassPath innerhalb seines Projekts dynamisch erweitern

[code]class MyClassLoader extends URLClassLoader {
MyClassLoader() {
super(new URL[0]);
}

protected void addURL(URL url) {
// … Gültigkeit prüfen
super.addURL(url);
}
}[/code]und dann über diesen die erst zur Laufzeit bekannten Klassen laden. Gültige URLs wären hier dann Jar-Archive und Verzeichnisse, welche sich recht simpel über “new File(pathorfile).toURI().toURL()” erstellen lassen. Dann kann man entweder mit dem SPI arbeiten oder irgendwas mit “Class.forName(String name, boolean init, ClassLoader loader)” rumfrickeln - immer im Hinterkopf habend, dass die Service-Provider-Klasse nicht viel mehr als eine Factory-Methode für die eigentliche “Service-Klasse” bietet.

1 „Gefällt mir“

auch auf die Gefahr hin, nur bekannte Trivilalitäten zu nennen, die nicht in Frage kommen:

wenn irgendein PlugIn vorher drankommt, mit Initialisierungs-/ Begrüßungsmethode usw.,
könnte dieses in einer Map <String, FactoryInterface> zu „Xyz“ ein Objekt der Klasse XyzFactory ablegen,
ohne Reflection, das PlugIn kennt seine wichtigen neuen Klassen ja,

der allgemein Code kann dann mit dem String klassenName
FactoryInterface fac = facMap.get(klassenName);
Object o = fac.create();
aufrufen usw.

wenn es aber darum geht, überhaupt erst an ein erstes Objekt des PlugIns zu kommen, an dem dann Initialisierungsmethode aufzurufen…


ansonsten ist Reflection aber auch kein Hexenwerk, erspart im Allgemeinen solche Umstände wie oben,
falls nicht aus irgendeinem Grund besonders angebracht, kann man durchaus verwenden

etwa wenn DB-Treiber über String variabel,
die DB-Libraries werden sich kaum selber im Programm anmelden…

Gerade fällt mir noch ein, dass das SPI ja eigentlich nichts anderes macht, als du mit deiner Config-Datei. Wenn es also nur um eine einzige Klasse geht, deren Codebase erst zur Laufzeit bekannt wird, brauchst du eigentlich nur feststellen, wie dieser Pfad zur Codebase lautet und das Ding mit deinem eigenen Loader laden. SPI ist dann natürlich vollkommen überzogen, das ist richtig.

Der Haken ab SPI ist das intern ServiceLoader genutzt wird, der intern lazy mit Class.newInstance arbeitet, und davon wird abgeraten. Viele Themen die sich mit ServiceLoader befassen behandeln an sich eigentlich nur dieses eine Interna und wie mangelhaft es ist. Einige gehen sogar soweit und geben die Meinung, wenn man von Class.newInstance auf Constructor.newInstance umbaut und dann dies mit einer zusätzlichen Methode für die Übergabe der Parameter erweitert, ServiceLoader ganz brauchbar ist. Und ich muss zugeben mich mit dieser Ansicht auch ein wenig angefreundet zu haben.

Was mir noch einfällt wäre vielleicht noch folgender Kompromiss: ServiceLoader/SPI nur für das laden der “leeren” Factories zu nutzen (denn da sollte ja beim lazy loading mit leerem Konstruktor nichts passieren) und erst dann darüber die eigentlichen Instanzen zu erzeugen.
Wie seht ihr das?

Was genau daran ist denn schlecht? Laut Dokumentation ist der Vorteil von Constructor.newInstance lediglich, dass Exceptions gewrapped werden:
http://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#newInstance--

Note that this method propagates any exception thrown by the nullary constructor, including a checked exception. Use of this method effectively bypasses the compile-time exception checking that would otherwise be performed by the compiler. The Constructor.newInstance method avoids this problem by wrapping any exception thrown by the constructor in a (checked) InvocationTargetException.

Bei einem Konstruktor, der keine Exceptions wirft, macht das keinen Unterschied. Und dass die Konstruktoren keine Exceptions werfen dürfen, kannst du per Contract für Klassen, die dynamisch geladen werden sollen, vorgeben. Oder es zumindest auf unchecked Exceptions einschränken. Oder irgend eine andere Konvention erfinden.

Wieso ist dir das eingefallen? So ungefähr steht es doch schon in meinen Beiträgen.

Naja, offensichtlich so, dass ich es nicht so herausgelesen habe.

In meiner letzten Firma haben wir auch auf Reflection gesetzt. Letztendlich kommt nicht darum herum, wenn man den Namen der Klassen zur Compiletime nicht kennt. Es gibt ja keine Möglichkeit ein Interface zu definieren, welches die Existenz eines bestimmten Konstruktor vorschreibt. Wir haben damals folgenden Ansatz gewählt:

Alle Klassen, welche zur Laufzeit geladen werden mussten bekamen ein leeres Marker-Interface (ähnlich Serializable und RandomAccess). Da wir einen Konstruktor mit Parametern benötigten, aber sicherstellen wollten, dass nach einem Refactoring trotzdem noch alles funktioniert. Haben wir im Marker-Interface definiert, dass es ein static-field geben muss, welches die Factory für Objekte der Klasse enthält. Die Factory hatte wiederum eine Schnittstelle, welche die Factorymethod mit den immer gleichen Parametern enthielt. Neben dem Namen der Klasse muss man dann lediglich das Factory-Field per Reflection auslesen.

Möchte man auf Reflection zum Zugriff auf das Field verzichten, kann man einen static initializer verwenden und dort über eine Static-Method die Factory in einer globalen Registry zu registieren.

public class X implements IBase {

	static {
		Reflection.register(new IFactory<X>() {
			@Override
			public X createInstance(final String id) {
				return new X(id);
			}

			@Override
			public Class<X> getType() {
				return X.class;
			}
		});
	}

	private final String fId;

	public X(final String id) {
		fId = id;
	}

	@Override
	public String getId() {
		return fId;
	}
}

public class Reflection {

	private static Map<Class<?>, IFactory<?>> sFactory = new HashMap<>();

	public static void main(final String[] args) throws ClassNotFoundException {
		assertTrue(sFactory.isEmpty());
		final Class<?> clazz = Class.forName("test.X", true /* important */,
				new URLClassLoader(new URL[] { Class.class.getResource("..") }));
		assertTrue(!sFactory.isEmpty());
		assertTrue(clazz.equals(sFactory.keySet().iterator().next()));
		final IBase i1 = sFactory.get(clazz).createInstance("instance1");
		final IBase i2 = sFactory.get(clazz).createInstance("instance2");
		System.out.println(i1.getId());
		System.out.println(i2.getId());
	}

	private static void assertTrue(final boolean empty) {
		if (!empty) throw new AssertionError();
	}

	public static void register(final IFactory<X> factory) {
		sFactory.put(factory.getType(), factory);
	}

}

public interface IFactory<X extends IBase> {

	public X createInstance(String id);

	public Class<X> getType();

}

public interface IBase {

	public String getId();

}

Der Trick hier ist, dass Class.forName mittels mit dem argument true für den Parameter initialize angewissen wird, die Klasse zu initialisieren, wodurch der static-initialiser aktiviert wird. Somit wird bis auf die Verwendung des Class-Objects und des ClassLoaders praktisch auf Reflection verzichtet.

Na gut, das mit dem “I” als Kennzeichnung für ein Interface macht man in Java eigentlich nicht, aber jeder hat da so seine eigene Ansicht.
Grundsätzlich macht der Code da oben nicht viel mehr als man auch von java.sql.DriverManager kennt: JDBC4-Driver werden mit ServiceLoader geladen, und da dort ja intern Class.newInstance() gecallt wird laufen auch die static-Blöcke in denen dann nicht viel mehr steht als DriverManager.registerDriver(DriverClass). Ist ja auch ok denn die eigentliche “Konstruktion” passiert ja erst in Driver.getConnection(),
Denke mal dass ich es in meinem Fall ähnlich machen werde.

Danke erstmal an alle soweit - ich meld mich wenn ich ein Ergebnis hab.

– edit

Da ich gerade zu blöd bin den Button zu finden, das Thema allgemein auf “solved” zu setzen, missbrauch ich mal meinen eigenen Post. Ich komm mit der neuen Foren-Software noch nicht ganz zurecht.

Klar geht das, man muss nur ein Framework einsetzen welches Reflection um dieses Problem zu loesen, dann muss man selber nicht Reflection einsetzen :wink:

Grundsätzlich vermutlich gute Idee, stell mir beisowas immer nur die Gegenfrage: Brauche ich Framework X wenn ich aus diesem nur eine oder zwei bestimmte Funktionalitäten brauche?
Bleistift: Ich muss mir nicht unbedingt OSGi ans Bein binden wenn ich daraus lediglich den loader-Code brauche.

Wenn das Framework gut gewartet wird, entscheide ich mich tendenziell für „ja“.
Zum „lediglich den loader-Code“ - ich habe zwar noch nicht mit OSGi gearbeitet, aber ist der „loader-Code“ nicht die zentrale Komponente und die Idee hinter OSGi?

Meine Frage, was dir an der Verwendung von Class.newInstance nicht gefällt, hast du mir noch nicht beantwortet.

Wenn man mal and Spring und/oder Guice gewoehnt ist, macht man so schnell nix mehr ohne, vor allem keinen “low level Kram” wie Reflections, ausser man hat einen Guten Grund dafuer es nicht zu nehmen.

@cmrudolph
OSGi ist letztlich nur eine Spezifikation für verschiedene Implementierungen. Was die zentrale Idee hinter OSGi ist weiß ich auch nicht, aber es ist schon etwas mehr als “nur der loader-code” - und letztlich nur den brauche ich. Den ganzen Krams mit Abhängigkeitenverwaltung und was es da nicht alles gibt brauch ich einfach nicht, da muss ich mir halt kein 10MB Framework mit reinpacken wenn es auch 10 Zeilen Code tun.

bzgl. Class.newInstance hat @SlaterB in der ersten Antwort schon DEN Link gepostet warum man es nicht nutzen bzw. Constructor.newInstance vorziehen sollte.

@maki
Mag sein, ist für mein Vorhaben aber einfach zu overkill.

Letztlich hab ich noch mal intensiv Google bemüht - ich werde wohl das Prinzip vom java.sql.DriverManager in leicht abgewandelter Version übernemen.