Blog

Spring 2.x schema-basierte Konfiguration und der PropertyPlaceholderConfigurer

Vincent Partington

Aktualisiert Oktober 23, 2025
4 Minuten

Spring 2.0 und spätere Versionen bieten eine schemabasierte Konfiguration, die es uns ermöglicht, unsere Konfiguration aussagekräftiger und präziser zu spezifizieren. Die Konfiguration von Spring Security 1.0 war zum Beispiel recht umständlich, wurde aber mit Spring 2.0 wesentlich vereinfacht. Ein Nachteil ist, dass die Dokumentation der Namespaces nicht so gut ist wie die für reguläre Beans. Für reguläre Beans kann ich einen Blick in die Javadoc werfen, während die Dokumentation für die Namespaces, die mit Spring geliefert werden, nicht den gleichen Detailgrad aufweist. Bis ein Javadoc-ähnliches Tool diese Dokumentation aus den Schemas generieren kann, sind die Schemas selbst die beste Dokumentation.

Ein kleines Problem

Aber abgesehen davon bin ich vor kurzem auf ein sehr praktisches Problem bei der Verwendung der schemabasierten Konfiguration in Kombination mit einem PropertyPlaceholderConfigurer gestoßen.

Der Kontext meiner Anwendung enthielt einen PropertyPlaceholderConfigurer, damit er für verschiedene Umgebungen konfiguriert werden konnte. Natürlich funktionierte das für alle meine Beans wie am Schnürchen. Es funktionierte auch für meine Spring Security-Konfiguration, z.B. für die Einstellungen für den LDAP-Server: <security:ldap-server id="adLdap" url="ldap://${ldap.host}:${ldap.port}/${ldap.base.dn}" manager-dn="${ldap.connect.dn}" manager-password="${ldap.connect.password}" /> Aber es funktionierte nicht mehr, als ich auch die Rollen, die die Anwendung verwendet, so konfigurierbar machen wollte: <security:intercept-url pattern="/remoting/**" access="${security.group.authenticated.name}" /> Jetzt habe ich diese Ausnahme vom Frühlingscontainer bekommen, als er meine Bohnen verkabelt hat: java.lang.IllegalArgumentException: Unsupported configuration attributes: ${security.group.authenticated.name} Offenbar wurde dieser Wert nicht ersetzt!

Analyse

Es stellte sich heraus, dass auch andere Leute dieses Problem hatten und dass sogar einige Bugs darüber berichtet wurden. So verstehe ich das Problem. Wenn eine XML-Datei von einem XmlBeanDefinitionReader gelesen wird, werden alle XML-Elemente, die nicht zum Standard-Bean-Namespace gehören, an einen NamespaceHandler übergeben, der dann eine Bean-Definition erstellt, die diesen XML-Elementen entspricht. Und danach werden alle Bean-Definitionen, die erstellt wurden (sowohl durch das Standard-Bean-Parsing als auch durch die NamespaceHandler), von allen vorhandenen BeanFactoryPostProcessors verarbeitet. Wie z.B. der PropertyPlaceholderConfigurer. Aber nicht alle Namespace-Handler machen das so. In dem Fall, auf den ich gestoßen bin, stellte sich heraus, dass der SecurityNamespaceHandler den HttpSecurityBeanDefinitionParser registriert, um das Element http zu behandeln. Und in dieser Klasse werden die Zugriffskonfigurationen nicht in einer BeanDefinition gespeichert, sondern direkt in einem RequestKey-Objekt abgelegt. Dadurch hat der PropertyPlaceholderConfigurer keine Möglichkeit, den Wert zu ändern! Nachdem ich das Problem entdeckt hatte, implementierte ich eine Lösung für das Problem. Anstatt die Beans nachzubearbeiten, verarbeitet meine Lösung die XML-Datei vor. Dies hat den zusätzlichen Vorteil, dass es für jeden XML-Attributwert funktioniert, nicht nur für EigenschaftswerteEs gibt einige verschiedene Möglichkeiten, meine Lösung zu verwenden, die ich weiter unten aufführe. Eine weitere gute Quelle für Informationen zur Verwendung dieser Klassen sind die Testfälle im Verzeichnis src/test/java in der ZIP-Datei.

PropertyPlaceholderReplacingXmlBeanFactory

Am einfachsten ist die Verwendung dort, wo Sie normalerweise eine XmlBeanFactory verwenden würden. Stattdessen verwenden Sie jetzt die com.xebia.springframework.beans.factory.xml.PropertyPlaceholderReplacingXmlBeanFactory wie folgt: Resource context1resource = new ClassPathResource("beans_with_property_placeholders.xml"); Resource props1resource = new ClassPathResource("beanvalues.properties"); Properties props1 = new Properties(); props1.load(props1resource.getInputStream()); BeanFactory factory = new PropertyPlaceholderReplacingXmlBeanFactory(context1resource, props1); Dies hat den Effekt, dass die Datei beans_with_property_placeholders.xml geladen wird, wie es die reguläre XmlBeanFactory tun würde. Nach dem XML-Parsing und bevor das XML nach Bean-Definitionen usw. durchsucht wird, werden jedoch alle Attributwerte (nicht nur die Eigenschaftswerte) geparst, um festzustellen, ob sie Eigenschaftsplatzhalter(${...}) enthalten. Ist dies der Fall, werden sie durch die in der Datei beanvalues.properties gefundenen Werte ersetzt. Hinweis zur Implementierung: Anders als die Implementierung der Ersetzung von Eigenschaftsplatzhaltern von Spring selbst, prüft diese Implementierung nicht auf unendliche Rekursion. Das bleibt dem Benutzer überlassen. ;-)

<xbeans:import-with-property-placeholder-replacement>

Meistens haben Sie jedoch weniger Kontrolle über die Factory, die zum Laden Ihrer Beans verwendet wird. Wenn Sie eine Webanwendung erstellen, verwenden Sie höchstwahrscheinlich den XmlWebApplicationContext oder etwas Ähnliches. Es ist zwar durchaus möglich, einen PropertyPlaceholderReplacingXmlWebApplicationContext zu erstellen, der den com.xebia.springframework.beans.factory.xml.PropertyPlaceholderReplacingXmlBeanDefinitionReader anstelle des Standardreaders verwendet (tun Sie das ruhig :-) ), aber ich dachte, dass eine wahrscheinlichere Anwendung darin besteht, dass ein generischer Kontext eine andere Kontextdatei importiert und die zu ersetzenden Werte beim Importieren angibt. Zu diesem Zweck habe ich das Tag import-with-property-placeholder-replacement geschrieben und in den neuen Namespace www.xebia.com/schema/xbeans eingefügt . In meinem Fall würde der Standardkontext XML die Sicherheitseinstellungen von Spring importieren und dabei die Werte der Zugriffsattribute durch die richtigen Werte ersetzen: <xbeans:import-with-property-placeholder-replacement resource="security-context.xml" properties="security.properties">

PropertyPlaceholderReplacingContextLoader

Und eine weitere Möglichkeit, dieses Material zu verwenden, ist ein JUnit 4.x Testfall. Die Annotation @ContextConfiguration nimmt einen optionalen Loader-Parameter auf, der auf com.xebia.springframework.test.context.PropertyPlaceholderReplacingContextLoader gesetzt werden kann. Um die zu verwendende Eigenschaftsdatei anzugeben, müssen Sie eine tt>@com.xebia.springframework.test.context.ContextConfigurationProperties Annotation wie diese hinzufügen: @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(loader = PropertyPlaceholderReplacingContextLoader.class, locations = {"/com/xebia/springframework/beans/factory/xml/beans_with_property_placeholders.xml"}) @ContextConfigurationProperties("/com/xebia/springframework/beans/factory/xml/beanvalues.properties") @TestExecutionListeners(value = { DependencyInjectionTestExecutionListener.class })

Fazit

Nun, die Implementierung war ein interessanter Streifzug durch die Interna von Spring 2.x. Es war interessant herauszufinden, wie Spring die Beans-Dateien lädt und wie ich mich da einklinken kann. Und die Implementierung meines eigenen Namespace hat auch Spaß gemacht. Außerdem bin ich gespannt, ob und wie Spring 3.0 dieses Problem lösen wird.

Verfasst von

Vincent Partington

Contact

Let’s discuss how we can support your journey.