Spring Framework: ApplicationContext als Singleton?

DarthShader

Erfahrenes Mitglied
Hallo,

ich hätte eine Frage zum Spring Framework, speziell zur Verwendung eines ApplicationContext. Da ich noch sehr neu in der Spring-Thematik bin, bitte ich meine eventuell "doofen" Fragen zu entschuldigen ;-)

Es geht mir darum, ein ApplicationContext zu haben, der global im gesamten Programm verfügbar ist. Er soll sich also quasi wie ein Singleton verhalten, sodass ich überall auf ihn zugreifen kann. Nun könnte ich ja eine Klasse erstellen, die den Spring ApplicationContext so kapselt und über statische Methoden verfügbar macht. Aber da habe ich eher ein "schlechtes Gewissen" und frage mich, ob es mit Spring selbst nicht eine bessere Möglichkeit gäbe? Vielleicht bietet Spring ja selbst eine Art von Singleton für den ApplicationContext an?

Vielen Dank für Eure Hilfe!
 
Hehe, die Frage gehört in die "Most frequent mistakes mit Spring". Ein AC ist nie Singleton. Schon allein um genau diese Art des Zugriffs zu verhindern. Spring ist u.A. auch angetreten um Dependencies durch Factorymethoden ala
Java:
Factory.getInstance();
zu verhindern. Diese sorgen für faktische Untestbarkeit der Klasse, in der dieser Code steht, da du die Implementierung nicht ohne weiteres austauschen kannst.

Dröseln wir es doch mal von unten auf. Du benötigst eine Dependency zum ApplicationContext.

1. Dependencies zu technischen Klassen sollten wohl überlegt sein. Warum brauchst du die, was willst du mit dem AC anstellen? Bietet Spring für das was du anstellen willst, evtl einen anderen (besseren) Weg?

Eine Dependency zu einer Frameworkklasse macht deine Klasse eigentlich auch zu einem Teil des Frameworks. Zumindest zu einer Klasse, die mit geschäftlicher Logik nicht viel zu tun haben sollte. Die Frage ist dabei dann nämlich: Wie kannst du die Klasse noch (möglichst ohne das Framework selbst) testen?

Oft sind Entwickler der Meinung Komponenten selbst aus dem AC auslesen zu müssen. getBean sollte möglichst gar nicht gerufen werden. Dafür gibt es DependencyInjection.

2. Wenn du beschlossen hast, dass eine Dependency zum AC unausweichlich ist (weil du beispielsweise eine BeanPostProcessor implementieren willst und dafür weitere Infos aus dem AC benötigst) ist ApplicationContextAware das Interface deiner Wahl. Wenn du dies implementierst, ruft Spring nach dem Instantiieren deiner Komponente diese Methode und übergibt genau den Context der die Komponente beherbergt (evtl. wichtig wenn du mit Contexthierarchien arbeitest).

Hier sieht man schön, wie Spring intern selbst nach den JavaBean Konventionen arbeitet. Dependencies drückt eine Komponente durch Setter aus. Ergo besteht ApplicationContextAware auch nur aus aus setApplicationContext().

Das ist jetzt eher grundsätzlich, spiegelt aber auch grundsätzlich die Arbeit mit Spring wieder. Man wird "genötigt" sich über Dependencies Gedanken zu machen und kommt auf die Art und Weise zu besserem und testbareren Code. Was du durch das Implementieren des Interfaces nun tun kannst, ist, deine Klasse per JUnit mit einem Mock für den ApplicationContext zu testen - genau das geht mit ner Factorymethode nicht.

Das sollte dir jetzt schon ein paar Anhaltspunkte für eine Entscheidung geben. Was hast du denn genau vor?

REINHAUN!
 
Hallo Oliver,

ok vorab - vielen vielen Dank für diese ausführliche Antwort, damit habe ich ehrlich gesagt nicht gerechnet :)

Jetzt möchte ich auch gerne auf Deinen Text eingehen:

Hehe, die Frage gehört in die "Most frequent mistakes mit Spring". Ein AC ist nie Singleton. Schon allein um genau diese Art des Zugriffs zu verhindern. Spring ist u.A. auch angetreten um Dependencies durch Factorymethoden
(...)
zu verhindern. Diese sorgen für faktische Untestbarkeit der Klasse, in der dieser Code steht, da du die Implementierung nicht ohne weiteres austauschen kannst.

