Click here to Skip to main content
15,886,840 members
Articles / Web Development / Spring

Spring Context Internals: Part 1 — Refresh

Rate me:
Please Sign up or sign in to vote.
4.00/5 (1 vote)
8 Jul 2017CPOL19 min read 22.8K   1  
A deep introduction into Spring Context's refresh process internals from every-day use perspective

If you try hard enough, it will accidentally start working.

Introduction

For a long time, Spring has been a de-facto leader amongst Java application containers. Spring Project has tons of code, libraries, extensions, and services around, a monstrous and fearsome infrastructure I was afraid of even taking a look into. And from this idea alone, I thought of Spring Context as of the same monstrous entity the whole project is. But it is not. Looking back, I realized there were much more effective ways of doing many things. Then there was a moment when I decided to stop "putting out flames" and thus to resolve the very core of my Spring-related problems: I didn't know how it works. As the time was coming, it turned out that the basic context is a simple and somewhat elegant extensible solution, and it actually takes a day or two to know it well enough for our everyday needs. This series has been created for those who have not yet become brave enough to step onto the steep Spring Context's learning curve.

In this part, the first one, we shall take a look at the overall bootstrapping process of a single application context. Context inheritance, proxying, and autowiring are to be explained in subsequent series. An article is presented as a sequence of chapters chronologically representing different stages of context bootstrapping. Since this article is intended for readers with various levels of Spring proficiency, there are "Reference" side notes that explain Spring basics for those who've not yet got familiar with specific parts of it.

Reference: ApplicationContext, BeanFactory

ApplicationContext is a top-level entity usually holding your entire Spring application. All the entities described in this (and subsequent) article are stored inside a context. Regarding dependency injection, a BeanFactory is a central part of a context. Actually, I thought for a long time that a context is itself just a bean storage, and a bean factory is just a "market interface", but it is far from the truth. It turns out, that, aside from BeanFactory, a context usually stores MessageSource, EventMulticaster, ConversionService, various context-specific trickery, and a lot of runtime caches. A useless fact: a context sometimes implements these service interfaces itself, redirecting their calls to a corresponding internal field, yet I can't put this knowledge to any good use from the moment I found it out.

BeanFactory is a place where all the managed beans of your context are stored in. A central place of a context, it's like Surströmming: whatever you're doing with your context, whatever you choose to work with, you only have THAT smell. A bean factory is so central that few actually know it's a separate class.

Managed bean is a bean with a manager. Not the one that yells on you that you must stop playing darts for three hours in a row instead of doing your job, but rather one that helps you with side tasks unrelated to your current goal. Managed beans thus rely on some support infrastructure provided by a corresponding container (e.g. use EJB's transaction capabilities, CDIs scoping, Spring's event system, etc.).

Context Preparation

