Custom starter
Using a custom starter
Automatic assembly source code tracking
From spring MVC to spring boot, the biggest feature is that there are few or no configurations. Automatic assembly plays a great role. This blog will take you to understand the source code of automatic assembly and how to customize the starter yourself
Custom starter
First, create a springboot project. pom only needs to import the spring boot starter dependency without any other
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.6</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.datang</groupId> <artifactId>test1</artifactId> <version>1</version> <name>test1</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
This is the overall directory structure
There should be no fewer startup classes, because our customized functions still need to use the annotations of springboot. The @ SpringBootApplication of startup class is a composite annotation, which will help us automatically scan the annotations in other packages
package com.datang; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Test1Application { public static void main(String[] args) { SpringApplication.run(Test1Application.class, args); } }
AddLog class is our final function class. It simply receives two parameters and prints them out with the show method
package com.datang.app;
public class AddLog {
private String log1;
private String log2;
public AddLog(String log1,String log2) {
this.log1 = log1;
this.log2 = log2;
}
public void show() {
System.out.println(log1+"-----"+log2);
}
}
There is no one of the most core classes of LogAutoConfiguration. The four annotations @ Configurable on the class declare that the current class is a configuration class, which is generally used in conjunction with the method annotation @ bean. @ ConditionalOnClass(AddLog.class) this is a conditional annotation, If you haven't seen the source code of springboot, it will be strange. This annotation indicates that this bean will be configured only when AddLog.class is found in the classpath. Under what circumstances will there be AddLog.class in the classpath? Of course, when your project references the dependency of the current custom starter, there will be. @ EnableConfigurationProperties(LogProperties.class) This annotation must be used with @ Configurable, which means that a class needs to be used as a configuration class. We will see how to use it later. @ autoconfiguraeafter (LogProperties. Class) this is also a conditional annotation, which means that the current class needs to be registered as a bean after LogProperties. Why? Because our member variable uses the LogProperties.addLogBean() method body to create an AddLog object, and uses the two methods of the LogProperties bean to get the parameters to construct itself. There are two annotations @ bean, which is to register the AddLog bean, and the other is also a conditional annotation, It means that when there is no bean of AddLog type in the current Spring container, the object in the method body needs to be registered in the bean to prevent bean conflict
package com.datang.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Configurable; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import com.datang.app.AddLog; @Configurable @ConditionalOnClass(AddLog.class) @EnableConfigurationProperties(LogProperties.class) @AutoConfigureAfter(LogProperties.class) public class LogAutoConfiguration { @Autowired private LogProperties logProperties; @Bean @ConditionalOnMissingBean public AddLog addLogBean() { return new AddLog(logProperties.getLog1(), logProperties.getLog2()); } }
The last configuration class, @ ConfigurationProperties(prefix = "dfsn.log"), can read properties from yml or properties and encapsulate them into LogProperties objects. To open this annotation, you must use @ EnableConfigurationProperties
package com.datang.config; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "dfsn.log") public class LogProperties { private String log1; private String log2; public String getLog1() { return log1; } public void setLog1(String log1) { this.log1 = log1; } public String getLog2() { return log2; } public void setLog2(String log2) { this.log2 = log2; } }
Now the idea is very clear. Our customized starter function class is AddLog, which requires instance parameters. Its parameters are read from the configuration file by the LogProperties class. LogAutoConfiguration is used to load AddLog into the Spring container when the specified conditions are met. Finally, we need the last step, Configure a file according to the rules of springboot. The file name is fixed. It needs to be named spring.factories in the META-INF folder. The file content is a key value. Its key is also fixed. Value is the LogAutoConfiguration configuration class
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.datang.config.LogAutoConfiguration
Using a custom starter
Specify the dependency of the custom starter in the pom file
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.9.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <!--springBoot-start SpringBoot Startup item --> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.datang</groupId> <artifactId>test1</artifactId> <version>1</version> </dependency> <!-- <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> --> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Use @ Autowired to inject the application class in the custom starter to call the method
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.datang.app.AddLog;
@RestController
@SpringBootApplication
public class DemoApplication {
@Autowired
private AddLog addLog;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@GetMapping("test")
public String test() {
addLog.show();
return "success";
}
}
The two parameters configured in the yml file are the two parameters that LogProperties needs to read
# Spring configuration #spring: # redis configuration #redis: # address #host: 127.0.0.1 # Port, 6379 by default #port: 6379 dfsn: log: log1: I am log1 log2: I am log2
Automatic assembly source code tracking
When looking at the source code, we first need to know what the function we are looking for, and don't blindly enter the source code. First of all, we think about what springboot can do with automatic assembly. When we integrate Redis,mongoDB and other middleware, we generally only need to introduce dependency and yml configuration, and then we can use bean s. Think about it, springboot needs to integrate so many middleware, Is it necessary to have a file describing the middleware supported. Spring boot autoconfigure is the core project of spring auto assembly. There is a spring.factories file under MATE-INF, which has the org.springframework.boot.autoconfigure.EnableAutoConfiguration attribute, and its value is a collection. It is like our custom starter
Let's find a familiar org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration class, which is Redis's auto assembly configuration class
This Redis configuration class is no different from the core steps we wrote. It has an @ Import annotation, which is the < Import > tag in spring, indicating that this class has other configuration classes
Now we know that the step of springboot automatic assembly is to configure the configuration class of the middleware to be integrated in the spring.factories file. If it is customized, we also need to create a file with the same name under META-INF and use the same key. Then spring will find the configuration class according to the value. Read the condition annotation from the configuration class, Judge whether to register bean s. Two very important conditions in the condition annotation are @ ConditionalOnClass and @ EnableConfigurationProperties. They must have dependencies in the pom file and configuration in yml respectively. This is also required for our custom starter
Next, let's look at the core annotation of springboot. In addition to the meta annotation, SpringBootApplication has three annotations @ SpringBootConfiguration, @ enableautoconfiguration and @ ComponentScan. @ SpringBootConfiguration actually encapsulates @ Configuration, indicating that the current class is a Configuration class. @ ComponentScan is a package scanning ID, It is the same as the < context: component scan > tag in xml. Tell springboot to scan the files in a package to see if there are any annotations to parse. After springboot2, this annotation will scan the startup class package and its sub packages by default
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication {
@EnableAutoConfiguration annotation is the core annotation of automatic assembly. It imports the AutoConfigurationImportSelector class
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration {
getCandidateConfigurations of AutoConfigurationImportSelector returns a collection that contains many class full paths. All bean s in this collection meet the conditions and are automatically assembled
Now let's reverse the derivation to see where to filter out these qualified classes
Enter the method springfactoryesloader.loadfactorynames. This method uses the classLoader to go to public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; Find the configuration under the path. This path is used for springboot automatic assembly and our custom starter. In this way, the configuration classes of all classes that may need to be assembled are found
Go back to AutoConfigurationImportSelector again. Now that you have found these configuration classes, you need to see how to filter the qualified classes. Find out who called getCandidateConfigurations()
Next, let's see how OnClassCondition judges