Gut, das sehe ich ein. Ich habe mir schon gedacht, dass diese Frage ziemlich vielen im Kopf herum geht, die Spring noch nicht verstanden haben. Aber ich denke, ich habe das Konzept mitlerweile verstanden, warum ich diese Singleton Sache trotzdem haben wollte, erkläre ich weiter unten.


Dröseln wir es doch mal von unten auf. Du benötigst eine Dependency zum ApplicationContext.

Dachte ich jedenfalls... wie gesagt weiter unten schreibe ich Dir, worum es mir geht.

1. Dependencies zu technischen Klassen sollten wohl überlegt sein. Warum brauchst du die, was willst du mit dem AC anstellen? Bietet Spring für das was du anstellen willst, evtl einen anderen (besseren) Weg?

Ich hoffe sehr, dass Du mir das gleich sagen kannst :)

Eine Dependency zu einer Frameworkklasse macht deine Klasse eigentlich auch zu einem Teil des Frameworks. Zumindest zu einer Klasse, die mit geschäftlicher Logik nicht viel zu tun haben sollte. Die Frage ist dabei dann nämlich: Wie kannst du die Klasse noch (möglichst ohne das Framework selbst) testen?

Ah, gute Gedanken - nein ich möchte meine Klasse eigentlich nur sehr ungern von Spring abhängig machen.

Oft sind Entwickler der Meinung Komponenten selbst aus dem AC auslesen zu müssen. getBean sollte möglichst gar nicht gerufen werden. Dafür gibt es DependencyInjection.

Ok - verstehe ich.


2. Wenn du beschlossen hast, dass eine Dependency zum AC unausweichlich ist (weil du beispielsweise eine BeanPostProcessor implementieren willst und dafür weitere Infos aus dem AC benötigst) ist ApplicationContextAware das Interface deiner Wahl.

Danke für diesen Hinweis, aber ich glaube in meinem Fall ist das nichts.

Das ist jetzt eher grundsätzlich, spiegelt aber auch grundsätzlich die Arbeit mit Spring wieder. Man wird "genötigt" sich über Dependencies Gedanken zu machen und kommt auf die Art und Weise zu besserem und testbareren Code.

Ja, da möchte ich auch hin :)

Das sollte dir jetzt schon ein paar Anhaltspunkte für eine Entscheidung geben. Was hast du denn genau vor?

Gut, dann werde ich mal eben eklären, vor welchem Problem ich stehe:

Ausgangssituation:

In meinem Projekt gibt es eine große Anzahl an Eingabemasken. Diese möchte ich nicht von Hand erstellen (den Swing Code schreiben), sondern mit einem GUI-Builder-Tool (z.B. Jogloo Eclipse Plugin der JFormDesigner, was auch immer...) erstellen. Erstmal aus Geschwindigkeitsgründen (man ist mit dem Tool immer schneller, als das ganze Layout Management von Hand zu machen), und dann auch noch, weil sogar jemand, der sich nicht mit Java auskennt, das Tool benutzen kann, um die Masken zu bauen (quasi "Outsourcing" ;).

Wie auch immer, der GUI Code wird generiert und ich habe eine Art von UIAdapter, der über Reflection auf die Komponenten zugreifen kann. Ein wie ich finde schöner ansatz, um Layout und Code voneinander zu trennen (noch stärker als das "normale" MVC).

Nun ist es so, dass ich auch noch neue Oberflächen-Komponenten habe - eigene Klassen, die ihren Client-Bereich selbst zeichnen. Darunter zeichnen sie einige Bilder, die als png Dateien vorliegen. Diese Bilder - Resourcen - können durch eine Klasse "ResourceManager" geladen werden. Diese Komponenten sind also Abhängig von dem ResourceLoader. Da haben wir ja schon einen Anwendungsfall für Spring - denn es könnte den Komponentenklassen den ResourceManagern injizieren. Ich hoffe ich habe Spring bis hierhin verstanden (bzw. allg. "Dep. Injection"?)

Das Problem:

Jetzt kommen wir zum Kern der Sache: möchte ich eine Maske mit dem Tool editieren, so instanziert das Tool meine Klasse (meist ein einfaches JPanel) sowie alle darin enthaltenen Komponenten (JButton, JTextField, was auch immer) sowie eben auch meine eigene Komponente (die den ResourceManager braucht).

Theoretisch müsste das Tool vorher einen ApplicationContext erzeugen, Spring erstellt also die GUI-Komponenten, injiziert ihnen den ResourceManager (der zuvor auch von Spring instanziert wurde) und so können die Komponenten auch Zugriff auf die Bilder-Resourcen erhalten.

Aber das wird so nichts - denn der Editor hat keine Ahnung von einem AppContext, er erzeugt die GUI-Komponenten selbst und ich möchte eigentlich keinen Einfluss auf den generierten Code haben. Deshalb dachte ich mir, dass jede meiner eigenen GUI-Komponenten sich den ApplicationContext holt, damit sie dann schließlich Zugriff auf den ResourceManager hat.

Wenn das Programm selbst abläuft, sodass ich vorher einen ApplicationContext erstellen kann, meine GUI-Komponenten Beans sind, diese eine Abhängigkeit vom ResourceManager haben, dann wäre das alles kein Problem (denke ich). Aber in dieser Situation weiß ich einfach keine andere Lösung, als dass ich einen globalen Zugriff auf den ApplicationContext aus einer GUI-Komponenten heraus habe, um darüber dann auf meinen ResourceManager zugreifen zu können.


So, nun ist die Erklärung doch schon länger geworden - ich hoffe sehr, dass alles verständlich ist. Vielleicht hast Du eine Idee dazu? Kann man es mit Spring-Boardmitteln lösen? Oder brauche ich doch nen Singleton, auf den ich Zugriff aus der GUI-Komponente nehme?


Vielen Dank für Deine weitere Hilfe!
 
Wenn das Programm selbst abläuft, sodass ich vorher einen ApplicationContext erstellen kann, meine GUI-Komponenten Beans sind, diese eine Abhängigkeit vom ResourceManager haben, dann wäre das alles kein Problem (denke ich).

Warum machst dus nicht genau so? Also deine GUI Komponenten so bauen, dass du sie durch Spring konfigurieren kannst? Damit ziehst du auf jeden Fall Dependencies grade und bist den ganzen Konfigurationscode los. (und hast natürlich mehr XML ;) )

Vielleicht noch ein paar Fragen zum Versändnis:

Brauchst du den ResourceManager wirklich? Kannst du nicht einfach die Resourcen direkt in die Komponenten injecten?

Jetzt kommen wir zum Kern der Sache: möchte ich eine Maske mit dem Tool editieren,
dem Tool? was für ein Tool?

Wenn ich mir das so durchlese scheint das eher ein grundsätzliches Problem zu sein, wie du eine Swing Anwendung mit Spring verheiratest. Im Allgemeinen geht man da genau so vor, wie es in dem Quote von dir schon angedeutet wurde. Sämtliche GUI Komponenten werden per Spring konfiguriert (vielleicht nicht jede TextBox), zumindest werden halt Services, die die Anwendungslogik enthalten, in die GUI injiziert, so dass die Trennung zwischen UI und Geschäftslogik per DI gelöst wird. Gestartet wird die Anwendung dann durch ein einfaches
Java:
new ClassPathXmlApplicationContext("fobar.xml");
, was dafür sorgt, dass alle GUI Komponenten instanziiert werden und dein JFrame erscheint.

Das macht das ganze viel sauberer, da du auch keine Singletons mehr von Hand implementieren musst und dieser ganze

Java:
if (null = this.component)
  this.component = new Foobar();

return this.component;
wegfällt, den man so kennt...

REINHAUN!
 
Hallo Oliver,

Warum machst dus nicht genau so? Also deine GUI Komponenten so bauen, dass du sie durch Spring konfigurieren kannst? Damit ziehst du auf jeden Fall Dependencies grade und bist den ganzen Konfigurationscode los. (und hast natürlich mehr XML ;) )

Vielleicht noch ein paar Fragen zum Versändnis:

Brauchst du den ResourceManager wirklich? Kannst du nicht einfach die Resourcen direkt in die Komponenten injecten?

Ich würde es sofort so machen, wenn ich nicht das GUI-Tool zum Erstellen der Masken verwenden wollte. Ich erklärte ja schon, dass das Tool natürlich nichts von einem AppContext weiß, d.h. es wird vorher auch kein "new ClassPathXmlApplicationContext("fobar.xml");" geben, nichts kann injiziert werden.

Es dreht sich dabei nur um die Design-Zeit, wenn man das Tool verwendet um die GUIs zu bauen. Wenn das Program läuft, kann ich natürlich einen ApplicationContext verwenden.