First of all, a context initializes its basic fields and properties for future use. At this point, no user-defined code is expected to be executed, even the earliest BeanDefinitionRegistryPostProcessor cannot run at this stage. The latter makes this stage the most boring one, as you can hardly break anything here. Notable milestones of this preparation are:

  • Register service entities in a bean factory: ApplicationContext itself, BeanFactory itself, SystemProperties, Environment, and so on. From now on, if you try to autowire an ApplicationContext interface from within the container, you will get one even though no one has ever declared it in any configuration file.
  • Register service ApplicationContextAwareProcessor: from now on, your ApplicationContextAware, EnvironmentAware and all these service interfaces are served by this small post-processor.
  • Register ApplicationListenerDetector: from now on, all your ApplicationListener implementations (Spring's internal event model listeners) are automatically registered within the context upon instantiation.

Reference: Aware Interfaces

A family of Aware interfaces allows beans to explicitly declare its dependent state regarding various context-related metadata. The most common ones are ApplicationContextAware and BeanFactoryAware, while you can see a lot of them around: EnvironmentAware, BeanNameAware and so on. These interfaces just declare a setter method with a corresponding class in its signature. I had been thinking that all this Aware stuff is just a fine name for service entities autowiring before I came into a context with no autowiring... If you can't autowire, and you need a bean without explicit name, like ApplicationContext (registerResolvableDependency creates them), you have no other choice, so Aware interfaces are in their own right here.

Also, BeanFactoryAware, BeanNameAware, and BeanClassLoaderAware interfaces are not actually served by an ApplicationContextAwareProcessor. A BeanFactory does everything about them on its own. It usually costs you to find it out at the first time.

Reference: ApplicationListener, Environment

ApplicationListener is all about Spring event system. An interface that allows a component to react to different ApplicationEvents. It is the default way of handling Spring events, like ContextRefreshedEvent.

Environment is an interface related to Spring's profiles. It provides API for retrieving configuration properties (system properties, app.properties, JVM arguments) and allows a developer to check profile's applicability. There were times when we were using raw properties to choose a bean impl at our @Bean methods, and I'm still not sure Spring profiles are any way better. Also, it is recommended to use profile-related annotations instead of this class.

There is an interesting caveat of how Spring adds its service objects to a bean factory. When I was a new-born Spring guy, I was trying to get these service objects from the context with variable success. The point is that Spring actually uses two different methods of introducing these service entities into the context.

The first one is registerResolvableDependency. In this scenario, Spring only binds an object with an interface to be submitted to bean factory's getBean method. That's it, BeanFactory, ApplicationContext, and, say, ResourceLoader are only available for autowiring. You cannot locate a bean with name "applicationContext" within a factory, and you cannot get it by its interface as well (unless you register it manually, of course). It was a dissatisfactory experience to get stuck into a hardly-reproducible debug point with only a bean factory available, seeking for a context bean, bruite-forcing its name back then. They say it's hard to find a black cat in a dark room, especially if it's not there. So, you should either have a good eye, or you should always remember about registerResolvableDependency. The good point is that you can just extract your context by ripping bean factory's resolvableDependencies field, but it's an 18+ experience in production code. So, don't spend your time in attempts to guess these service beans' name: there's none. It is of no meaning to list these beans here, as the list is a subject to change, yet you can always find an actual one by inspecting AbstractApplicationContext.prepareBeanFactory.

Java
public class ResolvableDependencyDemo {
	
	@Autowired
	public BeanFactory beanFactory;	// This will work

	public void init() {
		// This will not work
		BeanFactory found = beanFactory.getBean("beanFactory");
		// This will not work either
		BeanFactory found = beanFactory.getBean(BeanFactory.class);
		// You can usually debug through 
		// beanFactory.resolvableDependencies.get(BeanFactory.class)
	}
}

The second group is registerSingleton one. This one is boring because its beans are legal beans: even though they are created outside of context's standard bean instantiation lifecycle, their registration is fully legitimate. They even have their corresponding names! So, you can run getBean("systemProperties"), and your properties source will be emerged from the deeps of the context! But I have not found any evidence anyone uses them anyway.

An interesting fact:

Whilst AplicationContext, Environment, and other service objects are registered within the context at this stage, the latter will still use their original versions stored inside various AbstractApplicationContext fields. Thus it is not a great idea to play with their bean definitions inside the context: you can end up with parts of your application relying on different service objects. A "happy debugging" case.

All that bean post processor registration thing might seem secondary, but it is actually a heart of Spring Context. Almost everything here is done via various post-processors. Both autowiring and property injections (@Value annotation) are done by them. You can add your own annotations and injections. You can even add Guice's annotations support on your own! So, here it is: instead of digging through the whole context hierarchy in attempts to understand a tiny bit of Spring's logic, you can just master your contexts basics. Your colleagues will marvel at your X-Ray vision and foresighting capabilities.

Reference: post-processors

Spring's post-processors are SPIs (extension points) that allow third-parties to interfere with various context operations. Post-processors are logically divided into per-context (BeanFactoryPostProcessor) and per bean ones (BeanPostProcessor). Post-processor usually gets a BeanFactory instance and some service entities, depending on a type of a post-processor, and is expected to change them. Post-processors are thus tightly bound to their arguments, though with due proficiency one can stretch these bounds up to almost no limit. But we must always remember that Jedi thing with responsibility and all that stuff.

Running Factory Post-processors

Once all the preparations are done, Spring opens a small window for us to manipulate its bean factory. At this point, you have no ConversionService, no MessageSource, your beans instantiation usually breaks at this point and this is fine. If you do not understand that ConversionService or MessageSource stuff above, it is all right: I do neither. Fat chance, I just shall explain it below.

Well, your bean instantiation doesn't break, yet you might accidentally find out that you have no container transaction handlers installed, or, say, your session-scoped beans are more of "session", than of "scoped". But I can tell you from my experience, there is a lot of ways of loosing your transaction handlers, and this is not the worst one, so feel free to try. Also, third-party developers usually rely on BeanPostProcessor implementation for their context extensions, and none of these post-processors has been detected yet, so these extensions won't be applied. Yet seriously, it is highly recommended that you never instantiate any bean at this stage.

The window mentioned above consists of two casements: BeanDefinitionRegistryPostProcessor and BeanFactoryPostProcessor. The only purpose of these post-processors is to perform some factory-wide work by adding, removing, or modifying bean definitions. The only difference is that registry one opens first. Have you ever thought of how to merge Spring Context with Guice? Create your very own automated Mockito mocker? Or maybe you wanted to manually create beans depending on which services are available at your service discovery? Then BeanFactoryPostProcessor is a perfect place!

Despite these processors being powerful enough, they are actually tied by container restrictions: you can only manipulate bean definitions here. So, you will usually need both BeanFactoryPostProcessor and BeanPostProcessor to be implemented in order to achieve anything: factory-centric one populates bean definition attributes, while bean-centric one does all the job by applying these attributes to an actual instance.

I had always thought third-party IoC container integration is a complicated procedure with a lot of undocumented trickery and uncertainty. When I was working at a small contract software development firm, we had a huge issue with mixing third-party Guice with our Spring-centric applications. There were tons of smelly code to just work it around, direct Guice injector's invocations and hours of arguing on these "design decisions". If only any of us knew it actually takes ~10 lines of code to do a basic integration... Here is a simplified Guice integration for Spring. It does not support bean proxying (proxying will be reviewed in further articles), yet it illustrates power behind Spring's BeanFactory post-processors:

Java
@Slf4j
@RequiredArgsConstructor // #1
public class GuiceRegistrar implements BeanDefinitionRegistryPostProcessor { // #2

    private final Injector injector; // #3

    @Override
    public void postProcessBeanDefinitionRegistry(
					BeanDefinitionRegistry registry) throws BeansException {
        for (Key<?> key : injector.getBindings().keySet()) { // #4
            final Class<?> clazz = (Class) key.getTypeLiteral().getType();
            AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
            		.genericBeanDefinition(clazz)
                    .setScope(BeanDefinition.SCOPE_PROTOTYPE) // #5
                    .getBeanDefinition();
            registry.registerBeanDefinition(clazz.getName(), beanDefinition); // #6
        }
    }

    @Override
    public void postProcessBeanFactory(
    				ConfigurableListableBeanFactory beanFactory) 
    					throws BeansException {
        for (Key<?> key : injector.getBindings().keySet()) {
            final Class<?> clazz = (Class) key.getTypeLiteral().getType();
            String factoryBeanName = "&" + clazz.getName(); // #7
            beanFactory.registerSingleton(factoryBeanName, new FactoryBean<object>() {

                @Override
                public Object getObject() throws Exception {
                    return injector.getInstance(key); // #8
                }

                @Override
                public Class<?> getObjectType() {
                    return clazz; // #9
                }

                @Override
                public boolean isSingleton() {
                    return false; // #10
                }
            });
        }
    }
}

Here's what's going on:

  1. We use a little of Lombok to generate some constructor/logging boilerplate for us.
  2. We declare a BeanDefinitionRegistryPostProcessor. Technically, we can just cast a regular BeanFactory to a BeanDefinitionRegistry and do all the manipulations in one place. This post-processor must be registered in the context (a simple @Bean method or <bean> declaration will do), and a context will take care of the rest.
  3. This is Guice's analog of BeanFactory. Injector holds Guice's bean hierarchy and provides necessary metadata. It is a bean source we shall integrate into our application.
  4. This is Guice's replacement for ListableBeanFactory.getBeanDefinitionNames(): we get a list of Guice's dependency definitions. We want all of them to be introduced into our BeanFactory.
  5. We make all our Guice beans of "prototype" scope. This is the simplest way to respect Guice's scopes without manual mapping.
  6. We register our new bean definition. Nota bene, we have not yet introduced any means of bean instantiation Spring could use.
  7. This is it! The symbol at the left means this is a bean factory for clazz.getName()-named bean. Spring will handle it on its own and will call a factory bean below for any clazz.getName() instantiation attempt.
  8. On every bean creation request, we just redirect the call to Guice.
  9. This is quite important. We shall revisit it later.
  10. We don't want BeanFactory to cache our instance.

That's it! This little class alone actually allows us to use Guice-provided dependencies for Spring beans! No monstrous class hierarchies, no reflection magic, just a dozen of lines of standard Spring code. It will work with default Guice bean scope, as well as with @Singleton, though custom scopes can be a bit difficult to support, e.g. EJB's Stateless-alike scope type, @SessionScoped, and so on. Their support requires us to apply bean proxying depending on an underlying scope. Nevertheless, the above example covers the most of Guice's functionality one would usually like to integrate his or her application with. You should pay due attention to this example, as it is vital for Spring post-processors understanding and for further reading.

About non-invasive SPIs While Spring itself moves towards under-the-hood magic with out-of-the-box auto-configuring solutions, I wouldn't recommend that. These tricks look fine in advertisement, yet people usually meet with their disadvantages in a way painful to the hell. If I could return all these hours spent on resolving Spring's auto-configured crashing nightmare, I'd have enough time to make this article a poem (including special code chars and non-breaking spaces). When all our dependencies are declared at our XML context descriptor, we always have a solid schema of the whole software infrastructure. Once auto-configuring and manual bean definition registration kicks in, your context becomes terra incognita in a blink!

Registering BeanPostProcessors

Once the context is ready, we can start actually instantiating whatever is configured inside your bean factory. BeanPostProcessors come first, as they may intercept service beans instantiation. Like BeanFactoryPostProcessor, BeanPostProcessor interferes with factory's internal routines for the sake of extending Spring's functionality. You can, for instance, replace all the textual fields in your application with Hamlet. It has no actual meaning, yet it looks extremely good in debugger! Unlike BeanFactoryPostProcesor, this one interferes on per-bean basis rather than per-factory. It also can only interfere with beans at specific points of their lifetime (during initialization, during dependency injection, during destroyal). It is thus impossible to trigger bean instantiation from this bean's post-processor, but you can alter your bean by changing its properties, calling its methods, registering it somewhere, and so on.

Bean post-processors are cached inside BeanFactory to speed-up bean instantiation. All the bean adding methods contain explicit check for BeanPostProcessor interface, that triggers putting a bean into the cache.

Reference: BeanPostProcessor

BeanPostProcessor is a common interface for interfering with bean creation. While the default one is rather limited, various specific post-processor interfaces, like MergedBeanDefinitionPostProcessor or DestructionAwareBeanPostProcessor provide a lot of ways of changing the way a bean works. @PreDestroy annotation handling, autowiring, all this kind of stuff is made using various bean post-processors. You can write one that autowires nulls into random beans. Works best right before pre-Christmas release.

Service Beans Initialization

Then, here come several internal beans:

  1. MessageSource is instantiated, if missing in your bean factory.
  2. EventMulticaster is instantiated, if missing in your bean factory.
  3. Extensible onRefresh() is called.
  4. Now, all the event listeners are being looked up in the context and added into EventMulticaster. No bean instantiation takes place, the container just puts bean names instead of their instances.
  5. ConversionService is instantiated, if missing in your bean factory.

Reference: MessageSource, ConversionService

MessageSource is an interface for providing locale-dependent text strings for your application's captions by a short name (usually property-like). Spring templating engine uses MessageSource for getting translations. The most common implementation of the interface just takes application's resource bundles, like "resources/message.properties", "resources/message_fr.properties", "resources/message_ru.properties", and then looks for the best match for the locale provided in the request. A developer can provide any other implementation, including network-based, as a messageSource bean, and a context will use it instead. From my experience: a network-based translation loading infrastructure mentioned above gets dropped before the first release anyway because no one actually needs it. You can save some time by dropping it BEFORE it's implemented.

ConversionService interface is one of the built-in ways, along with Java's PropertyEditor, of converting types Java usually fails to convert itself. Yes, like any other respected framework, Spring has its own type conversion infrastructure. When I was working on adopting Cucumber Spring into our project, it was a tough call to decide which of their conversion services I want to use. In Spring, a ConversionService backs property editors up in @Value injections, it also provides type conversion for controller arguments, Spring Data repositories, and any other third-party Spring-centric technology that wants to benefit from unified conversion infrastructure. You can extend or override this service by providing a bean named conversionService into your bean factory. It is generally safe because any framework consisting of more than two files will probably invent their own type conversion infrastructure, and you won't break it by overriding Spring's one.

The EventMulticaster is the heart of Spring's event system, and, honestly, it is the only explicit part of it. Even though it is registered that late, you won't lose your early events because of the dark work-arounding side of Spring Context here: it actually just stores all the events in a simple list and then sends them after all the listeners are loaded at stage 4 described above. An elegant design as it is. The EventMulticaster is also an extension point itself: you can override the default implementation for, say, Hazelcast-powered clustered event processing, or for custom event filtering by providing a bean named applicationEventMulticaster. If we knew it in advance, we'd have less than four different event systems in our current project.

About Spring Events

While Spring is usually presented as a cutting-edge technology in Java, its event system is rather poor. If you want to experience a real event-handling pleasure, I would recommend looking at the latest CDI spec from Java EE. No-interface implementation and transaction binding usually make it a number one event system for any (or for all three) modern Java EE applications. You can, for instance, say that you only need an event to be processed if an enclosing transaction at the place of its origin has been successfully committed.

The onRefresh() invocation rarely bothers end-user, though this is a place where, for example, an embedded servlet container is created. I remember I had to scroll through four hundred of bean definition back then just to find out there is NO embedded servlet container in a bean factory. I wish I knew it is stored in a field in my top-level context. The container is just created by a corresponding context implementation from a factory stored in a context itself, no magic here:

Java
@Override
protected void onRefresh() {
  super.onRefresh();
  createEmbeddedServletContainer();
  // Try-catch is skipped for readability
}

private void createEmbeddedServletContainer() {
  EmbeddedServletContainer localContainer = this.embeddedServletContainer;
  ServletContext localServletContext = getServletContext();
  if (localContainer == null && localServletContext == null) {
    EmbeddedServletContainerFactory containerFactory
        = getEmbeddedServletContainerFactory();
    this.embeddedServletContainer = containerFactory
        .getEmbeddedServletContainer(getSelfInitializer());
  }
  // Here come other initialization options
}

At this point, the context is actually ready to be used. Any getBean call must work as expected, yet there are some steps left for context to finalize the refresh.

Finalize Context Refresh

There are some minor steps left:

  • Non-lazy singletons are instantiated upon refresh at this point.
  • Auto-starting SmartLifecycle implementations get their start() method called by the container. The container's lifecycle is officially begun at this point. Lifecycle processing is a broad topic itself, so we'll take a look at it in one of further articles in this cycle.
  • A ContextRefreshedEvent is sent. Now everyone knows we are "online". It is for beans only relying on refreshing, with no need to react to a container lifecycle end (unlike LifecycleListeners).
  • LiveBeansView is registered in JMX. This is a last operation and it does not block other beans from functioning, as all the lifecycle events are sent.

Actually, this singletons instantiation step is the most complicated and interesting amongst all the above. Bean definition merging, post-processing, proxying, method overriding, cyclic references protection, the world on its own to discover. I'm going to make a separate article dedicated solely to Spring's bean instantiation process, so don't miss the update! For now, we shall just say that all our beans has magically been instantiated at once.

About Context Lifecycle Events

As one could've seen before, along with a ContextRefreshedEvent, there are also a ContextStartedEvent and a ContextStoppedEvent hanging around. While these seem to be perfectly related to whatever we're talking about right now, you will rarely see them around your application. The problem is that both the above event types are related to ApplicationContext's start() and stop() invocations. While context has technically been started, unless there was a start() call, you will not receive a ContextStartedEvent anywhere. That's why it always takes at least two compile-and-run cycles to handle an application start event from within your container: first time you always try to catch start instead of refresh.

Manual Context Refresh

All the context bootstrapping operations described above are performed during a phase called "Context Refresh". While the context always gets refreshed at the beginning of its lifecycle, it is sometimes possible to trigger further refresh manually. Not all contexts are refreshable, yet the commonly used XMl-, Annotation- and Groovy-based are usually refreshable. Upon manual refresh, all the previously instantiated beans will be destroyed, a bean factory will be replaced with a new one, and all the above steps will be repeated. Such a refresh is usually handy if you need an application to re-read underlying context description (XML files, annotated classes), yet you don't need it to be fully restarted by whatever reason. It is so useful that I have no evidence of anyone calling this method anywhere outside an application context itself.

Conclusion

Here are all the above steps in a form of a cheat sheet:

  • Prepare context
    • Register internal beans in a BeanFactory (ApplicationContext, Environment, etc.)
    • Register *Aware interfaces' BeanPostProcessor
    • Register event listeners' BeanPostProcessor
  • Bean factory post-processing
    • Running BeanDefinitionRegistryPostProcessors from ApplicationContext
    • Running BeanDefinitionRegistryPostProcessors from BeanFactory
    • Running BeanFactoryPostProcessors from ApplicationContext
    • Running BeanFactoryPostProcessors from BeanFactory
  • BeanPostProcessors registration
  • Service beans registration
    • MessageSource for translations
    • EventMulticaster for events
    • Multicasting early events
    • ConversionService for type conversions
  • Context finalization
    • Singletons instantiation
    • SmartLifecycle.start() called on auto-starting beans
    • ContextRefreshedEvent sent
    • LiveBeansView registered in JMX

The great idea of Spring Context is that it is designed to be as lightweight as possible. Even though it handles a bunch of workarounds for some historical problems, like early events/listeners list, it delegates a lot of work to various post-processors, which can be employed for our every-day needs if we know what's going on.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


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

 
-- There are no messages in this forum --