Click here to Skip to main content
15,891,473 members
Articles / Web Development / Spring

Spring Context Internals: Part 2 - Bean Sources

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
11 Oct 2017CC (ASA 3U)18 min read 11.7K   2   2
Spring Context Internals: Bean sources

It's not always what you think it is.

Once in a while, I needed to plug some third-party module into our project. Despite the fact that Spring makes it extremely simple in terms of design, an actual plugging may give you a lot of headache at the moment you realize it does not behave as you expect. The trick was that the module had XML context configuration, while we used to employ JavaConfig...

In this part of the series, we shall review Spring's bean loading process in detail for both XML and Annotation contexts. Bean overriding, extension points, custom per-project defaults, and all this stuff are actually not that hard if you know where to look.

This article describes processes usually happening around bean factory creation and post-processing. Here's where we are relating to the previous article (https://www.codeproject.com/Articles/1195578/Spring-Context-Internals-Part-Refresh):

Our place in Spring Context bootstrapping

At the bottom of this text, there is a schema that structurally summarizes all the components described today. You may use it to navigate yourself back whenever you're lost in the article.

Bean Definition Reader Concept

Spring's context configuration parsing infrastructure is usually detached from contexts themselves by an entity called bean definition reader. This reader accepts a single bean definition source type (e.g., an XML context descriptor or a @Configuration class instance) and encapsulates all the logic related to this source.

On BeanDefinitionReader Interface

Whilst there is a BeanDefinitionReader interface, not every reader implements it. An AnnotatedBeanDefinitionReader is a fine example. The reason latter one doesn't is that the interface was kind of poorly designed to only represent resource-based contexts. It is centered around Resource class, which is redundant in case of JavaConfig.

Bean definition usually provides a method for parsing bean source, be so a Resource instance, or a pre-loaded Class. There are 5 standard readers:

  • XmlBeanDefinitionReader - hard-coded into XML contexts
  • AnnotationBeanDefinitionReader - hard-coded into Annotation contexts
  • ConfigurationClassBeanDefinitionReader - invoked by ConfigurationClassPostProcessor, parses @Bean methods
  • GroovyBeanDefinitionReader - hard-coded into GenericGroovyApplicationContext
  • PropertiesBeanDefinitionReader - can only be used externally (see below)

Since there's no significant operational difference between XML- and Groovy-based parsers, we shall skip Groovy one.

Property-based Bean Definition Reader

In present days, when you need a simple context definition without all that excessive XML hassle, you take JavaConfig. However, long before JavaConfig, there was an even more simple approach. We can actually describe our context... in a property file:

transport.(class)=me.jtalk.example.TcpTransport
# We take constructor parameters, only indexed access is supported
transport.$0=127.0.0.1
transport.$1=8080
# Access property by value
transport.description=A transport to some HTTP service on port 8080
# We set it to prototype scope 
transport.(scope)=prototype

requester.(class)=me.jtalk.example.PollingRequester
requester.timeout=1000
# Access property by reference to another bean
requester.transport(ref)=transport

The context above is somehow equivalent to:

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"
   xsi:schemaLocation = "http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">

    <bean name="transport" 
         class="me.jtalk.example.TcpTransport"
         scope="prototype">
        <constructor-arg index="0">127.0.0.1</constructor-arg>
        <constructor-arg index="1">8080</constructor-arg>
        <property name="description">A sample transport definition</property>
    </bean>

    <bean name="requester"
          class="me.jtalk.example.PollingRequester">
        <property name="timeout">1000</property>
        <property name="transport" ref="transport"/>
    </bean>
</beans>

And imagine that: this context definition approach is so rare there's literally no matching context type associated with it! Spring itself uses it for WebMVC's resource bundles parsing, but if we want to make a real runtime context from it, we need to make it the hard way:

Java
GenericApplicationContext ctx = new GenericApplicationContext();
PropertiesBeanDefinitionReader reader 
        = new PropertiesBeanDefinitionReader(ctx);
reader.loadBeanDefinitions("classpath:/context.properties");
ctx.refresh();
// Now we have our context up-and-running

This property-based context is rather simple and has no extension points. It also does not participate in any importing relations. Its only purpose seems to be that it is, well, funny. On this high note, let's proceed to where the REAL work's being done!

XML Bean Definition Reader

There is a family of XML-based contexts from Spring's internal point of view, including ClassPathXmlApplicationContext, WebXmlApplicationContext, and so on. By oversimplifying things a little, we might say that:

Java
XmlApplicationContext = AbstractAC + XmlBeanDefinitionReader

The abstract context implementation is something we have already reviewed quite precisely in the previous article, so we shall now focus on that reader thing.

About Refreshable Contexts