dem Tool? was für ein Tool?

Irgendein GUI-Builder-Tool, z.B. Jigloo Eclipse Plugin, JFormDesigner, Netbeans Form Editor etc... egal was, ein Tool eben, mit dem man eine Maske "zusammenklicken" kann, der dann den GUI Code generiert und natürlich nichts von Spring oder ähnliches weiß.

Beispiel: http://www.cloudgarden.com/jigloo/images/screenshot5.PNG


Wenn ich mir das so durchlese scheint das eher ein grundsätzliches Problem zu sein, wie du eine Swing Anwendung mit Spring verheiratest. Im Allgemeinen geht man da genau so vor, wie es in dem Quote von dir schon angedeutet wurde. Sämtliche GUI Komponenten werden per Spring konfiguriert (vielleicht nicht jede TextBox), zumindest werden halt Services, die die Anwendungslogik enthalten, in die GUI injiziert, so dass die Trennung zwischen UI und Geschäftslogik per DI gelöst wird. Gestartet wird die Anwendung dann durch ein einfaches
Java:
new ClassPathXmlApplicationContext("fobar.xml");
, was dafür sorgt, dass alle GUI Komponenten instanziiert werden und dein JFrame erscheint.

Das macht das ganze viel sauberer, da du auch keine Singletons mehr von Hand implementieren musst und dieser ganze

Hm ja, das verstehe ich auch alles. Aber wie gesagt, ich habe die Situation, in der ich die Masken mit dem GUI Tool bauen möchte. Auf einer Maske habe ich eine selbst geschriebene GUI-Komponente (z.B. ein Button der anders aussieht). Und diese Komponente lädt Resourcen, braucht also den ResourceManager. Ich habe während der Verwendung des Tools also keine Chance, zuvor einen ApplicationContext zu erstellen, der mir alles nötige injiziert. Deshalb dachte ich, ich könnte global aus der GUI-Komponente heraus auf einen ApplicationContext zugreifen/ihn erstellen, damit er mir den ResourceManager injiziert.

Ich glaube langsam, dass meine Problemstellung nicht mit Spring so straightforward zu lösen ist. Ich bin aber auch recht überzeugt davon, dass meine Vorgehensweise keine Schlechte ist - also das GUI Tool zu verwenden. Ich spare in meinem Fall damit bis zu 80% Entwicklungszeit (auf die GUI selbst bezogen) ein, das ist schon enorm.

Vielleicht muss ich davon abstand nehmen, die GUI-Komponenten als Spring Beans zu verstehen, vielleicht muss ich meinen ResourceManager wirklich als Plain-Old-Singleton (SOS ;) ) realisieren. Ich kann mir anders einfach nicht vorstellen, wie die GUI-Komponenten sonst den Zugriff auf den ResourceManager erhalten (natürlich immer in Hinsicht auf die Verwendung des GUI-Tools).


Oder hast Du vielleicht noch eine Idee? :)
 
Hm... dann vielleicht so einen Workaround, dass du den AC an zentraler Stelle im JFrame oder so instanziierst und dir dort, und nur dort Beans ausliest, und sie an die entsprechenden Komponenten weitergibst. Das ist zwar nicht schön, wenn solcherlei Hacks auf eine zentrale Stelle im Code (wegen mir auch eine dedizierte Klasse als Singleton) beschränkst, ist das meiner Meinung nach okay.

Somit wär also folgendes möglich:

Du kannst deine Komponenten mit dem Matisse or whatever zusammenklicken. Diese deklarierst du dann als Springbean und injectest den ResourceManager (quasi einen Service für die Geschäftslogik).

Die Komponenten, die dann diese speziellen Komponenten beinhalten kannst du dann auch mit dem GUI Tool zusammenklicken, lässt allerdings die Stellen offen, in die deine speziellen Komponenten reinsollen.

Dann brauchts noch etwas Gluecode (AC instanziieren, Beans auslesen und damit die Lücken füllen) und es sollte tun...

Ich glaub das Problem kannst du auf eben genannte Weise lösen, wenn du sinnvolle Klassenaufteilungen verwendest. Du musst halt schauen, dass du mit dem GUI Editor die Komponentengranularität so wählst, dass du die an sinnvollen Stellen auftrennen kannst. Wenn du den kompletten JFrame in einer Klasse runtercodest wird das natürlich nix. Packst du Panes in eine extra Klasse, die du mir Spring mit Services verbinden willst, sollte das eigentlich recht sauber funktionieren.

