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. ImplementImportSelector
interface
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 implementationImportBeanDefinitionRegistrar
interface
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…