For most of context implementations, usually derived from GenericApplicationContext, it is impossible to load configuration more than once: successive refresh() calls just throw an exception with no gain. Unlike, a refreshable context can be hot-reloaded in runtime, allowing us to change whatever is configured there. While refreshability is kind of useless when speaking of application's primary context because of its kill-all-and-start-again stance, it might come in handy for small per-component contexts to be reloaded on demand.

XML application contexts is the only major refreshable context family. Both Annotation-config and Groovy ones are not.

The XmlBeanDefinitionReader itself is not that important: it just provides basic configuration capabilities with resource handling and thread safety. All the actual work is done by BeanDefinitionDocumentReader, capable of actual XML-to-BeanDefinition conversion.

About Document Reader Configuration

Despite the fact that XmlBeanDefinitionReader implements all the proper interfaces and provides a bunch of custom setters, it is instantiated inside an AbstractXmlApplicationContext by a simple "new" invocation, and it is thus impossible to configure it without providing your own context implementation.

BeanDefinitionDocumentReader

The reader is quite straight-forward: it just parses XML, detects its hard-coded tags, and delegates custom tags to a pre-configured set of resolvers. The list of these hard-coded tags include:

  • <profile>: Check if this XML is included into a specified profile
  • <bean>: Parse bean definition
  • <beans>: Parse subset of bean definitions
  • <alias>: Register bean definition alias
  • <import>: Process another configuration file

About Nested <beans> Element

Sometimes, we need to apply global configuration options (e.g. profile) to a subset of bean definitions. A fine example would be to provide two sets of bean implementations based on profile, without messing with multiple XML configurations. <beans> allows us to configure this in a single XML descriptor.

Spring reads tags one by one, overriding bean definitions when necessary. <import> statements are handled recursively: the reader calls itself with a new resource location. Spring also keeps track of resources already being loaded to avoid cyclic dependencies.

About Cyclic Dependencies Detection

Even though Spring does keep track of already loaded resources, it will handle two successive import clauses with the same resource. Cyclic dependencies detection works like a call stack: a locator of a resource currently being imported is stored into a set, and removed after importing is done. It is thus unwise to duplicate import clauses relying on this mechanism.

Here's a simplified example of this "hard-coded only" configuration. Seems poor, doesn't it?

XML
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">

    <import resource="classpath:/properties.xml"/>

    <bean name="myService" class="me.jtalk.spring.example.MyService">
        <constructor-arg ref="transport"/>
    </bean>
    <alias name="myService" alias="myTransportService"/>

    <beans profile="integration">
        <bean name="transport" 
              class="me.jtalk.spring.example.IntegrationTransportImpl"/>
    </beans>

    <beans profile="production">
        <bean name="transport" class="me.jtalk.spring.example.TransportImpl"/>
    </beans>
</beans>

Custom XML Handlers

You might've noticed that the subset of tags mentioned above is not anyhow sufficient. In our actual descriptors, we usually have a lot of stuff including <aop:scoped-proxy>, <context:annotation-config>, <tx:annotation-driven>, <util:map>, and so on. Even though these tags are provided by Spring Framework, the "context" code base is unaware of them. There is a magic bringing these to life, and this magic is called NamespaceHandlerResolver.

You might've heard of a file named spring.factories: the way Boot's auto-configuration works - we provide a file into our classpath with a pre-defined name, and from that file, Spring can determine whatever it needs to bootstrap a related subsystem. XML parser works in a similar fashion: there is a contracted name spring.handlers with mappings between schema names and handler classes, and each handler is capable of processing the element specified:

http\://www.springframework.org/schema/aop=\
    org.springframework.aop.config.AopNamespaceHandler
http\://www.springframework.org/schema/c=\
    org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
http\://www.springframework.org/schema/p=\
    org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
http\://www.springframework.org/schema/util=\
    org.springframework.beans.factory.xml.UtilNamespaceHandler
If I were an XML context user...

... I would really keep the above in mind. The common downside of all that managed Java thing is that you have no way of just tracking the entity you're interested in back to the one that defines its behaviour. You can hardly track annotations, and you have almost no means of tracking this extension magic. An IDE could help if you remember to make a full-text search amongst all the libraries with your xmlns value.

We can have as many of these files in our classpath as needed, all of them will be parsed and added into context's NamespaceHandler registry. When BeanDefinitionDocumentReader discovers an unknown tag, it resolves its namespace and then looks a corresponding handler up in that registry.

Once a handler is found, all the parsing is delegated to its corresponding method. There are two methods in NamespaceHandler, related to a pair of corresponding element handling modes BeanDefinitionDocumentReader supports: bean parsing and delegation.

NamespaceHandler actually has a third method named init(), but it is kind of useless and self-describing.

Although NamespaceHandler can be used directly, it is usually better to extend NamespaceHandlerSupport instead. It provides a handy way of registering per-tag-name bean registrars and decorators:

Java
public class SomeNamespaceHandler extends NamespaceHandlerSupport {

	@Override
	public void init() {

		// Something like 
		// <myns:herp-bean name="myHerpBean"> 
		// instead of 
		// <bean name="myHerpBean" class="me.jtalk.spring.example.MyHerpBean"/>
		registerBeanDefinitionParser("herp-bean", new HerpBeanParser());
		registerBeanDefinitionParser("derp-bean", new DerpBeanParser());

		// Something like
		// <bean name="mySampleBean" class="me.jtalk.spring.example.MySampleBean">
		//     <myns:proxified/>
		// </bean>
		registerBeanDefinitionDecorator("proxified", new ProxifierDecorator());
	}
}

A thoughtful reader might find herp/derp-bean to be similar to util:map and util:list and myns:proxified to mimic aop:scoped-proxy.

Bean parsing means handling of a top-level bean definition. Unintuitive, but bean parsing is not required to always end up in bean definition creation. Some Spring-provided bean parsers, like context:annotation-config's one, never produce any bean definition. Instead, they use their ParserContext argument for adjusting BeanDefinitionRegistry settings and registering annotations-related post-processors without actual bean creation. They thus return null instead of a produced bean definition, like a simplified <context:annotation-config> parser below:

Java
public class AnnotationConfigBeanDefinitionParser implements BeanDefinitionParser {

	@Override
	public BeanDefinition parse(Element element, ParserContext parserContext) {
	    BeanDefinitionRegistry registry = parserContext.getRegistry();
		registerAnnotationConfigProcessors(registry);
		return null;
	}
}
On weird "return null" thing

Spring actually has no use for top-level bean definition parser's result. It will be gently abandoned around DefaultBeanDefinitionDocumentReader#parseBeanDefinitions and serves no purpose. However, it is needed for nested bean definitions. It is because of Spring's funny feature of declaring beans inside <constructor-arg> and <property> elements: if we allow their parsers to return null bean definitions, we'll end up having nothing to inject.

Spring Handlers Practice

Let's play some spring handlers. Our step-by-step plan will be to:

  • Create our own namespace with XSD
  • Write a handler for that namespace
  • Match them by providing custom spring.handlers to classpath

We'll invent our very own (quite useful, of course) logger bean registration system.

Well, considering that juniors may be reading this, I should explicitly proclaim the parenthesized part of the previous sentence being ironical.

All Spring namespaces look quite the same: these start from http://www.springframework.org/schema gibberish ending with a sub-namespace name, like http://www.springframework.org/schema/context, http://www.springframework.org/schema/beans, or http://www.springframework.org/schema/util. The XSD is not required to match to an actual HTTP address, so we can just provide a fake reasonable name, like http://xmlns.jtalk.me/schema/logger-bean.

As XSD infrastructure and format are out of scope of this article, here's an unexpected transition right to the end:

XML
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
           targetNamespace="http://xmlns.jtalk.me/schema/logger-bean"
           elementFormDefault="qualified">

    <xs:element name="log-bean">
        <xs:complexType>
            <xs:attribute name="type">
                <xs:simpleType>
                    <xs:restriction base="xs:string">
                        <xs:enumeration value="slf4j"/>
                        <xs:enumeration value="jul"/>
                        <xs:enumeration value="jcl"/>
                    </xs:restriction>
                </xs:simpleType>
            </xs:attribute>
            <xs:attribute name="name">
                <xs:simpleType>
                    <xs:restriction base="xs:string"/>
                </xs:simpleType>
            </xs:attribute>
            <xs:attribute name="package">
                <xs:simpleType>
                    <xs:restriction base="xs:string"/>
                </xs:simpleType>
            </xs:attribute>
        </xs:complexType>
    </xs:element>
</xs:schema>

The schema above will match into something like:

XML
<log:log-bean name="slfLogger" 
              package="me.jtalk.spring.my.business.package" 
              type="slf4j" />

Now, we make a handler. Watch for hints:

Java
// We extend from NamespaceHandlerSupport. It provides a convenient
// way of storing handlers and matching them to parse/delegate methods.
public class LoggerBeanHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
    	// This handler will match <log-bean> declarations around 
    	// your context XML.
        registerBeanDefinitionParser("log-bean", new LoggerParser());
    }

    @Getter // This is Lombok. You should really give it a try.
    @RequiredArgsConstructor
    private enum LogType {

    	// Just a simple store with per-logger-interface settings.
        SLF4J(Logger.class, LoggerFactory::getLogger),
        JCL(Log.class, LogFactory::getLog),
        JUL(java.util.logging.Logger.class, java.util.logging.Logger::getLogger);

        private final Class loggerType;
        private final Function<string object=""> loggerFactory;
    }

    // The parser itself: 
    private static class LoggerParser implements BeanDefinitionParser {

        @Override
        public BeanDefinition parse(Element element, 
                                    ParserContext parserContext) {

        	// The below lines originally contained helper methods with attribute
        	// absence handling, verbose naming, and so on. I have removed these
        	// to your convenience.
            LogType type = LogType.valueOf(element.getAttribute("type")
                                                  .toUpperCase());
            String loggerPackage = element.getAttribute("package");

        	// The magic is inside.
            BeanDefinition bd = createFactoryBeanDefinition(type, loggerPackage);

        	// I have kept this bean name generation part because it involves 
        	// a funny trick with a reader context. 
            String beanName = parseBeanName(element, 
                                            bd, 
                                            parserContext.getReaderContext());

            // Our bean definition is now part of a bean registry. Hooray. 
            // This part is vital: even though our definition will be returned
            // back to Spring, it will not re-register it for us.
            parserContext.getRegistry().registerBeanDefinition(beanName, bd);

            // Thanks to this little buddy, we can now use our log-bean inside
            // <constructor-arg> and <property> tags.
            return bd;
        }

        private BeanDefinition createFactoryBeanDefinition(LogType type, 
                                                           String loggerPackage) {

        	// Our bean definition is based on a factory below. 
        	// You could've seen a more "direct" approach of bean
        	// factory instantiation in the previous article:
        	// we were using "registerSingleton" for that purpose,
        	// instantiating a factory at once. Below is a more 
        	// "gentle" approach Spring tends to use internally.
            AbstractBeanDefinition bd = BeanDefinitionBuilder
            	    .genericBeanDefinition(LoggerFactoryBean.class)
                    .getBeanDefinition();

            // This is how <constructor-arg> looks like on the inside.
            // We will simulate bean definition with two arguments:
            // a logger class and a factory closure. Take a note of 
            // argument names matching factory's constructor.
            ConstructorArgumentValues args = new ConstructorArgumentValues();

            // Create, name, register...
            ConstructorArgumentValues.ValueHolder logTypeArg 
                    = new ConstructorArgumentValues.ValueHolder(
                            type.getLoggerType());
            logTypeArg.setName("loggerClass");
            args.addGenericArgumentValue(logTypeArg);

            // ..., repeat
            ConstructorArgumentValues.ValueHolder logSupplierArg 
                    = new ConstructorArgumentValues.ValueHolder(
                            (Supplier)() 
                                -> type.getLoggerFactory().apply(loggerPackage));
            logSupplierArg.setName("loggerSupplier");
            args.addGenericArgumentValue(logSupplierArg);

        	// Spring's bean definition is now aware of our constructor args.
            bd.setConstructorArgumentValues(args);

            return bd;
        }

        private String parseBeanName(Element element, 
                                     BeanDefinition bd, 
                                     XmlReaderContext readerContext) {
            if (element.hasAttribute("name")) {
                return element.getAttribute("name");
            } else {
            	// The funny part is that you need a bean definition instance
            	// to name a bean definition. Even though it's a simple concept,
            	// many people find it unintuitive that your bean definition
            	// is completely detached from bean name. It's like an inode:
            	// your file descriptor knows nothing of your file's name,
                // only a directory has its name.
                return readerContext.generateBeanName(bd);
            }
        }
    }

    // This is an extended example from what you could've seen in a previous
    // article. We used to employ FactoryBean for our Guice-to-Spring
    // bridge, and now we make a step further: an AbstractFactoryBean 
    // provides caching capabilities and declarative singleton configuration.
    // Its getObjectType is also a sole reason we need logger class in our 
    // enumeration nearby.
    private static class LoggerFactoryBean<t> extends AbstractFactoryBean<t> {

        private final Class<t> loggerClass;
        private final Supplier<t> loggerSupplier;

        public LoggerFactoryBean(Class<t> loggerClass, Supplier<t> loggerSupplier) {
            this.loggerClass = loggerClass;
            this.loggerSupplier = loggerSupplier;
        }

        // This is for auto-wiring resolver. The previous article covers 
        // it a little. Short-cut: Spring cannot determine factory's 
        // return type until at least one instance is created. Instance 
        // creation does not help in case of non-singleton beans.
        @Override
        public Class<?> getObjectType() {
            return loggerClass;
        }

        @Override
        protected T createInstance() throws Exception {
            return loggerSupplier.get();
        }
    }
}

Phew! Now, as we have seemingly suffered enough, a spring.handlers part will be short:

http\://xmlns.jtalk.me/schema/logger-bean=\
    me.jtalk.spring.xmlhandler.LoggerBeanHandler

We just match our namespace with a handler definition above. That's all, we can now test our approach:

XML
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:log="http://xmlns.jtalk.me/schema/logger-bean"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
           http://xmlns.jtalk.me/schema/logger-bean
           log-bean-1.0.xsd"><!-- Here comes our actual XSD location. 
                                  You are free to serve it via HTTP or
                                  to store it in classpath, as I did -->

    <!-- Standalone beans declaration -->
    <log:log-bean name="slfLogger" 
                  package="me.jtalk.spring.logger.slf" 
                  type="slf4j" />

    <bean class="me.jtalk.spring.xmlhandler.LogTester">
        <!-- Inject by reference -->
        <constructor-arg name="slj" ref="slfLogger"/>
        <constructor-arg name="jul">
            <!-- Inject by nesting -->
            <log:log-bean package="me.jtalk.spring.logger.jul" type="jul" />
        </constructor-arg>
    </bean>
</beans>

You can do any testing inside LogTester implementation. I just print some gibberish into different log engines, nothing special.

Handling @Configuration Classes

There is a significant part of XML context design we have not discussed yet. @Bean-annotated methods were designed for Annotation-config contexts to be able to employ FactoryBean-alike attitude with all due flexibility. As a backward compatibility change, these methods were enabled to work from XML context: you can just declare any bean with @Bean-annotated method inside your XML context, and all these methods will be instantiated at runtime, performing all due checks. An enclosing XML can override bean definitions declared this way, and everything will work as expected. But how does Spring do it? Is there a custom tag parser? A hard-code?

A Fine Exercise

As an exercise, an engaged reader can now stop reading, remember <context:annotation-config>, and make a fine detective work by employing whatever was discussed above. The answer goes below.

The answer lies inside a <context:annotation-config> handler. We have already taken a look to a simplified context namespace handler above. Here it is, for your convenience:

C#
public class AnnotationConfigBeanDefinitionParser implements BeanDefinitionParser {

	@Override
	public BeanDefinition parse(Element element, ParserContext parserContext) {
	    BeanDefinitionRegistry registry = parserContext.getRegistry();
		registerAnnotationConfigProcessors(registry);
		return null;
	}
}

For them who have made it to the end of the first half of the previous article, it should all make sense now. All the annotation-config element does is register a bunch of registry post-processors enabling various annotations handling mechanisms. Our pal is called ConfigurationClassPostProcessor, and it, aside from a lot of @Configuration instrumentation and importing, does that @Bean parsing by redirecting it to a ConfigurationClassBeanDefinitionReader instance. So, if we run our XML context from the XML below, our flow will be:

C#
@Configuration
public class Config {
    @Bean
    public String myBean() {
        /**
          * 4: Finally, after the whole XML is parsed, our 
          * ConfigurationClassPostProcessor is invoked. It 
          * parses #myBean() and should by expectations
          * replace myBean instance from XML. But it doesn't.
          * Why?
          */
        return "JavaConfig"; 
    }
}
XML
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <!-- 
        1: we register ConfigurationClassPostProcessor for the bean below
    -->
    <context:annotation-config/>

    <!-- 
        2: we register our @Configuration class as a bean.
        Its @Bean method has not been parsed yet, as 
        ConfigurationClassPostProcessor will not be called until
        the whole XML is parsed
    -->
    <bean class="Config"/>

    <!--
        3: we register our 'myBean' instance, it does not
        override anything becoming a first bean of this name.
    -->
    <bean name="myBean" class="java.lang.String">
        <constructor-arg>XmlConfig</constructor-arg>
    </bean>
</beans>

Since post-processors are run after all the XML parsing is done (see "and here" at the heading schema of this article), they technically must override any bean definitions inside a context despite actual XML tags order. However, it is not the case here: Spring does not allow @Bean methods to override parent XML's work with a magical method ConfigurationClassBeanDefinitionReader.isOverriddenByExistingDefinition. It works by disallowing several bean definitions to be overridden, except:

  • Ones that were loaded from @Configuration class not within the same inheritance hierarchy (i.e., not from current class' descendant)
  • Component-scanned beans
  • Support and infrastructure beans

Our bean below does not match any of the criteria above, so it cannot override XML-provided bean even though it is parsed much later.

On Support and Infrastructure Beans

Spring employs bean definition's role parameter as a mark of what does this bean does in terms of business-related application life-cycle. Aside from application role, which relates to business activities, Spring also supports infrastructure and support beans. First, take care of Spring's internals and are irrelevant to business beans, and latter provide reasonable assistance around third-party beans registration, but are not used in business code directly.

An interesting part here is that Spring will not override XML-provided beans even if they are registered before our @Configuration class. Spring has no information on bean registration order here. Here comes the rule of thumb of Spring inter-source bean processing: you can only override @Bean by XML, not the other way around.

When I Was Younger

We used to have @ImportResource annotation for configuring Spring Batch in our JavaConfig application. Batch was an external project not yet officially acclaimed to be part of Spring Framework, and we also wanted to replace one of its service beans with our implementation. Bug squashing, all that thing. We tried to override these XML-provided beans within our @Configuration class... with no luck. It took me a day to derive some sort of tendency with these imports, it took me a week to end up with a theory of why is it what it is, and it took almost a year to replace that theory with actual understanding which, to be honest, differs quite a lot.

Bean override suffers from a long lasting backward compatibility trail, making it hard to introduce new functionality seamlessly. The rule of thumb above can save you a lot of effort.

Annotation Bean Definition Reader

Annotation-driven context was an attempt to move applications away from XML metadata. Enterprise JavaBeans had long before made XML dependency descriptor a synonym of pain, and people thus started looking for more lightweight and stable IoC containers. The JavaConfig project was an attempt to reduce amount of declarativeness by replacing XML with @Configuration classes. It was merged into Spring 3.0, and it would be enhanced with component scanning capability as per Spring 3.1.

Even though JavaConfig seems entirely different from what XML has to offer, it's not that bad. Spring had its architecture polished by years of development and maintenance, making it extremely decoupled and so maintainable. We shall see that a lot of what we've learned at the XML part applied here as well.

AnnotationConfigApplicationContext is designed aside from SOLID's S, making it an entry point of both component-scanning and @Configuration-annotated approaches. This context class thus employs two different venues:

  • ClassPathBeanDefinitionScanner - handle component scanning patterns
  • AnnotatedBeanDefinitionReader - handle a pre-loaded @Configuration-annotated class
Java
public void main(String[] args) {

	// Launching a scanning context
	AnnotationConfigApplicationContext scannedCtx 
            = new AnnotationConfigApplicationContext("me.jtalk.example");

	// Launching a scanning context v2
	AnnotationConfigApplicationContext scannedCtx2 
            = new AnnotationConfigApplicationContext();
	scannedCtx2.scan("me.jtalk.example");
	scannedCtx2.refresh();

	// Launching a class-based context
	AnnotationConfigApplicationContext classCtx 
            = new AnnotationConfigApplicationContext(MyConfig.class);

	// Launching a class-based context v2
	AnnotationConfigApplicationContext classCtx2 
            = new AnnotationConfigApplicationContext();
	classCtx2.register(MyConfig.class);
	classCtx2.refresh();
}
A Note on Refresh

Please note that both constructor-initialised contexts require no manual refreshing. Moreover, they will fail as annotation contexts are not refreshable. Would you need to do some preparations, like injecting a manual dependency into context's bean factory, scan/register methods come to rescue.

As a rule of thumb: in Spring, context constructors that consume configuration location trigger refresh rendering a context to be "sealed" at once.

Despite these two types of annotation-config venues seem different, they actually are tiny and simple. The catch here is that all the major work is made under-the-hood by an already-presented ConfigurationClassPostProcessor. As we have already discovered, this is a BeanDefinitionRegistryPostProcessor capable of converting @Configuration classes into bean definitions via ConfigurationClassBeanDefinitionReader, an unusual reader that, instead of some resource, parses bean definitions as declared as @Bean methods on specified @Configuration classes! So here it is: all the AnnotationConfigApplicationContext's difference is the way it adds annotated beans into the registry. The rest is the same as for XML!

Both ClassPathBeanDefinitionScanner and AnnotatedBeanDefinitionReader just create bean definitions for every class mentioned and put them into BeanDefinitionRegistry. They also must parse bean-related metadata, like:

  • Bean name
  • Proxy mode
  • isPrimary mode
  • Bean scope
  • isLazy mode
  • Bean role (application/support/infrastructure, see above)
  • Bean description
On Scanner Internals

Whilst both seem quite the same, scanner differs in a quite significant manner: it does not actually load class instance. Scanner heavily employs Spring's internal ClassReader infrastructure, parsing class byte-code in order to obtain metadata. It allows applications to actually skip whatever doesn't match their filtering criteria without wasting class-loader cache. The whole thing became less important since Java 8 has decommissioned PermGen space, yet there's a bit of satisfaction left for whoever hates wasting resources.

Most of the above is done by scanner/reader's internal hard-code. The only three exceptions here are bean name, scope, and proxy mode.

AnnotationBeanNameGenerator

Bean names are generated by BeanNameGenerator. XML context employs its simplified implementation, while Annotation-config uses an advanced AnnotationBeanNameGenerator. However, we can provide whatever instance we want via setBeanNameGenerator method of our context instance! While XML context's one was a boring collection of inner/child/whatever bean specifics, this one actually covers a lot of customary stuff alone. This single bean covers:

  • Spring's @Component
  • CDI's @ManagedBean
  • CDI's @Named
On General Purpose Framework Development

Spring is a widely-used general-purpose framework used in both performance-critical and enterprise applications, start-up prototypes and highly regulated environments. Its developers face various challenges while trying to satisfy the whole Java market with their product. AnnotationBeanNameGenerator contains a catchy point on that: whilst most developers would use something like instanceof to check annotation type, Spring uses String.equals. Because you might want to skip these annotations from your classpath, while Spring must keep supporting them.

Generator provides an isStereotypeWithNameValue method, which actually allows us to register ANY annotation having value attribute to be a name source. Watch the hands:

Java
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MyNamed {
    String value();
}

class CustomABNG extends AnnotationBeanNameGenerator {
    @Override
    protected boolean isStereotypeWithNameValue(String annotationType, 
    	Set<string> metaAnnotationTypes, 
    	Map<string object=""> attributes) {

        return Objects.equals(annotationType, MyNamed.class.getName())
                || metaAnnotationTypes.contains(MyNamed.class.getName())
                || super.isStereotypeWithNameValue(annotationType, 
                                                   metaAnnotationTypes, 
                                                   attributes);
    }
}

@MyNamed("nameFromMyNamed")
class SampleBean {}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@MyNamed("nameForNestedMyNamed")
public @interface CustomComponent {
}

@CustomComponent
class NestingSampleBean {}

// If we now make something like that, it will work!
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.setBeanNameGenerator(new CustomABNG());
ctx.scan("me.jtalk.examples");
ctx.refresh();
SampleBean sb = ctx.getBean("nameFromMyNamed");
NestingSampleBean nsb = ctx.getBean("nameForNestedMyNamed");</string></string>

This way, we can add extra naming techniques from less-renown frameworks Spring will never support in a couple lines of code!

About meta-annotations

You could've noted metaAnnotationTypes in the code above. Spring calls inherited annotations that way, so you can actually put your @MyNamed at any other annotation, and the generator above will process it! Meta-annotation check is what makes CustomComponent look-up to work!

ScopeMetadataResolver

Unlike bean name generator above, this is a whole new concept. Scoping rules might be difficult to adjust in runtime, so there is a special resolver in place.

On XML Analogue

In XML context, the only true way of providing extension data to our bean definition is NamespaceHandler. Everything we'd wish to do with our context can be expressed through a set of handlers, and we thus need no special treatment for extensions. Annotation-config lacks such extensibility, thus making it essential to interfere with bean creation that way. All the latter adjustments can be made with custom BeanPostProcessor/BeanFactoryPostProcessor.

Resolver takes a not-yet-registered bean definition and converts it into ScopeMetadata instance. The metadata is later used by both Scanner and Reader to apply scoping/proxying rules accordingly.

Spring by now provides two resolver implementations out-of-the-box:

  • Spring @Scope resolver - annotation-based with scope defaulting to singleton
  • CDI resolver - @Singleton-based with scope defaulting to prototype.

And, well, there are strong chances you'll never see CDI resolver in use in your life, so we'd better focus up on @Scope-based one.

Spring prototype scope vs. CDI's @Dependent One

Whilst Spring declares its prototype to be the same as CDI's @Dependent, but it actually is not. Java EE strictly defines bean scoping rules, there's effectively no bean with undefined lifetime. Dependent bean's shutdown is tied up to its holder, so its @PreDestroy will be handled as soon as its parent bean (to which it is dependent) gets destroyed. Unlike CDI, Spring never calls destruction callbacks on non-singleton beans. So, Spring's CDI conformity is not what you should actually rely on.

As you could've seen, @Scope annotation is defined like this:

Java
public @interface Scope {
	String scopeName();
	ScopedProxyMode proxyMode();
}

Basically, ScopeMetadataResolver does one thing: converts this annotation into ScopeMetadata instance. While we can't really intervene here, we can define our own resolver with custom defaulting rules. The one below parses @Scope annotation, defaulting to prototype scope and TARGET_CLASS proxying for non-singleton beans:

Java
public class MyScopeMetadataResolver 
				implements ScopeMetadataResolver {

	ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
        ScopeMetadata metadata = new ScopeMetadata();
        // Setting defaults
        metadata.setScopeName(BeanDefinition.SCOPE_PROTOTYPE);
        metadata.setScopedProxyMode(ScopedProxyMode.TARGET_CLASS);
        // We only process beans created from annotated configuration 
        // classes. XML- and Groovy-provided beans are out of scope
        // of this resolver.
        if (definition instanceof AnnotatedBeanDefinition) {
            AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition;
            // Get reflection-based annotation fields
            Map<string object=""> attrs = annDef.getMetadata()
            	.getAnnotationAttributes(Scope.class.getName());
            if (attrs != null) {
                String scopeName = (String) attrs.get("value");
                if (Strings.isNullOrEmpty(scopeName)) {
                    scopeName = BeanDefinition.SCOPE_PROTOTYPE;
                }
                metadata.setScopeName(scopeName);
                ScopedProxyMode proxyMode 
                        = (ScopedProxyMode) attrs.get("proxyMode");
                boolean defaultProxyMode = proxyMode == null 
                                           || proxyMode == ScopedProxyMode.DEFAULT;
                if (!scopeName.equals(BeanDefinition.SCOPE_SINGLETON) 
                	&& defaultProxyMode) {
                	// TARGET_CLASS for non-singletons
                    proxyMode = ScopedProxyMode.TARGET_CLASS;
                } else {
                	// No proxying for singleton beans
                    proxyMode = ScopedProxyMode.NO;
                }
                metadata.setScopedProxyMode(proxyMode);
            }
        }
        return metadata;
	}
}

As soon as we register this resolver with our AnnotationConfigApplicationContext#setScopeMetadataResolver, all the annotated beans will be registered as prototypes by default. I'd say you should skip doing so in production environments, but this feature has its uses, for instance, when defining a nested context with set of third-party prototype worker beans you can't adjust scopes on.

@Bean Method Nuance

As of Spring 4.3.7, you cannot override scoping rules of @Bean-annotated factory methods. They accept @Scope annotation, but its resolver is hard-coded to be AnnotationScopeMetadataResolver in ConfigurationClassBeanDefinitionReader. After all these years, there's still room for improvements!

Annotation Bean Definition Scanner

In the previous paragraph, we have already mentioned the ClassPathBeanDefinitionScanner. It covers component-scanning approach and has a lot of conditionals evaluation. Spring's conditionals are out of scope for this article.

Configuration Class Parsing Infrastructure

As you might've noticed, both XML- and annotation-based context approaches use ConfigurationClassBeanDefinitionReader as a way of handling Java-configured beans. I would say even more: this class is the only standard way of doing so. No matter how you would register your @Configuration class into a bean factory, in order to turn it into bean definitions, you need this reader.

Let's recall the way to enable it:

  • XML context: registered by AnnotationConfigBeanDefinitionParser in ContextNamespaceHandler via NamespaceHandler infrastructure
  • Annotation context (by @Configuration): registered by AnnotatedBeanDefinitionReader
  • Annotation context (by component-scan): registered by ClassPathBeanDefinitionScanner