Zur ganz großen Not, machst du halt nur den ResourceManager als Springbean, instantiierst den AC, liest die Bean aus und setzt die selbst in den Komponenten. Allerdings wird dann Spring fast überflüssig (vorrausgesetzt du nutzt nicht noch andere Features ;) ).

REINHAUN!
 
Hallo,

hm ja, danke für Deine Gedanken :) Ich bin schonmal froh, dass ich nicht ganz so falsch lag mit meinen.

Ich glaube, die einfachste Möglichkeit ist momentan, zu akzeptieren, dass ich das alte Singleton Pattern für den Resourcemanager brauche. Der ResourceManager ist auch die einzige Abhängigkeit für die GUI-Komponenten (ich hoffe, das bleibt auch so).

Ich denke, es gibt auch keine geeignetere Möglichkeit, vor allem nicht mit der normalen Dep. Injection. Denn man muss bedenken - der GUI Code ist z.b. ein JFrame, welcher seine eigenen Komponenten selbst instanziert. Selbst wenn ich den RM zum JFrame injizieren lasse, ist der RM noch lange nicht bei den Komponenten injiziert, ohne dass ich den generierten Code anfassen muss.

Ich habe aber noch eine andere "Lösung" für die Sache. Ich habe einen "UserInterfaceAdapter", quasi einen Manager, der per Reflection den GUI Code analysiert und zu allen GUI-Komponenten actions/listeners hinzufügen kann. Ich könnte von Spring den RM einfach dem UserInterfaceAdapter injizieren lassen. Ab da delegiert der UserInterfaceAdapter den RM weiter an die Komponenten. Das ist jedoch auch keine Lösung dafür, dass die einzelnen JFrames autark funktioneren müssen, da das GUI-Tool die Komponenten natürlich einfach selbst instanziert beim Bearbeiten der GUI. In dem Fall lass ich einfach zu, dass jede Komponente ihren eigenen ResourceManager baut. Das hebelt natürlich den Sinn des ResourceManager aus, aber ist während der Bearbeitung einer GUI nicht so wichtig (hauptsache der RM funktioniert dann als Cache für Resourcen während die Anwendung "richtig" läuft.)


Ich möchte mich noch für Deine Hilfe bedanken. Es ist klasse, wenn sich jemand solche Mühe gibt, anderen weiter zu helfen, anstatt nur nen klugen Satz in den Thread zu werfen :)
 
Tom holt gleich wieder den großen Hammer ;). Aber die Idee ist nicht schlecht. Der Eclipsekram ist für DarthShader wahrscheinlich gar nicht so interessant, der Kern allerdings schon.

Man mit @Configurable an einer Klasse und AspectJ LoadTimeWeaving dafür sorgen, dass Beans Services injiziert bekommen, sobald sie mit new Instantiiert werden. Das könnte dich einen guten Schritt weiter bringen. Angenommen deine custom Komponenten erben alle von einer Basisklasse, dann könntest du in der den Setter für den ResourceManager unterbringen und die Klasse wie in Toms Beispiel deklarieren und annotieren.

(Nur, um Toms Tutorial auf das für Darth wichtige zusammen zu fassen ;) )

REINHAUN!
 
Ja, das klingt wirklich nach einer Lösung, mir fällt jedenfalls noch kein Nachteil bzw. Problem von dieser Sache in bezug auf mein Thema ein.

Ich muss zugeben, dass ich den Thread von Thomas noch nicht gelesen habe, aus Zeitgründen. Das hole ich aber noch nach (jetzt erstmal bissl feiern, noch 5 1/2 Stunden bis 2008 und ich sitz hier schon wieder und schreib was ins Forum ;) ).

Kurze Frage, auch auf die Gefahr hin, dass es in dem Thread den ich noch nicht gelesen habe, beantwortet wird:

Dieses Load Time Weaving wird ja durch AspectJ realisiert. Benötigt das Ding nicht einen eigenen Kompiler/JVM? Falls ja, das wäre schlecht, denn mein Programm sollte auf vielen verschiedenen JVMs laufen können (es soll evtl. einmal auf einem PDA laufen), nicht nur auf der standard VM für PCs von Sun.
 

Neue Beiträge

Zurück