Skip to content

Bean Configuration Overriding [SPR-5509] #10181

Closed
@spring-projects-issues

Description

@spring-projects-issues

Ben Rowlands opened SPR-5509 and commented

Overview

Spring provides a powerful mechanism to "pull" (inherit) configuration from a "parent" configuration. Sometimes it would be convenient to "push" (override) configuration onto a target bean or replace the bean definition entirely using a single consistent mechanism.

We see 2 main use-cases for configuration overriding:

  • A) Testing production configurations with small tweaks
  • B) Extending a default configuration
Use-Case A

When running an integration style test we find we need to override properties or even entire beans in the production configuration to allow tests to run quickly by swapping out real resources with mocked or faked implementations.

Use-Case B

Frameworks built on top of Spring often ship with some 'default' configuration. Applications using this framework import the default configuration into their own configuration to get the framework setup. If these defaults need to be tweaked the only practical option is often to copy & paste the framework configuration and update the required property(s). It is preferable for the Application importing the defaults to be very specific and only override the specific properties it needs to rather than copying the entire config and avoiding getting future changes to the defaults. A real world example of this can be found in our own runtime framework. It manages Application caches in a cache manager configured by the framework defaults. Often Applications want to customize the cache manager, for example, to tweak the size of the thread pool used to keep caches refreshed.


Details

Applications can be designed up front for overriding by using ${} placeholders however this is not always convenient and can obscure the configuration. In other cases the PropertyOverrideConfigurer can help but this requires the overrides be placed in a separate properties file. Spring also provides the ability to re-define beans by putting a bean with the same name in a later configuration file. Together these techniques can solve most, if not all, use-cases however individually they confuse and scatter the overrides. If united into a single mechanism it would allow a consistent approach and central location to define these overrides.

Examples of overriding features we use
  1. Replace beans
  2. Override properties, constructor-args or bean-attributes (for example, the class or lazy-init value)
  3. Custom mutation of properties, for example adding or removing from a collection

1 and 2 represent the 80-90% case.

It is possible to implement these features in an ad-hoc fashion using a custom BFPP to implement the overriding. However we feel a more natural, integrated and standard solution to express the overriding intent in a first class way would be generally useful in Spring. Some ideas on possible approaches to give more background are:

XML Syntactic Sugar
  • Re-use the "parent" bean attribute:
<bean name="foo" class="bar.Widget">
  <property name="p1" value="1"/>
  <property name="p2" value="2"/>
</bean>
<bean name="foo" parent="foo">
  <property name="p1" value="2"/>
</bean>

This is interpreted as "foo" in Config#2 redefines and overrides "foo" from Config#1. This isn't possible with the current implementation since bean definitions with the same name replace previous bean definitions so once Config#2 is read the original 'foo' is lost.

  • Use a naming convention
<bean name="+foo">
  <property name="p1" value="2"/>
</bean>

This is interpreted as "+" (add) the configuration to the bean definition for "foo". This is unlikely to be a general solution since it would break backwards compatibility, but gives an example of what a custom BFPP could do.

  • Add a new 'override' attribute to <bean>
<bean override="foo">
  <property name="p2" value="2"/>
</bean>

This explicitly "overrides" the configuration for "foo".

With both these approaches it isn't clear how to remove configuration - they can only add properties (replacing any previous properties). An additional property attribute or special value could be used to declare that the property should be removed from the definition. Its not clear if this is a real limitation.

Java code

Java is the most powerful and natural mechanism to override beans. For example, if the original configuration was expressed using Spring JavaConfig it would just a case of overriding the method and calling super if required:

@Bean
@Override
public Widget Foo() {
  Widget w = super.Foo();
  w.setP1(2);
  return w;
}

Most configuration is currently expressed in XML so bridging the gap and allowing Java to override selected beans definitions would provide a powerful mechanism. Its not clear if the bean-instance (easiest to work with) or bean-definition (most powerful) should be given to the overriding method. For example, all these overrides could be expressed as methods in a class:

// this 'processor' is called to fix up bean definition 'foo'
@OverrideBean()
public void Foo(AbstractBeanDefinition definition) {
  definition.getPropertyValues().addPropertyValue("p1", 2);
}

The AbstractBeanDefinition API can be cumbersome to use, especially when dealing with collections and bean references. For some use-cases it may be sufficient to work with the concrete bean-instance, a factory pattern like that used in the Scope API would allow for the XML bean definition to be completely ignored if required:

// this 'factory' is called to create the bean 'foo'
@OverrideBean()
public Object Foo(BeanCreator factory) {
  // Can avoid creating 'foo' defined in config by just using new Foo()
  // The BeanCreator could also offer access to BeanDefinition allowing
  // both instance/definition APIs to work side by side.
  Foo foo = (Foo)factory.create();
  foo.setP1(2);
  return foo;
}

Both Java approaches work cleanly with the testing use-case since a single test class can define both the test logic and test overrides, rather than spreading these throughout multiple files.


Issue Links:

27 votes, 29 watchers

Metadata

Metadata

Assignees

No one assigned

    Labels

    has: votes-jiraIssues migrated from JIRA with more than 10 votes at the time of importin: coreIssues in core modules (aop, beans, core, context, expression)status: bulk-closedAn outdated, unresolved issue that's closed in bulk as part of a cleaning process

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions