The conditional annotations like
@Conditionalwere introduced only after Spring 4, and they are the biggest contributors to achieving automatic configuration in Spring Boot!
So the question arises: If we are still using the old version of Spring 3.x, how can we achieve automatic configuration?
The code is hosted on GitHub, feel free to Star 😘
Requirements and Issues#
Core Demands#
- Existing system, no plans for refactoring
- Spring version is 3.x, no plans to upgrade or introduce Spring Boot
- Expect to enhance functionality with minimal code changes
For example:
- Hope to uniformly add logging across the entire site (e.g., summary information for RPC framework web calls, summary information for database access layer), which is actually a common feature.
- We have referenced some infrastructure and want to further enhance the functionality of this infrastructure, which should be solved from the framework level.
Problems Faced#
-
Spring 3.x does not have conditional annotations
Without conditional annotations, we are unclear when we need or do not need to configure these things.
-
Unable to automatically locate the required automatic configuration
At this point, we cannot let the framework automatically load our configuration like Spring Boot's automatic configuration; we need to use other means to allow Spring to load our customized functionalities.
Core Solution Approach#
Conditional Judgments#
- Judging through
BeanFactoryPostProcessor
Spring provides us with an extension point, and we can use BeanFactoryPostProcessor to solve the problem of conditional judgment. It allows us to perform some post-processing on our Bean definitions after the BeanFactory is defined and before the Beans are initialized. At this time, we can judge our Bean definitions to see which Beans are present or missing, and we can also add some Bean definitions—adding some custom Beans.
Configuration Loading#
- Write Java Config class
- Introduce configuration class
- Through
component-scan - Through XML file
import
- Through
We can consider writing our own Java Config class and adding it to component-scan, then finding a way to make the current system's component-scan include our written Java Config class; we can also write an XML file. If the current system uses XML, we can check if our XML file can be loaded in the loading path; if not, we can manually import this file.
Two Extension Points Provided by Spring#
BeanPostProcessor#
- For Bean instances
- Provides custom logic callbacks after Bean creation
BeanFactoryPostProcessor#
- For Bean definitions
- Obtains configuration metadata before the container creates Beans
- In Java Config, it needs to be defined as a
staticmethod (if not defined, Spring will report awarningat startup, you can try it out)
Some Customizations for Beans#
Since the two extension points of Spring have been mentioned above, let's expand on some ways to customize Beans.
Lifecycle Callback#
-
InitializingBean/@PostConstruct/init-methodThis part is about initialization, allowing some customization after the Bean's initialization. There are three ways:
- Implement the
InitializingBeaninterface - Use the
@PostConstructannotation - Specify an
init-methodin the Bean definition's XML file; or specifyinit-methodwhen using the@Beanannotation
All of these allow us to call specific methods after the Bean is created.
- Implement the
-
DisposableBean/@PreDestroy/destroy-methodThis part involves operations we should perform when the Bean is being collected. We can specify that this Bean, when destroyed, if:
- It implements the
DisposableBeaninterface, then Spring will call its corresponding method - We can also add the
@PreDestroyannotation to a method, which will be called upon destruction - Specify a
destroy-methodin the Bean definition's XML file; or specifydestroy-methodwhen using the@Beanannotation, which will call this method upon destruction
- It implements the
XxxAware Interfaces#
-
ApplicationContextAwareThe entire
ApplicationContextcan be injected through the interface, allowing us to obtain a completeApplicationContextwithin this Bean. -
BeanFactoryAwareSimilar to
ApplicationContextAware. -
BeanNameAwareAllows the Bean's name to be injected into this instance.
If you are interested in the source code, see:
org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean
If the current Bean has a method namedcloseorshutdown, it will be treated by Spring as adestroy-methodand will be called upon destruction.
Some Common Operations#
Check if a Class Exists#
ClassUtils.isPresent()
Call Spring's ClassUtils.isPresent() to check if a class exists in the current Class Path.
Check if a Bean is Defined#
ListableBeanFactory.containsBeanDefinition(): Check if a Bean is defined.ListableBeanFactory.getBeanNamesForType(): You can see what names of certain types of Beans have already been defined.
Register Bean Definitions#
BeanDefinitionRegistry.registerBeanDefinition()GenericBeanDefinition
BeanFactory.registerSingleton()
Roll Up Your Sleeves and Get to Work#
The theory has been covered, now let's start practicing.
In the current example, we assume the current environment is: not using Spring Boot and higher versions of Spring.
Step 1: Simulate a Lower Version of Spring Environment
Here, we simply introduced the spring-context dependency and did not actually use Spring 3.x, nor did we use any features from Spring 4 or above.
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>io.github.y0ngb1n.samples</groupId>
<artifactId>custom-starter-core</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
Step 2: Example of Implementing the BeanFactoryPostProcessor Interface
@Slf4j
public class GreetingBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
// Check if the required GreetingApplicationRunner class exists in the current Class Path
boolean hasClass = ClassUtils
.isPresent("io.github.y0ngb1n.samples.greeting.GreetingApplicationRunner",
GreetingBeanFactoryPostProcessor.class.getClassLoader());
if (!hasClass) {
// Class does not exist
log.info("GreetingApplicationRunner is NOT present in CLASSPATH.");
return;
}
// Check if there is a Bean definition with id greetingApplicationRunner
boolean hasDefinition = beanFactory.containsBeanDefinition("greetingApplicationRunner");
if (hasDefinition) {
// The current context already has a greetingApplicationRunner
log.info("We already have a greetingApplicationRunner bean registered.");
return;
}
register(beanFactory);
}
private void register(ConfigurableListableBeanFactory beanFactory) {
if (beanFactory instanceof BeanDefinitionRegistry) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(GreetingApplicationRunner.class);
((BeanDefinitionRegistry) beanFactory)
.registerBeanDefinition("greetingApplicationRunner", beanDefinition);
} else {
beanFactory.registerSingleton("greetingApplicationRunner", new GreetingApplicationRunner());
}
}
}
Register our Bean (see CustomStarterAutoConfiguration), here are a few points to note:
- The method is defined as
static - When using, if the two projects are not in the same package, you need to actively add the current class to the project's
component-scan
@Configuration
public class CustomStarterAutoConfiguration {
@Bean
public static GreetingBeanFactoryPostProcessor greetingBeanFactoryPostProcessor() {
return new GreetingBeanFactoryPostProcessor();
}
}
Step 3: Verify if the Automatic Configuration is Effective
Add dependencies in other projects:
<dependencies>
...
<dependency>
<groupId>io.github.y0ngb1n.samples</groupId>
<artifactId>custom-starter-spring-lt4-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>io.github.y0ngb1n.samples</groupId>
<artifactId>custom-starter-core</artifactId>
</dependency>
...
</dependencies>
Start the project and observe the logs (see custom-starter-examples), to verify if the automatic configuration is effective:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.0.RELEASE)
2019-05-02 20:47:27.692 INFO 11460 --- [ main] i.g.y.s.d.AutoconfigureDemoApplication : Starting AutoconfigureDemoApplication on HP with PID 11460 ...
2019-05-02 20:47:27.704 INFO 11460 --- [ main] i.g.y.s.d.AutoconfigureDemoApplication : No active profile set, falling back to default profiles: default
2019-05-02 20:47:29.558 INFO 11460 --- [ main] i.g.y.s.g.GreetingApplicationRunner : Initializing GreetingApplicationRunner.
2019-05-02 20:47:29.577 INFO 11460 --- [ main] i.g.y.s.d.AutoconfigureDemoApplication : Started AutoconfigureDemoApplication in 3.951 seconds (JVM running for 14.351)
2019-05-02 20:47:29.578 INFO 11460 --- [ main] i.g.y.s.g.GreetingApplicationRunner : Hello everyone! We all like Spring!
At this point, we have successfully implemented automatic configuration-like functionality in lower versions of Spring. 👏