Nach Aufbau des ApplicationContext weitere Bean-Definitionen dazuladen?

DarthShader

Erfahrenes Mitglied
Hallo,

ich erstelle in meiner Spring (2.5) Anwendung den Application Context folgendermaßen:

Java:
context = new FileSystemXmlApplicationContext( "context.xml" );

Innerhalb der "context.xml" wird das "<import />" Tag verwendet, um weitere Context-XML-Dateien zu laden/importieren.

Ich würde jetzt gerne, nachdem der ApplicationContext aufgebaut wurde, noch dynamisch Bean-Definitionen, also z.B. einfach noch eine weitere Context-XML-Datei, dazuladen. Hintergrund ist, dass ich eine Art von "Modulsystem" aufbauen will, mit dem ich dann verschiedene als Beans definierte Services lade, oder eben nicht lade.

Naiv gesprochen suche ich eine Art von Methode wie "context.addBeanDefinitions( "additional-beans.xml" )".

Hat jemand eine Idee, wie ich dies umsetzen könnte?


Vielen Dank für Eure Hilfe!
 
Das ApplicationContext interface unterstützt parent-child Beziehungen.
Damit kann man bei solchen Lookup Geschichten seeehr coole Sachen machen ;-)

Java:
package test;
/**
 * Dummy bean just to show the  parent-child relation
 * between ApplicationContext instances.
 */
public class SimpleBean {
	public void setParameter(int p) {
		m_param = p;
	}
	@Override
	public String toString() {
		return "" + m_param;
	}
	private int m_param;
}

Java:
package test;

import javax.management.InvalidApplicationException;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

/**
 * Illustrate the parent relationship between ApplicationContext
 * instances.
 */
public class Main {
	
	public static void main(String[] args) throws Exception {
		ApplicationContext parent = new FileSystemXmlApplicationContext( "conf/context.xml" );
		ApplicationContext child = new FileSystemXmlApplicationContext(
				new String[] {"conf/context2.xml" }, parent);

		SimpleBean b1 = (SimpleBean) parent.getBean("bean1");
		SimpleBean b2 = (SimpleBean) child.getBean("bean2");
		SimpleBean b3 = (SimpleBean) child.getBean("bean1");
		
		if(b1 != b3)
			throw new InvalidApplicationException("should be equal");
		if(b2==b1)
			throw new InvalidApplicationException("should not be equal");
		if(b2==b3)
			throw new InvalidApplicationException("should not be equal");
		
		System.out.println("b1: " + b1.toString());
		System.out.println("b2: " + b2.toString());
		System.out.println("b3: " + b3.toString());
	}
	
}

Und die beiden xml Spring configs:

conf/config.xml
Code:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-2.5.xsd
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
        
    <bean id="bean1" class="test.SimpleBean" p:parameter="81"/>
    
</beans>

conf/config2.xml
Code:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-2.5.xsd
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
        
    <bean id="bean2" class="test.SimpleBean" p:parameter="42" />
    
</beans>

Das stinkt förmlich nach einem Pattern, indes, ich kenne den Namen nicht. :D
Lass mich gerne aufklären.
 
Zuletzt bearbeitet von einem Moderator:
Hallo kabel2,

vielen Dank für Deine Antwort. Ja das sieht wirklich gut aus, ich werde es bei mir ausprobieren, aber ich denke mal, diese Parent-Child Funktionalität wird die Lösung für mich sein.

Ich hatte mich zunächst gefragt, wie der 2. AppContext aufgebaut werden kann, wenn er eine Abhängigkeit zum 1. AppContext hat (z.b. referenziert ein Property einer Bean aus dem 2. AppContext auf eine Bean aus dem 1. Context).
Wenn ich es richtig verstehe, funktioniert das aber, weil man den Parent-Context bereits im Constructor des Child-Contextes angibt.

Kann man eigentlich auch

Java:
SimpleBean bean = (SimpleBean)parent.getBean("bean2");

machen (also man holt die Bean mit Namen "bean2" aus dem Parent context, obwohl es im Child Context definiert wurde)? Ich vermute, es geht nur andersherum, also wenn eine Bean im Child nicht gefunden wird, wird der Parent durchsucht.
 
