Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bean Configuration Overriding [SPR-5509] #10181

Closed
spring-projects-issues opened this issue Feb 19, 2009 · 4 comments
Closed

Bean Configuration Overriding [SPR-5509] #10181

spring-projects-issues opened this issue Feb 19, 2009 · 4 comments
Labels
has: votes-jira Issues migrated from JIRA with more than 10 votes at the time of import in: core Issues in core modules (aop, beans, core, context, expression) status: bulk-closed An outdated, unresolved issue that's closed in bulk as part of a cleaning process

Comments

@spring-projects-issues
Copy link
Collaborator

spring-projects-issues commented Feb 19, 2009

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

@spring-projects-issues
Copy link
Collaborator Author

Chris Beams commented

Slating for review during the 3.2 timeline, when a number of other issues related to bean visibility and overriding will be addressed.

@spring-projects-issues
Copy link
Collaborator Author

spring-projects-issues commented Mar 12, 2011

Chris Beams commented

#12637 also suggests an 'override' attribute but with conflicting semantics.

@spring-projects-issues
Copy link
Collaborator Author

Neale Upstone commented

Looking forward to seeing this in 4 (and it would be worthy of a 4.0 release, to see the end of bean override gotchas).

Here's what I've just somehow done in a haze when migrating towards Java Config from XML.

I (sleepily) replaced

<cache:annotation-driven /> 

with:

<bean id="cacheManager" class="org.foo.config.root.CacheConfig"/>

Where CacheConfig is:

@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public SimpleCacheManager cacheManager() {
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        cacheManager.setCaches(Lists.newArrayList(
                    new ConcurrentMapCache("billionaires"),
                    new ConcurrentMapCache("spaceFlights")
                ));

        return cacheManager;
    }
}

And what I got was that I can boot up a context with CacheConfig.class directly, but when I referenced the bean from the XML it failed, because I'd typed id="cacheManager" when I meant id="cacheConfig" (and in fact would be wiser leaving it out).

Default bean definition behaviour of requiring override="true" (or @Bean(override=true)) would have saved me the time.

And this was a simple one to resolve ;)

@spring-projects-issues spring-projects-issues added status: waiting-for-triage An issue we've not yet triaged or decided on type: enhancement A general enhancement in: core Issues in core modules (aop, beans, core, context, expression) has: votes-jira Issues migrated from JIRA with more than 10 votes at the time of import labels Jan 11, 2019
@spring-projects-issues spring-projects-issues removed the type: enhancement A general enhancement label Jan 11, 2019
@rstoyanchev rstoyanchev added status: bulk-closed An outdated, unresolved issue that's closed in bulk as part of a cleaning process and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Jan 11, 2019
@spring-projects-issues
Copy link
Collaborator Author

Bulk closing outdated, unresolved issues. Please, reopen if still relevant.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
has: votes-jira Issues migrated from JIRA with more than 10 votes at the time of import in: core Issues in core modules (aop, beans, core, context, expression) status: bulk-closed An outdated, unresolved issue that's closed in bulk as part of a cleaning process
Projects
None yet
Development

No branches or pull requests

2 participants