Explain in detail how to quickly implement functions like automatic configuration in the lower version of Spring

  • 2021-07-24 10:57:20
  • OfStack

After Spring 4, conditional annotations such as @ Conditional were introduced, which is the biggest contributor to the automatic configuration in Spring Boot!

Then the question arises: If we are still using the old version of Spring 3. x, how do we implement an automatic configuration at this time?

Needs and issues

The core appeal

Existing system, not intended to be reconstructed The version of Spring is 3. x, and there is no plan to upgrade the version and introduce Spring Boot It is expected that the function enhancement can be realized under the premise of less code change

For example:

I hope to add log records to the whole station system 1 (such as the summary information of RPC framework Web call and the summary information of database access layer), which is actually a general function. We have cited some infrastructures and want to make further enhancements to the functions of these infrastructures. At this time, we should solve this problem from the framework level.

Problems faced

3. Spring of x has no conditional annotation

Because there are no conditional annotations, we don't know when these things need/don't need to be configured

Unable to automatically locate the automatic configuration that needs to be loaded

There is no way for the framework to automatically load our configuration like the Spring Boot auto-configuration. We need to use one other means for Spring to load our customized functions.

Core solution ideas

Conditional judgment

Judge by BeanFactoryPostProcessor

Spring provides us with an extension point, and we can solve the problem of condition judgment through BeanFactoryPostProcessor. It allows us to do some post-processing on the definition of Bean after the definition of BeanFactory and before the initialization of Bean. At this time, we can judge our Bean definition, see which Bean definitions exist/are missing, and add one Bean definition-add one Bean customized by ourselves.

Configuration load

Writing the Java Config class Introducing configuration classes Through component-scan Through the XML file import

You can consider writing your own Java Config class and adding it to component-scan, and then find a way to make the component-scan of the current system include the Java Config class we wrote; You can also write XML file. If the current system uses XML, can we load our XML file on its loading path? If not, you can use manual import.

Two extension points provided by Spring

BeanPostProcessor

For the Bean instance Provide custom logical callbacks after Bean is created

BeanFactoryPostProcessor

Definition for Bean Get configuration metadata before the container creates Bean Java Config needs to be defined as static method (if not defined, Spring will report 1 warning at startup, you can try 1)

Some customizations about Bean

Since the two extension points of Spring have been mentioned above, here are some customization methods for Bean under 1.

Lifecycle Callback

InitializingBean / @PostConstruct / init-method

This part is about initialization. You can do some customization after the initialization of Bean. There are three ways here:

Implement InitializingBean interface Use @ PostConstruct annotations Specify 1 init-method in the ES1120EN file defined by Bean; Or specify init-method when using the @ Bean annotation

All these allow us to call specific methods after the Bean is created.

DisposableBean / @PreDestroy / destroy-method

This part is one of the operations we should do when Bean is recycled. You can specify that when this Bean is destroyed, if:

It implements the interface DisposableBean, so Spring will call its corresponding method You can also add an @ PreDestroy annotation to a method, which will be called on destruction Specify an destroy-method in the XML file defined by Bean; Or destroy-method is specified when using the @ Bean annotation, this method is called on destruction

XxxAware interface

ApplicationContextAware

The whole ApplicationContext can be injected through the interface, and we can get a complete ApplicationContext in this Bean.

BeanFactoryAware

Similar to ApplicationContextAware.

BeanNameAware

You can inject the name of Bean 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 with close or shutdown method name, it will be treated as destroy-method by Spring and will be called on destruction.

1 Some common operations

Determine whether a class exists

ClassUitls.isPresent()

Call ClassUitls. isPresent () provided by Spring to determine whether a class exists under the current Class Path.

Determine whether Bean has been defined

ListableBeanFactory. containsBeanDefinition (): Determine whether Bean has been defined. ListableBeanFactory. getBeanNamesForType (): You can see which names have been defined for certain types of Bean.

Register an Bean definition

BeanDefinitionRegistry.registerBeanDefinition() GenericBeanDefinition BeanFactory.registerSingleton()

Roll up your sleeves and work hard

The theory is finished, so let's start practice.

In the current example, we assume 1 that the current environment is not using Spring, Boot and a higher version of Spring.

Step 1: Simulates a lower version of the Spring environment

The spring-context dependency is simply introduced here, and the version of Spring 3. x is not really used, but the features above Spring 4 are not used either.


<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: Taking the implementation of BeanFactoryPostProcessor interface as an example


@Slf4j
public class GreetingBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

 @Override
 public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
  throws BeansException {

  //  Judge the current  Class Path  Is there a required  GreetingApplicationRunner  So 1 Category 
  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;
  }

  //  Does it exist  id  For  greetingApplicationRunner  Adj.  Bean  Definition 
  boolean hasDefinition = beanFactory.containsBeanDefinition("greetingApplicationRunner");
  if (hasDefinition) {
   //  Current context already exists  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());
  }
 }
}

To register our Bean (see CustomStarterAutoConfiguration), there are several points to note:

The method here 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 component-scan of the project

@Configuration
public class CustomStarterAutoConfiguration {

 @Bean
 public static GreetingBeanFactoryPostProcessor greetingBeanFactoryPostProcessor() {
  return new GreetingBeanFactoryPostProcessor();
 }
}

Step 3: Verify that the automatic configuration is in effect

Add dependencies to 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 log (see custom-starter-examples) to verify that automatic configuration is in effect:


 .  ____     _      __ _ _
 /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/ ___)| |_)| | | | | || (_| | ) ) ) )
 ' |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: 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, auto-configuration-like functionality has been successfully implemented in a lower version of Spring. clap

The code is hosted by GitHub, welcome Star

Reference link

https://github.com/y0ngb1n/spring-boot-samples
https://github.com/digitalsonic/geektime-spring-family


Related articles: