preface

I believe that if you have used Spring Boot, you will be very curious about this phenomenon:

Introduce a component dependency, add a configuration, and the component takes effect.

For example, we often use Redis in Spring Boot like this:

1. Introduce dependencies

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Copy the code

2. Write configurations

spring:
  redis:
    database: 0
    timeout: 5000ms
    host: 127.0. 01.
    port: 6379
    password: 123456
Copy the code

Ok, now you just need to inject the RedisTemplate to use it, like this:

@Autowired
private RedisTemplate redisTemplate;
Copy the code

In the meantime, what did we do? We didn’t do anything, so how does this RedisTemplate object get injected into the Spring container?

Next, let us take such a question step by step analysis of the principle, the principle is called automatic assembly.

SPI

Before we do that, let’s take a look at the SPI mechanism.

SPI, or Service Provider Interface, is a Service discovery mechanism. It automatically loads the classes defined in the files by looking for them in the META-INF/services folder under the classpath path.

chestnuts

Create a project with the following structure

Provider is the service provider, which can be understood as our framework

Zoo is for the user, because my service provides an interface called Animal, so all implementations are animals ~

There is nothing in pom.xml

1. Define an interface

Define the interface Animal in the Provider module

package cn.zijiancode.spi.provider;

/** * Service provider animal */
public interface Animal {

    / / call
    void call(a);
}
Copy the code

2. Use the interface

Introduce a provider in the Zoo module

<dependency>
  <groupId>cn.zijiancode</groupId>
  <artifactId>provider</artifactId>
  <version>1.0.0</version>
</dependency>
Copy the code

Write a kitten to implement the Animal interface

public class Cat implements Animal {
    @Override
    public void call(a) {
        System.out.println("Meow, meow, meow."); }}Copy the code

Write a dog to implement the Animal interface

public class Dog implements Animal {

    @Override
    public void call(a) {
        System.out.println("Woof woof!!"); }}Copy the code

3. Write a configuration file

Create a folder called meta-INF /services

In the new file folder cn. Zijiancode. Spi. The provider. The Animal

Yes, you read that correctly, the fully qualified class name of the interface is the filename

Edit the file

cn.zijiancode.spi.zoo.Dog
cn.zijiancode.spi.zoo.Cat
Copy the code

It contains the fully qualified class name of the implementation class

3. The test

package cn.zijiancode.spi.zoo.test;

import cn.zijiancode.spi.provider.Animal;

import java.util.ServiceLoader;

public class SpiTest {

