[#SJC-38] Support for Inner Classes to represent Child

advertisement

[SJC-38]

Support for Inner Classes to represent Child contexts

Created: 25/Nov/07 Updated:

29/Oct/08 Resolved: 09/Dec/07

Status: Resolved

Project: Spring JavaConfig

Component/s: None

Affects

Version/s:

None

Fix Version/s: 1.0.0.M3

Type: Improvement

Reporter: Ben Rowlands

Resolution: Fixed

None Labels:

Remaining

Estimate:

Time Spent:

Not Specified

Not Specified

Not Specified Original

Estimate:

Issue Links: Related

Priority:

Assignee:

Votes:

Major

Chris Beams

0 is related to SJC-227 Eliminate support for inner @Configur... Closed

Description

We could use InnerClasses to represent a child context nested inside the parent (top-level class) context. Currently the bean reader scans both the top-level class AND any inner classes for bean definitions and lumps them into the same context. Conceptually it is cleaner to express inner class configurations as child contexts of the parent.

If these semantics doen't make sense in the core framework it would be convenient to have a swtich to disable scanning of innner classes (it looks just about possible to hook into the

ConfigurationClassScanningBeanDefinitionReader to do this but its a lot of work; how about a setter - setScanInnerClasses(bolean) - on the AnnotationApplicationContext?).

For example, consider this hierachy represented here by 2 classes:

@Configuration public class ParentConfig

{

@Bean public Greeting greeting()

{ return new Greeting( "Parent" ); }

}

And,

@Configuration

public abstract class ChildConfig

{

@Bean public Person bob()

{ Person bob = new Person(); bob.setGreeting( greeting() ); return bob; }

@ExternalBean public abstract Greeting greeting();

}

It would be more succinct to nest the child context inside the parent context as an inner class

(assuming we don't need to break the configuration into 2 seperate files for management or sharing reasons).

@Configuration public class SingleConfig

{

@Bean public Greeting greeting()

{ return new Greeting( "Parent" ); }

@Bean public Class<?> childContext()

{ return ChildConfig.class; }

@Configuration public static abstract class ChildConfig

{

@Bean public Person bob()

{ Person bob = new Person(); bob.setGreeting( greeting() ); return bob; }

// It would be more succinct to call the greeting() method directly.

// ... but we need 0-arg ctor so JavaConfig can instantiate (so inner-class is static).

@ExternalBean public abstract Greeting greeting();

}

}

In this case the driver would need some logic to extract the childContext class and instantiate, something like:

AnnotationApplicationContext parent = new AnnotationApplicationContext(

SingleConfig.class );

Class<?> k = ( Class<?> ) parent.getBean( "childContext" );

AnnotationApplicationContext child = new AnnotationApplicationContext( parent ); child.setConfigClasses( k ); child.refresh();

A utility could handle scanning a configuration Class for any inner classes with the

@Configuration annotation and then instantiate a new context and chain to its parent returning a

Map of Child contexts? (perhaps using the @Configuration name attribute as the key?). Need to consider recursive nature of inner-classes (innner-class inside an inner-class!)

Comments

Comment by Chris Beams

[ 26/Nov/07 ]

Ben, thanks for the idea. It's an interesting one. Would you provide a few real-world scenarios around this? It's clear what you're trying to achieve, but I'm hard-pressed to imagine where it would be practically useful. Your comment about separating things out for partitioning or management reasons is apt – I think this is the most common case.

Given a use case or two, we'll definitely give it consideration for the upcoming M3.

Also, if this is real-world for you right now, it would be interesting to see what you've done as a work-around in the absence of this feature.

Comment by Ben Rowlands

[ 27/Nov/07 ]

A big benefit of hierarchies are to provide/override autowire candidates.

For my specific use-case; I am trying to design a mechanism to configure a chain of work items.

Each work item will exist in its own context, parented to the top-level application context. Each child context (containing the work item) can declare beans that are visible only to the local context and can also refer to beans in its parent explicitly, as you would expect with a hierarchy of contexts. The real benefit of doing things this way is when we use autowiring. A work item might declare a field as @Autowired (assume this field represents some required service).

Spring will walk the context hierarchy to resolve suitable auto-wire candidates. The top-level application context could define a set of default instances that can be used, however child contexts might provide different instances for particular services. This can be done simply by defining a suitable service in the child context.

A very simple example would be:

@Configuration public class AppConfiguration

{

// some default service...

@Bean public IService1 service1()

{ return new Service1Impl(); }

@Bean public IService2 service2() { return new Service1Impl(); }

@Configuration public static classs WorkItem1Config

{

@Bean public IService2 service2fast()

{ return new Service2FastImpl(); }

@Bean(autowire=ByType) public WorkItem workItem()

{ // will get autowired with beans service1 and service2fast }

}

}

Whilst this same configuration can be implemented in different ways (we could just reference the beans explicitly!), the hierarchy is an intuitive way to allow overriding and we find autowiring helps reduce lots of boilerplate in large configurations. Of course, this doesn't need support from JavConfig (other than ) - we could use a different config class for each context - we just think its more succinct using inner classes due to the overhead of creating a new class each time.

Comment by Chris Beams

[ 30/Nov/07 ]

What did you have in mind for getting hold of the nested contexts? I see at least two possible approaches:

1: process the child directly, assuming that any outer classes will also be processed:

--------------------------------------------------------------------------------------------------------------------

----------

Perhaps you intended to have this implemented such that you could bootstrap as follows:

JavaConfigApplicationContext ctx = new

JavaConfigApplicationContext(AppConfiguration.WorkItem1Config.class);

This would give you direct access to the child context. It assumes that the for any configuration class provided to JavaConfigApplicationContext, we should first check to see if it is nested within any outer classes and recursively process them first.

This option seems desirable because it allows the user to speak directly in terms of the configuration they most care about.

2: process in a top-down fashion, but maintain a registry of all Configs encountered:

--------------------------------------------------------------------------------------------------------------------

----------

A second option might be to provide a registry of all @Configuration classes encountered during processing, available via a method on JavaConfigApplicationContext: public Map<Class<?>, ? extends JavaConfigApplicationContext> getContextRegistry(); which would allow callers to get at any context no matter how deeply nested, simply by querying the registry for a given @Configuration class. Again, using the above example:

JavaConfigApplicationContext ctx = new

JavaConfigApplicationContext(AppConfiguration.class);

JavaConfigApplicationContext workItemCtx = ctx.getContextRegistry().get(WorkItem1Config.class);

This has a layer of indirection to it, but at least it's flat. If there are deeply nested classes, they'll all be available via the top-level context's registry. And it's not such a burden to expect the caller to point at the top-level @Configuration class... It is called AppConfiguration, after all

Of these options, which do you prefer? Note that they're not necessarily mutually exclusive.

Perhaps you've conceived of another approach? Feedback appreciated, thanks.

Comment by Ben Rowlands

[ 02/Dec/07 ]

At present we do something like (1) - but with no auto-detection. I explicitly pass the configuration class literal to the WorkItem constructor.

@Bean public WorkItem item1()

{ // Item1Configuration.class *could* be inner-class or *could* be a top-level class return new

WorkItem( Item1Configuration.class ); }

The WorkItem bootstrap code basically creates a new JavaConfigApplicationContext around the config class and then grabs a handle to its parent Application context (via

ApplicationContextAware) to establish the hierarchy.

I think (2) would be very handy for other use-cases. However in our use-case the work item configuration may be expressed as an inner class but might also be placed in a top-level class

(the inner class is really just a succinct shortcut to provide the nested configuration), so I guess all we need is the ability to disable scanning of inner classes.

Comment by Chris Beams

[ 09/Dec/07 ]

There may be more work to do on this issue in the future, but the basic implementation is complete.

By default, inner classes are not processed at all. Consider the following:

@Configuration public class OuterConfig {

@Bean public Service service()

{ return new ServiceImpl(); }

@Configuration public static class InnerConfig {

@Bean public Object someOtherBean() {

}

}

}

InnerConfig's 'someOtherBean' will not be visible (nor processed at all) if we instantiate the context against the OuterConfig class:

JavaConfigApplicationContext ctx = new JavaConfigApplicationContext(OuterConfig.class); ctx.getBean("service"); // all good ctx.getBean("someOtherBean"); // throws NoSuchBeanDefinitionException

Previous to this change, the above would have worked just fine, because inner configurations' bean definitions were marshalled into the same context as the outer configuration bean defs.

Now, in order to access the inner bean defs, we must actually instantiate the context against the inner class itself:

JavaConfigApplicationContext ctx = new

JavaConfigApplicationContext(OuterConfig.InnerConfig.class);

// the following call works because there is a context hierarchy now. InnerConfig can

// see OuterConfig's beans, because OuterConfig is InnerConfig's parent context. ctx.getBean("service"); ctx.getBean("someOtherBean"); // this works as well

As mentioned above, there may be additional polish to put on this feature, but it fundamentally works. What we need now is for folks to try it out in real-world situations and provide feedback.

Comment by Ben Rowlands

[ 09/Dec/07 ]

Brilliant, thanks for fixing this Chris!

I just ran my tests against the latest snapshot and they pass fine. I'll let you know how we get on with inner-classes for child contexts.

Comment by Chris Beams

[ 29/Oct/08 ]

This support is being considered for removal in the 1.0.0.m5 timeline. Ben (or others), please weigh in if you have objections to this. Has this proved useful?

The complexity of the implementation is hard to justify as we get SJC as lean and mean as possible for 1.0

Generated at Tue Feb 09 14:30:59 UTC 2016 using JIRA 6.4.11#64026sha1:78f6ec473a3f058bd5d6c30e9319c7ab376bdb9c.

Download