Zuletzt bearbeitet:
Ich hatte mich zunächst gefragt, wie der 2. AppContext aufgebaut werden kann, wenn er eine Abhängigkeit zum 1. AppContext hat (z.b. referenziert ein Property einer Bean aus dem 2. AppContext auf eine Bean aus dem 1. Context).
Wenn ich es richtig verstehe, funktioniert das aber, weil man den Parent-Context bereits im Constructor des Child-Contextes angibt.
Gute Frage. Es funktioniert definitiv (ich hab gerade das Beispiel entsprechend erweitert, kann das ja nochmal posten).
Ja, und das nur deswegen, weil der parent mitangegeben wird.
Schade, ich hätte erwartet, dass das lazy abgehandelt werden kann. Eventuell kann man da was über nen Proxy drehen.

Es scheint, dass Spring unbedingt die Konsistenz der Beans untereinander im deklarativen Teil garantieren will.

Kann man eigentlich auch

Java:
SimpleBean bean = (SimpleBean)parent.getBean("bean2");

machen (also man holt die Bean mit Namen "bean2" aus dem Parent context, obwohl es im Child Context definiert wurde)? Ich vermute, es geht nur andersherum, also wenn eine Bean im Child nicht gefunden wird, wird der Parent durchsucht.

Das geht natürlich nicht, der parent kennt die Kette nur weiter nach oben.
Eine interessante Spielerei wäre ein Kontext der multiple parents kennt.
Das wirft dann einige interessante Fragen auf, aber über so etwas könnte dann eine Extension einen bestehenden ApplicationContext erweitern.
Es könnte auch sein, dass sowas schon exisitiert.
 
Mit ApplicationContext Hierarchien nach dem Problem zu schlagen halte ich für etwas übertrieben. Um programmatisch weitere BeanDefinitionen hinzuzufügen benutzt man im allgemeinen einen BeanFactoryPostProcessor und implementiert die entsprechende Logik in der Callback methode die der anbeitet. Hier kann man z.B. bestehende BeanDefinitionen anschauen und dann je nach Gutdünken witere programatisch erzeugen oder aber auch bestehende entfernen.

Eine andere Möglichkeit ist, einen eigenen Namespace zu schreiben, hinter dem sich dann mehrere BeanDefinitionen verstecken. Näheres dazu hier: http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/apd.html

Wenn du ein Modulsystem ansprichst: es gibt von uns eine kleine Springerweiterung, die es möglich macht XML Konfiguration so zu gestalten, das "automatisch" bestimmte Beandefinitionen gefunden und verdrahtet werden. Das sieht zum Beispiel so aus:

XML:
<import resource="classpath*:foo/bar.xml" />

<bean class="...">
  <property name="plugins">
    <hera:list class="com.acme.Plugin" />
  </property>
</bean>

Der kritische Teil hier ist das <hera:list /> Element. Das sammelt alle BeanDefinitionen ein, deren Beanklasse com.acme.Plugin implementiert und injiziert die Liste der gefundenen Beans in die deklarierte Bean. Der gewildcardete Import oben sorgt dafür, dass alle Konfigurationsfiles mir gegebenem Namen gefunden werden, was ein einfaches "zusammenstecken" von Komponenten (z.B. durch das Deklarieren als Maven Dependency) ermöglicht. Alternativ dazu kann das Classpathscanning von Spring benutzt werden.

In einem Plugin (JAR, Projekt) würdest du jetzt einfach ein bar.xml in den Ordner foo legen, das folgendes enthält:

XML:
<bean class="com.acme.MyPluginImpl" />

Wenn MyPluginImpl nun PLugin implementiert, wird die bean automatisch gewired. Das ganze heißt Hera und ist unter http://hera.synyx.org zu finden.

Gruß
Ollie
 
Zuletzt bearbeitet von einem Moderator:
Wer schlägt hier wen? :)

Hmm, aber dafür muss eine eigene Klasse implementiert werden.
Und der PostProcessor müsste dann eigens aufgerufen werden?
Könnte man dann nicht gleich einfach alles direkt auf dem FileSystemXmlApplicationContext machen?
Oder versucht man dann, solche Änderungen im PostProcessor zu bündeln, quasi in Manier eines ChangeSets?
 
Hmm, aber dafür muss eine eigene Klasse implementiert werden.
Ja. Wenn du Hera benutzt, entfällt das. Im übrigend ist das genau der Mechanismus, den Spring Core dafür vorsieht (http://static.springsource.org/spri.../factory/config/BeanFactoryPostProcessor.html). Desweiteren löst zwar der Weg über die AppContext Hierarchien das hinzuladen von Beans, jedoch nicht das automatische Wiring, wenn man kein autowiring benutzt.

Und der PostProcessor müsste dann eigens aufgerufen werden?
Nein, Implementierungen, die dieses Interface implementieren werden automatisch beim Starten des ApplicationContext gerufen. Ein einfaches Deklarieren der PostProcessor implementierung reicht.

Könnte man dann nicht gleich einfach alles direkt auf dem FileSystemXmlApplicationContext machen?
Kann man. Was machst du allerdings in einer Webapplikation? Da hast du nur wenig Einfluss auf die ApplicationContext-Implementierung bzw. -Auswahl. Testbarkeit ist auch so eine Sache. Eine Klasse kannst du testen. Einem Client aber vorzugeben nicht nur in welcher Reihenfolge Konfigurationsdateien geladen werden sollen, sondern auch noch, welche Datei in welchen AppContext geladen werden muss, der dann parent von welchem ist? Das klingt nicht danach, als wäre das sehr robust.

Oder versucht man dann, solche Änderungen im PostProcessor zu bündeln, quasi in Manier eines ChangeSets?
Exakt das passiert in Hera. Allerdings wird hier der FactoryBean Mechanismus verwendet und einfach hübsch hinter einem Namespace versteckt. Im Normalfall

Gruß
Ollie
 
Hallo ihr beiden,

ich musste Eure Posts erstmal für mich gedanklich sortieren, und auch gut 2 oder 3mal darüber nachdenken. Aber ich denke die Erörterung nun nachvollziehen zu können.

Ich würde gerne jetzt nochmal mein Ziel definieren, damit klar wird, was ich eigentlich vor habe und wir ggf. die Diskussion dahin lenken können:

Ich habe einen Server, der über eine simple "klassische" Konfigurationsdatei konfiguriert wird (quasi wie die Apache Konfiguration). Dies ist für mich notwendig, da von dem Server pro Kunde eine Instanz läuft, die jeweils anders konfiguriert sind. Diese verschiedenen Einstellungen immer direkt in dem Context-XML zu machen, ist nicht praktikabel, es ist daher viel einfacher, eine übersichtliche Konfigurationsdatei zu haben.

Damit diese Konfigurationswerte in die Context-Konfiguration gelangen, habe ich eine eigene von "PropertyPlaceholderConfigurer" abgeleitete Klasse geschrieben, die sowas möglich macht.


Das zur "Vorgeschichte". Mein Ziel ist es nun, eine Art von Modulsystem aufzubauen, damit ich in der Konfiguration z.B. sagen kann "enable-webservice". Ist das angegeben, so würde er mir die für den Webservice notwendigen Beans laden, die ich wiederum in einer eigenen XML Datei (z.B. module-webservice-context.xml) definiert habe.

Das bedeutet für mich, dass ich programmatisch (ich prüfe im Code, quasi zur Bootstrap Zeit) die Bean Definitionen aus "module-webservice-context.xml" dazuladen will, wenn das property "enable-webservice" in meiner Konfiguration (die nichts mit Spring zu tun hat, und auch kein XML ist) angegeben wurde bzw. "true" ist.

Mit ApplicationContext Hierarchien nach dem Problem zu schlagen halte ich für etwas übertrieben. Um programmatisch weitere BeanDefinitionen hinzuzufügen benutzt man im allgemeinen einen BeanFactoryPostProcessor und implementiert die entsprechende Logik in der Callback methode die der anbeitet. Hier kann man z.B. bestehende BeanDefinitionen anschauen und dann je nach Gutdünken witere programatisch erzeugen oder aber auch bestehende entfernen.

Nur, was genau mache ich denn in der Callback Methode eines PostProcessors? Ich will ja nicht programmatisch manuell Beans erzeugen und irgendwo injizieren o.Ä., ich müsste an der Stelle eine weitere Context-XML Datei laden und diese sozusagen "hinzufügen", das ist es ja, was ich brauche.

Die Ansätze mit dem Component Scanning, Deinem Framework Hera etc. klingen alle sehr gut. Aber ich bring das irgendwie nicht mit meinem Vorhaben zusammen, aufgrund einer einfachen boolean-Variable in meinem Server-Code zu entscheiden, ob weitere in XML definierte Beans nun hinzugeladen werden oder nicht (dies muss ja nicht nachträglich irgendwann passieren, sondern kann schon zur Zeit passieren, wenn der Context geladen wird).

Wie gesagt, hätte ich nicht so viele Instanzen mit verschiedenen Konfigurationen, würde ich das alles direkt in den XML machen, und alle anderen angesprochenen Mechanismen verwenden. Aber ich möchte schon weitere meine zentrale, einfache Konfigurationsdatei verwenden.


Über weitere Ideen würde ich mich sehr freuen


Vielen Dank!
 
Zum Thema mergen von Bean-"Pools" habe ich wenig bis gar nichts gefunden.

Hier wird beschrieben, wie zu instanziierende Klassen mittels Properties ausgetauscht werden können.
Passt hier aber nicht ganz.

Interessantiert ist schon das hier. Dazu wirst du allerdings erstmal bisschen tippen müssen. Danach kannst in der Config folgendes ausgedrückt werden:
XML:
<condbean:cond test="${developmentmode}">
  <bean id="industryDAO" class="robertmaldon.app.dao.HibernateIndustryDAO">
      <property name="sessionFactory" ref="sessionFactory"/>
  </bean>
</condbean:cond>

Nächster Versuch:
Java:
package test;

import org.springframework.context.support.AbstractXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class Main {

	public static class BeanException extends Exception {
		private static final long serialVersionUID = 4547470448250921179L;
		public BeanException(String string) {
		}
	}
	
	/**
	 * Demo the merging of bean pools
	 */
	public static void main(String[] args) throws Exception {
		AbstractXmlApplicationContext ac = new FileSystemXmlApplicationContext("conf/context1.xml");
		
		if(false == ac.containsBean("hiho"))
			throw new BeanException("should not be null");
		if(true == ac.containsBean("hiho2"))
			throw new BeanException("should be null");

		ac.setConfigLocations(new String[] { "conf/context1.xml", "conf/context2.xml" });
		ac.refresh();
		
		if(false == ac.containsBean("hiho"))
			throw new BeanException("should not be null");
		if(false == ac.containsBean("hiho2"))
			throw new BeanException("should not be null");
	}
	
}

conf/context1.xml
XML:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
       
       <bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"/>
       
    <bean id="hiho" class="test.SimpleBean" p:parameter="42"/>
    
</beans>

conf/context2.xml
XML:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
       
       <bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"/>
       
    <bean id="hiho2" class="test.SimpleBean" p:parameter="81"/>
    
</beans>

Das äußerst dumme daran ist, dass die Methoden zum Holen der aktuell benutzten ConfigLocations protected, und damit nicht zugreifbar sind.

OK, (nach einigem rumprobieren) das hier sollte das sein was Du willst:
Java:
package test;

import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.support.AbstractXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.core.io.FileSystemResource;

public class Main {

	public static class BeanException extends Exception {
		private static final long serialVersionUID = 4547470448250921179L;
		public BeanException(String string) {
		}
	}
	
	/**
	 * Demo the merging of bean pools
	 */
	public static void main(String[] args) throws Exception {
		AbstractXmlApplicationContext ac = new FileSystemXmlApplicationContext("conf/context1.xml");
		
		if(false == ac.containsBean("hiho"))
			throw new BeanException("should not be null");
		if(true == ac.containsBean("hiho2"))
			throw new BeanException("should be null");
		
		BeanDefinitionRegistry bdRegistry = (BeanDefinitionRegistry) ac.getBeanFactory();
		BeanDefinitionReader bdReader = new XmlBeanDefinitionReader(bdRegistry);
		bdReader.loadBeanDefinitions(new FileSystemResource("conf/context2.xml"));
		
		if(false == ac.containsBean("hiho"))
			throw new BeanException("should not be null");
		if(false == ac.containsBean("hiho2"))
			throw new BeanException("should not be null");
	}
	
}

xml configs sind diesselben wie oben.
Das funktiniert allerdings nur, weil die BeanFactory eine DefaultListableBeanFactory ist, die BeanDefinitionRegistry implementiert.
ConfigurableListableBeanFactory (Rückgabetyp von #getBeanFactory()) tut dies nicht!
 
Zuletzt bearbeitet von einem Moderator:
Hallo kabel2,

das ist ja klasse, vielen Dank für diese ausführliche Antwort. Ich werde mir ebenfalls die Links, die Du angegeben hast, durchlesen (allerdings erst morgen ;) ).

Auf den letzten Ansatz, den Du beschrieben hast, bin ich vorhin ebenfalls gekommen, Du bist aber schneller gewesen ;) Allerdings ist der Hinweis mit der "BeanDefinitionRegistry" gut, das hatte ich noch nicht beachtet.

Nun gut, auf jeden Fall scheint das ein brauchbarer Ansatz zu sein. Evtl. kann man die besagten Nachteile durch eine Ableitung einer entsprechenden Application Context Klasse auch beseitigen.

Generell ist mein Problem aber soweit gelöst denke ich, ich danke Euch!
 

Neue Beiträge

Zurück