    public static void main(String[] args) {
        // Use Java ServiceLoader for loadingServiceLoader<Animal> load = ServiceLoader.load(Animal.class); load.forEach(Animal::call); }}Copy the code

Test results:

Auf!!!!!! Meow, meow, meowCopy the code

The overall project structure is as follows:

Understand auto assembly with SPI

To review what we did, we created a file under Resources, put some implementation classes in it, and then loaded them through the ServiceLoader class loader.

Assuming that someone has already written the configuration and so on, we can just use this part of the code below to dispatch all the implementation classes related to Animal.

// Use Java ServiceLoader for loading
ServiceLoader<Animal> load = ServiceLoader.load(Animal.class);
load.forEach(Animal::call);
Copy the code

Further, what would happen if someone wrote the same code and injected all the implementation classes into the Spring container?

Wow, can’t I just fucking inject it and woof? !

I believe that we have a spectrum here

Look for configuration files in Spring Boot

In the SPI mechanism, this is done by placing a configuration file on the component. Is Spring Boot the same? Let’s look for it.

Open the redis component

Why, there is no document about automatic assembly in this, is our guess wrong?

Don’t worry, all spring-boot-starter-x component configurations are in spring-boot-AutoConfigura components

“Spring. factories” (” Spring. factories”

Key is EnableAutoConfiguration. EnableAutoConfiguration. Oh oh oh oh oh! Got it! Got it!

Scroll down to see if there are any Redis related.

Open up the RedisAutoConfiguration class and see what code is in it

OMG! Case solved! Case solved!

Now we have the configuration file: Spring. factories, which are used to automatically configure the factories.

You somehow read the spring.factories file, load all the auto-configuration classes into the Spring container, and then use Spring’s mechanism to inject the @beans of the configuration classes into the container.

Next, let’s learn what this certain way is ~

Some injection methods in Spring

Let’s look at some of the injection methods available in Spring

Talking about Spring, I’m a veteran, interested friends can see my Spring source code analysis series: zijiancode. Cn/categories /…

About the way of injection, I believe that small partners must be: this?

EnableXxxxx (@component, @bean, etc.)

Such as: EnableAsync open asynchronous, EnableTransactionManagement open transactions

Are you curious how this annotation works?

Check it out

Hey, there’s an Import annotation in there

Import annotations can be used in three ways

I know there must be some friends who know how to use the Import annotation, but in order to take care of the friends who don’t know, AH Jian still need to explain it

1. Common components

public class Man {

    public Man(a){
        System.out.println("Man was init!"); }}Copy the code
@Import({Man.class})
@Configuration
public class MainConfig {}Copy the code

Use the @import annotation on the configuration class and put the value into the Bean you want to inject

2. ImplementImportSelectorinterface

public class Child {

    public Child(a){
        System.out.println("Child was init!"); }}Copy the code
public class MyImport implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.my.source.spring.start.forimport.Child"}; }}Copy the code
@Import({MyImport.class})
@Configuration
public class MainConfig {}Copy the code

This injects an ImportSelector into Spring, and when Spring scans MyImport, it calls the selectImports method, which injects the classes from the String array returned by selectImports into the container.

3. The implementationImportBeanDefinitionRegistrarinterface

public class Baby {

    public Baby(a){
        System.out.println("Baby was init!"); }}Copy the code
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        BeanDefinition beanDefinition = new RootBeanDefinition(Baby.class);
        registry.registerBeanDefinition("my-baby",beanDefinition); }}Copy the code
@Import({MyImportBeanDefinitionRegistrar.class})
@Configuration
public class MainConfig {}Copy the code

Similarly, when Spring scans the class, it calls the registerBeanDefinitions method. In this method, we manually inject a Baby Bean into Spring. Theoretically, we can inject any Bean indefinitely this way

SpringBootApplication annotations

The only annotation we use when using the SpringBoot project is @SpringBootApplication, so it’s the only one we can do it with.

Hey! What did we find? EnableAutoConfiguration! Big clue, no problem

EnableAutoConfiguration is also essentially done by Import, and imports a Selector

Let’s take a look at the code logic

selectImports

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
  if(! isEnabled(annotationMetadata)) {return NO_IMPORTS;
  }
  AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
  return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
Copy the code

getAutoConfigurationEntry

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
  if(! isEnabled(annotationMetadata)) {return EMPTY_ENTRY;
  }
  AnnotationAttributes attributes = getAttributes(annotationMetadata);
  // Get the candidate configuration class
  List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
  // Remove duplicate configurations
  configurations = removeDuplicates(configurations);
  // Get the configuration to exclude
  Set<String> exclusions = getExclusions(annotationMetadata, attributes);
  checkExcludedClasses(configurations, exclusions);
  // Remove all configurations to exclude
  configurations.removeAll(exclusions);
  // Filter out configuration classes that do not have injection conditions, using Conditional annotations
  configurations = getConfigurationClassFilter().filter(configurations);
  // Notifies automatic configuration of associated listeners
  fireAutoConfigurationImportEvents(configurations, exclusions);
  // Returns all auto-configuration classes
  return new AutoConfigurationEntry(configurations, exclusions);
}
Copy the code

Let’s look at how do we read from the configuration file

getCandidateConfigurations

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
  // Here is the key, using SpringFactoriesLoader to load all configuration classes is not like our SPI ServicesLoader
  List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
                                                                       getBeanClassLoader());
  Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
                  + "are using a custom packaging, make sure that file is correct.");
  return configurations;
}
Copy the code

getSpringFactoriesLoaderFactoryClass

protectedClass<? > getSpringFactoriesLoaderFactoryClass() {return EnableAutoConfiguration.class;
}
Copy the code

In combination with the previous step, load the configuration file and read the configuration whose key is EnableAutoConfiguration

loadFactoryNames

public static List<String> loadFactoryNames(Class<? > factoryType,@Nullable ClassLoader classLoader) {
  String factoryTypeName = factoryType.getName();
  return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
Copy the code
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {

  try {
    // The FACTORIES_RESOURCE_LOCATION value is: meta-INF /spring.factories
    // Read meta-INF /spring.factories from your classpathEnumeration<URL> urls = (classLoader ! =null ?
                             classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                             ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
    // Read the contents of the file and encapsulate them into a map
    result = new LinkedMultiValueMap<>();
    while (urls.hasMoreElements()) {
      URL url = urls.nextElement();
      UrlResource resource = new UrlResource(url);
      Properties properties = PropertiesLoaderUtils.loadProperties(resource);
      for(Map.Entry<? ,? > entry : properties.entrySet()) { String factoryTypeName = ((String) entry.getKey()).trim();for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
          result.add(factoryTypeName, factoryImplementationName.trim());
        }
      }
    }
    cache.put(classLoader, result);
    return result;
  }
  catch (IOException ex) {
    throw new IllegalArgumentException("Unable to load factories from location [" +
                                       FACTORIES_RESOURCE_LOCATION + "]", ex); }}Copy the code

Over, after all, the focus of this section is the automatic assembly mechanism, you understand the principle of ok

Ps: Because the logic behind is actually quite complicated, expanded to say too much

summary

This paper introduces the principle of SpringBoot automatic assembly. We first warmed up through the SPI mechanism, and then deduced the principle of Spring automatic assembly according to the SPI mechanism. In the middle, we also reviewed the use of @import annotation, and finally solved the case successfully

Next section: Implementing a custom starter

If you want to know more exciting content, welcome to pay attention to the public account: programmer AH Jian, AH Jian in the public account welcome your arrival ~

Personal blog space: zijiancode. Cn/archives/sp…