springboot custom starter

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

 

Posted on Sat, 30 Oct 2021 13:09:06 -0400 by RW