ConfigurationClassBeanDefinitionReader is package-private, and we thus have no options of manipulating it. No default name generator override, no custom scope resolving, nothing. I might speculate that Spring guys found no gentle way of introducing a configurable version inside both XML- and annotation-based configurations since they provide quite different APIs. Also, in XML-based configurations, its post-processor is injected in a way inaccessible for end-user's application.

Conclusion

Let's now take an overview of what we have reviewed today:

Spring
|-- XmlApplicationContext
|   \-- XmlBeanDefinitionReader
|        \-- BeanDefinitionDocumentReader
|              |-- DefaultBeanNameGenerator
|              \-- NamespaceHandler
|                    |-- ContextNamespaceHandler
|                    |   \-- AnnotationConfigBeanDefinitionParser
|                    |       \-- ConfigurationClassPostProcessor
|                    |           \-- ConfigurationClassBeanDefinitionReader
|                    |-- AopNamespaceHandler
|                    |-- ...
|                    \-- TxNamespaceHandler
\-- AnnotationApplicationContext
    |-- BeanFactory
    |   \-- ConfigurationClassPostProcessor
    |       \--- ConfigurationClassBeanDefinitionReader
    |-- AnnotationBeanDefinitionReader
    |   |-- AnnotationBeanNameGenerator
    |   \-- ScopeMetadataResolver
    \-- ClassPathBeanDefinitionScanner
        |-- AnnotationBeanNameGenerator
        \-- ScopeMetadataResolver

The whole monstrous Spring infrastructure is built on a small-yet-solid foundation of context infrastructure. Once we manage to understand that foundation, the whole thing lies open in front of us.

License

This article, along with any associated source code and files, is licensed under The Creative Commons Attribution-Share Alike 3.0 Unported License


Written By
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
SuggestionMissing reference to Part 1 Pin
Klaus Luedenscheidt10-Oct-17 19:15
Klaus Luedenscheidt10-Oct-17 19:15 
GeneralRe: Missing reference to Part 1 Pin
Roman Nazarenko11-Oct-17 8:11
Roman Nazarenko11-Oct-17 8:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.