This chapter delves deeper into Spring Core’s capabilities beyond basic Inversion of Control (IoC), exploring various services that supplement and extend its core functionalities.
Specifically, it will cover:
-
Managing the bean life cycle: Strategies for beans to receive notifications from the Spring container at different life cycle points, using interfaces, reflection, or JavaBeans annotations.
-
Making beans "Spring aware": How beans can interact with their
ApplicationContextinstance using interfaces likeBeanNameAwareandApplicationContextAware. -
Using FactoryBeans: Implementing beans that act as factories for other beans, integrating with the Spring
BeanFactory. -
Working with JavaBeans PropertyEditor implementations: Understanding how Spring uses
PropertyEditorsfor type conversion, including supplied and custom implementations. -
Learning more about the Spring ApplicationContext: A detailed look at `ApplicationContext’s additional features beyond IoC, such as internationalized message support, resource loading, and event publishing, including its use in web applications.
-
Testing Spring applications: Methods for creating Spring test contexts to effectively test Spring applications.
-
Using Spring Boot: How Spring Boot simplifies the creation of stand-alone, production-grade Spring applications.
-
Using configuration enhancements: Features like profile management and environment/property source abstraction to streamline application configuration.
-
Using Groovy for configuration: Exploring Groovy as an alternative or supplement to XML and Java-based bean configuration.
|
Spring’s Impact on Application Portability
The text discusses the trade-off between using Spring-specific features and ensuring application portability across different IoC containers. While avoiding Spring-specific features might seem to enhance portability, it means sacrificing the extensive functionality Spring offers.
The author advises against creating unnecessary portability requirements, as end-users typically prioritize functionality over the ability to run on multiple IoC containers, and building on the "lowest common denominator" can disadvantage an application.
However, the text argues that coupling an application to Spring can paradoxically increase its portability in a wider scope. As a freely available, open-source, vendor-agnostic framework, Spring allows applications to run wherever Java runs. It provides capabilities similar to JEE and simplifies many aspects of it, enabling sophisticated web applications to run in simpler servlet containers by replacing vendor-specific or configuration-dependent JEE features with Spring’s equivalents.
|
Bean Life-Cycle Management
Spring IoC containers, including Spring, allow beans to receive notifications at specific points in their lifecycle to perform relevant processing. The two key events are post-initialization and pre-destruction.
-
Post-initialization occurs after Spring has set all property values and completed any configured dependency checks.
-
Pre-destruction is fired just before Spring destroys the bean instance.
A notable exception is that beans with prototype scope will not receive the pre-destruction event, although they do receive post-initialization notifications.
Spring provides three mechanisms for beans to hook into these lifecycle events:
-
Interface-based mechanism: The bean implements a Spring-specific interface (e.g.,
InitializingBean,DisposableBean), and Spring invokes a defined callback method. -
Method-based mechanism: You specify the names of initialization and destruction methods in your
ApplicationContextconfiguration. -
Annotation-based mechanism: You use JSR-250 annotations (
@PostConstructfor post-initialization,@PreDestroyfor pre-destruction) on methods within the bean class.
While all three achieve the same goal, the choice depends on application requirements. Interface-based mechanisms are widely used within Spring itself and can be convenient for many beans of the same type or when coupling to Spring is already present. However, for your own beans, method-based or annotation-based (especially JSR-250, which is a standard) approaches offer better portability as they don’t require implementing Spring-specific interfaces. If a bean is intended to be self-contained and used across different Spring projects, the interface-based mechanism is recommended. When using annotations, ensure your IoC container supports the JSR-250 standard.
|
Hooking into Bean Creation
Spring bean initialization callbacks allow a bean to verify if all its required dependencies are met and apply custom logic to dependency resolution, such as providing defaults for optional dependencies. This is crucial because Spring’s automatic dependency checking is an all-or-nothing approach.
These callbacks are invoked after Spring has finished injecting all possible dependencies, making them suitable for checks that cannot be performed in the constructor (as dependencies aren’t available yet). Beyond dependency validation, initialization callbacks are also ideal for triggering actions that a bean must perform automatically based on its configuration, such as starting a scheduler.
|
Executing a Method When a Bean Is Created
Spring allows you to define an initialization method for a bean, which is invoked after the bean’s properties have been set. This callback mechanism is beneficial for:
-
Decoupling your application from Spring.
-
Integrating pre-built or third-party beans.
-
Managing a small number of similar beans.
To designate an initialization method, you specify its name using the initMethod attribute within the @Bean annotation in Java configuration classes.
The provided example demonstrates this with a Singer bean that includes an init() method. This init() method performs validation and sets default values:
-
If the
nameproperty is null, it assigns aDEFAULT_NAME. -
If the
ageproperty is not set (i.e.,Integer.MIN_VALUE), it throws anIllegalArgumentException.
In the SingerConfiguration, three Singer beans are defined, all using init() as their initialization method:
-
singerOne: Has bothnameandageset, soinit()makes no changes. -
singerTwo: Lacks aname, soinit()assigns theDEFAULT_NAME. -
singerThree: Lacks anage, causinginit()to throw anIllegalArgumentException, which Spring wraps in aBeanCreationException, preventing the bean’s creation.
This approach ensures that beans are correctly configured and validated before they are used. The initialization method must not accept any arguments, though its return type is ignored. While static initialization methods are possible, non-static methods are generally preferred for validating instance-specific state.
|
Implementing the InitializingBean Interface
The InitializingBean interface in Spring allows developers to execute custom code within a bean immediately after Spring has finished configuring it. This interface defines a single method, afterPropertiesSet(), which serves the same purpose as a traditional initialization method.
This post-configuration hook is useful for:
-
Validating the bean’s configuration to ensure it’s in a valid state.
-
Providing default values for properties if they haven’t been explicitly set.
The provided example demonstrates a Singer class implementing InitializingBean. Its afterPropertiesSet() method checks if the name property is null and sets a default if so, and it throws an IllegalArgumentException if the age property is not set. Using InitializingBean eliminates the need to specify an initMethod attribute in the bean’s configuration, yielding identical results to the initMethod approach.
|
Using the JSR-250 @PostConstruct Annotation
JSR-250 annotations, specifically @PostConstruct, are supported by
Spring (starting from version 2.5) to define bean lifecycle
initialization methods.
Key Points:
-
Purpose:
@PostConstructmarks a method that Spring should call after a bean’s properties have been set but before the bean is fully ready for use. -
Example (
Singerclass): The providedSingerclass uses@PostConstructon apostConstruct()method to perform initialization logic, such as setting a default name or validating theageproperty. -
Alternatives:
@PostConstructis an alternative to:-
Using
initMethodattribute with@Bean(e.g.,@Bean(initMethod="myInitMethod")). -
Implementing the
InitializingBeaninterface.
-
-
Method Naming: The method annotated with
@PostConstructcan have any name (e.g.,postConstructis just a convention). -
Comparison of Approaches:
-
@Bean(initMethod=..):-
Benefit: Decouples application from Spring.
-
Drawback: Requires configuring the initialization method for each bean.
-
-
InitializingBeaninterface:-
Benefit: Specifies initialization callback once for all instances of a class.
-
Drawback: Couples the application to Spring.
-
-
@PostConstructannotation:-
Benefit: Applied directly to the method, clear intent.
-
Drawback: Requires the IoC container to support JSR-250.
-
-
-
Choosing an Approach:
-
For portability (less coupling to Spring), use
@Bean(initMethod=..)or@PostConstruct. -
To reduce configuration and potential errors, use
InitializingBean.
-
-
Private Initialization Methods: Both
@Bean(initMethod=..)and@PostConstructallow initialization methods to be declared asprivate. Spring can still call them via reflection, preventing accidental external calls and ensuring they are only invoked once during bean creation.
|
Understanding Order of Resolution
The provided text details the specific order in which Spring invokes various initialization mechanisms on a single bean instance. This order is a fundamental part of the Spring bean creation lifecycle:
-
Constructor Call: The bean instance is first created by calling its constructor.
-
Dependency Injection: Dependencies are injected, typically via setters (e.g., methods annotated with
@Autowired), which is handled by infrastructure beans likeAutowiredAnnotationBeanPostProcessor. -
Pre-Initialization Callbacks (
@PostConstruct): Before the main initialization, pre-initializationBeanPostProcessor-s are consulted. The@PostConstructannotated method is invoked at this stage (managed byCommonAnnotationBeanPostProcessor). This happens after construction and dependency injection, but beforeafterPropertiesSet()and theinitMethod. -
InitializingBean.afterPropertiesSet(): If the bean implements theInitializingBeaninterface, itsafterPropertiesSet()method is executed next, once all bean properties have been set. -
Custom Initialization Method (
initMethod): Finally, the method specified by theinitMethodattribute in the@Beanannotation is executed. This is considered the "actual" initialization method of the bean.
The AllInitMethodsDemo example, including the MultiInit class and MultiInitConfiguration, clearly demonstrates this precise order in its console output: Constructor → Setter (@Autowired) → @PostConstruct → afterPropertiesSet() → initMethod. Spring leverages internal BeanPostProcessor infrastructure beans (like CommonAnnotationBeanPostProcessor for @PostConstruct and AutowiredAnnotationBeanPostProcessor for @Autowired) to manage these steps.
|
Making Your Beans “Spring Aware”
Using the BeanNameAware Interface
The BeanNameAware interface in Spring allows a bean to obtain its own name within the Spring container.
Key aspects:
-
Method: It has a single method,
setBeanName(String beanName). -
Invocation: Spring calls this method after the bean has been configured but before any lifecycle callbacks (like initialization or destruction methods).
-
Implementation: Typically, the
setBeanName()method simply stores the provided bean name in a private field for later use by the bean. -
Usage: No special configuration is required to use
BeanNameAware. It’s often used to enhance log messages by including the bean’s name. -
Caution: While useful for internal purposes like logging, it’s advised not to give bean names business meaning solely to leverage
BeanNameAware. If a bean requires an internal "name" with business significance, it’s better to define a custom interface (e.g.,Nameable) and inject the name via dependency injection, keeping Spring configuration names concise and separate from business logic.
|
Using the ApplicationContextAware Interface
The ApplicationContextAware interface allows a Spring bean to obtain a reference to its configuring ApplicationContext instance. While it enables programmatic access to other beans via getBean(), this practice is generally discouraged in favor of dependency injection to avoid unnecessary coupling and complexity.
A key use case for ApplicationContextAware is demonstrated by automatically registering a shutdown hook. By implementing ApplicationContextAware and its setApplicationContext() method, a bean (like ShutdownHookBean) can check if the ApplicationContext is a GenericApplicationContext and then call registerShutdownHook() on it. This eliminates the need for manual ctx.registerShutdownHook() calls during application bootstrap, ensuring that preDestroy() methods on singletons are automatically invoked when the application shuts down.
|
Use of FactoryBeans
FactoryBean Example - The MessageDigestFactoryBean
The provided text explains how to integrate Java’s MessageDigest
class, which requires creation via a static factory method
(MessageDigest.getInstance()), into a Spring application using a
FactoryBean.
Here’s a summary of the key points:
-
Problem Statement: Java’s
MessageDigestclass cannot be instantiated directly withnew; it requiresMessageDigest.getInstance("algorithm"). Managing such objects in Spring without aFactoryBeanwould necessitate analgorithmNameproperty on the bean and an initialization callback to invokegetInstance(). -
Solution:
FactoryBean: Spring’sFactoryBeaninterface provides a way to encapsulate complex object creation logic. It allows other beans to declare a dependency on the product of theFactoryBean(e.g.,MessageDigest), rather than theFactoryBeanitself. -
MessageDigestFactoryBeanImplementation:-
It implements
FactoryBean<MessageDigest>andInitializingBean. -
afterPropertiesSet(): This callback method is used to create the actualMessageDigestinstance by callingMessageDigest.getInstance(algorithmName). ThealgorithmNamedefaults to "MD5" but can be configured. -
getObject(): Returns theMessageDigestinstance created inafterPropertiesSet(). This is the object that Spring injects into other beans. -
getObjectType(): Specifies that the factory produces objects of typeMessageDigest.class. -
isSingleton(): Indicates that the producedMessageDigestinstance is a singleton.
-
-
Consuming the `FactoryBean’s Product:
-
A
MessageDigesterbean is introduced, which has twoMessageDigestdependencies (digest1anddigest2). -
In the Spring configuration (
MessageDigestConfig), twoMessageDigestFactoryBeanbeans are defined (one for SHA1, one for MD5). -
The
MessageDigesterbean then explicitly obtains theMessageDigestinstances by callinggetObject()on the configuredMessageDigestFactoryBeanbeans, demonstrating how the factory provides the actualMessageDigestobjects for injection.
-
-
Benefits of
FactoryBean:-
It’s ideal for integrating classes that cannot be created directly using the
newoperator (e.g., those requiring static factory methods or complex setup). -
It acts as an adapter, allowing such objects to fully leverage Spring’s Inversion of Control (IoC) capabilities.
-
It simplifies the configuration for consuming beans, as they only need to declare a dependency on the product type, while the
FactoryBeanhandles the underlying creation complexity.
-
|
Accessing a FactoryBean Directly
This text explains how to directly access a FactoryBean in Spring. While Spring automatically provides the object produced by a FactoryBean, you can access the FactoryBean itself by prefixing the bean name with an ampersand (&) when calling getBean().
However, the text strongly advises against directly accessing the FactoryBean and manually calling getObject(). The FactoryBean is intended as internal infrastructure to support IoC, and directly interacting with it creates unnecessary coupling and extra work, potentially leading to issues if Spring’s implementation details change. It’s best to let Spring manage the object creation process.
|
JavaBeans PropertyEditors
Using the Built-in PropertyEditors
The provided content demonstrates a Spring bean, DiverseValuesContainer, which declares 14 properties of various types supported by Spring’s built-in PropertyEditor implementations. Although the properties are not Strings, their values are injected as simple String literals, and Spring automatically converts these Strings to the appropriate types using the built-in editors.
Key points include:
-
The bean properties cover types like byte arrays, Character, Class, Boolean, List, Date, Float, File, InputStream, Locale, Pattern, Properties, trimmed String, and URL.
-
Custom editors such as
CustomDateEditor(with a specified date format) andStringTrimmerEditorare registered explicitly via aPropertyEditorRegistrarimplementation because they are not registered by default. -
The example includes a
ValuesHoldercomponent providing some values (like a list and an InputStream) injected via Spring Expression Language (SpEL). -
Running the example shows Spring converting the String values to their respective types and injecting them, with logging output confirming the conversions.
-
Table 4-1 summarizes the main built-in Spring `PropertyEditor`s, describing their purpose, such as converting Strings to byte arrays, Characters, Classes, Booleans, Collections, Dates, Files, InputStreams, Locales, Patterns, Properties, trimmed Strings, and URLs.
Overall, this example illustrates how Spring’s built-in `PropertyEditor`s simplify configuration by automatically converting String property values to complex types, easing application setup with common components like files, URLs, and dates.
|
Creating a Custom PropertyEditor
-
When Spring’s built-in PropertyEditors are insufficient, you can create your own by extending java.beans.PropertyEditorSupport and overriding only setAsText().
-
Example domain class: FullName with firstName and lastName fields.
-
Custom editor (NamePropertyEditor):
-
Extends PropertyEditorSupport.
-
In setAsText(), splits the incoming string on the space and builds a new FullName instance, then calls setValue().
-
-
Registration:
-
Use a CustomEditorConfigurer bean.
-
Supply a Map where the key is the target type (FullName.class) and the value is the custom editor class (NamePropertyEditor.class).
-
-
Usage demo:
-
Person component has a FullName property.
-
@Value("John Mayer") triggers Spring to feed the string through NamePropertyEditor and inject the resulting FullName.
-
Running the application logs the correctly populated FullName object.
-
-
Key points:
-
Only one method (setAsText) needs implementation when you extend PropertyEditorSupport.
-
Custom editors are registered globally through CustomEditorConfigurer.
-
Since Spring 3, a newer Type Conversion API and Field Formatting SPI offer a more modern alternative for type conversion.
-
|