Read the harvest

  • 👍🏻 Learn to customize spring-boot-starter
  • 👍🏻 Understand SpringBoot automatic configuration

Download this chapter source code

  • ❤ ️ development starter
  • ❤ ️ testing the starter

What is a Starter

Starter is a very important concept in Spring Boot. It is equivalent to a module. It can integrate the dependencies required by the module and automatically configure the beans in the module according to the environment (condition).

Spring Boot can automatically scan and load corresponding modules and set default values, which is out of the box

Why Starter

In our daily development work, there are often some independent configuration modules outside the business, we often put them in a specific package, and then if another project needs to reuse this function, we need to hard copy the code to another project, re-integration, extremely troublesome.

If we package these power configuration modules that can be independent of business code into a starter, and set the default value in the starter, we only need to reference and rely on them in POM when reuse, Spring Boot will complete automatic assembly for us, out of the box.

Springboot is automatically configured

Starter is a very important mechanism in SpringBoot. It can discard the complicated configuration and integrate it into starter. Users only need to introduce the starter dependency in Maven. Spring Boot automatically scans the Spring. factories files in the classpath path of each JAR package, loads the auto-configuration classes, loads the beans, and launches the default configuration.

Spring Boot provides the spring-boot-starter dependency module for daily enterprise application development scenarios. All of these dependency modules follow the conventions of default configurations and allow us to tweak those configurations, following the “convention over configuration” philosophy.

You can have a look at my previous article, a detailed introduction to the springboot automatic configuration process: a understand 🔥 Springboot automatic configuration principle – nuggets (juejin. Cn)

spring.factories

If the Starter and the Starter are not in the same main package, you need to configure the Spring. factories file to make it work. The spring. Factories files in the classpath path of each JAR are loaded by default. Configuration of key is org. Springframework. Boot. Autoconfigure. EnableAutoConfiguration

Starter develops common annotations

SpringBoot loads autoconfiguration classes in spring.factories that define runtime judgments. ConditionalOnMissingBean(A.class), for example, is valid only if there is no specified a-type bean information in the IOC container.

@Conditional is a new annotation provided by Spring4, which makes a judgment according to certain conditions and registers the bean to the container if the conditions are met.

  • Attribute mapping annotation
    • ConfigurationProperties: Mapping of configuration file attribute values to entity classes
    • @ EnableConfigurationProperties: and @ ConfigurationProperties, add @ ConfigurationProperties modified class to the ioc container.
  • Configure bean annotations
    • @configuration: Identifies this class as a Configuration class and injects it into the IOC container
    • @bean: Used on methods to declare a Bean. By default, the Bean name is the method name and is of type return value.
  • Conditions of annotation
    • @Conditional: Creates a specific Bean based ona Condition class that implements the Condition interface and overwrites the matches interface to construct a judgment Condition.
    • ConditionalOnBean: a bean is instantiated only if the specified bean exists in the container
    • ConditionalOnMissingBean: a bean is instantiated if the specified bean does not exist in the container
    • ConditionalOnClass: a Bean is instantiated only if there is a specified class in the system
    • @ ConditionalOnMissingClass: system is not specified in the class, will instantiate a Bean
    • ConditionalOnExpression: a Bean is instantiated only when the SpEl expression is true
    • AutoConfigureAfter: Instantiate a bean after it has been automatically configured
    • AutoConfigureBefore: Instantiate a bean before it is automatically configured
    • ConditionalOnJava: does the version in the system meet the requirements
    • @ ConditionalOnSingleCandidate: when the specified Bean has only one in the container, or there are multiple but specifies the preferred beans trigger instantiation
    • ConditionalOnResource: Whether the specified resource file exists in the classpath
    • @ ConditionalOnWebApplication: it’s a web application
    • @ ConditionalOnNotWebApplication: no web applications
    • ConditionalOnJndi: the JNDI specifies the existence entry
    • ConditionalOnProperty: Configure the loading rule for Configuration
      • Prefix: indicates the prefix of the attribute name
      • Value: array, get the value of the corresponding property name, cannot be used together with name
      • Name: an array. It can be used together with prefix to form a complete configuration attribute name, but cannot be used together with value
      • HavingValue: Compares whether the obtained attribute value is the same as the given value of havingValue, and then loads the configuration
      • MatchIfMissing: Whether this configuration property can be loaded when it is missing. If true, it will load normally without the configuration property. Otherwise, it will not take effect

Full mode and Lite Lightweight mode

  • @configuration proxyBeanMethods:
    • Full Mode (default) :@Configuration(proxyBeanMethods = true)
      • Under the same configuration class, when the @bean modifier is called directlymethodsThe injected object is calledThe method will be proxied, the bean real columns are taken from the IOC container, so the real columns are the same. In this mode, SpringBoot checks whether the component exists in the container every time it is started
    • Lite Lightweight Mode:@Configuration(proxyBeanMethods = false)
      • Under the same configuration class, when the @bean modifier is called directlymethodsThe injected object is calledThe method will not be proxied, equivalent to calling a normal method directly, there is a constructor, but there is no bean life cycle, and a different instance is returned.
  • Note: proxyBeanMethods are designed to use @bean annotationsmethodsBy agent. Instead of the singleton and multi-instance setting parameters of the @bean.
  • The test example is not shown here, you can download my code to see
@Configuration(proxyBeanMethods = false)
public class AppConfig {
    
    // Add a myBean to the ioc container
    @Bean
    public Mybean myBean(a) {
        return new Mybean();
    }

    // Put a portion of yourBean into the IOC container
    @Bean
    public YourBean yourBean(a) {
        System.out.println("= = = = = = = = = =");
        // Note: @configuration (proxyBeanMethods = false) : myBean() is not proxied
        // Note: @Configuration(proxyBeanMethods = true) : myBean() method proxy, from the IOC container
        return newYourBean(myBean()); }}Copy the code

When to use Full mode and when to use Lite Lightweight mode?

  • Full mode is recommended when there are dependencies between bean instances injected into containers in your same Configuration class
  • When there are no dependencies between bean instances injected into the container in your same Configuration class, Lite Lightweight mode is recommended to improve springBoot startup speed and performance

Starter naming convention

  • The official Spring Starter is usually named spring-boot-starter-{name}, for example, spring-boot-starter-web
  • It is recommended that the unofficial Starter be named in the {name}-spring-boot-starter format, for example, mybatis-spring-boot-starter.

The development of the Starter

1. Create the Starter project

  • After creating a project, delete the main startup class

2. Add dependencies


      
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.1</version>
        <relativePath/> <! -- lookup parent from repository -->
    </parent>

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.ljw</groupId>
    <artifactId>ljw-spring-boot-starter</artifactId>
    <version>1.0</version>
    
   <properties>
        <java.version>1.8</java.version>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>


    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <! -- contains auto-configuration code -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>

        <! -- Config file click to jump to entity -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

    </dependencies>

</project>
Copy the code
  • We do not have a main entry. We need to remove the Maven package plugin spring-boot-Maven-plugin from the POM file
  • Spring – the boot – configuration – processor:
    • The spring-boot-configuration-processor (spring-boot-configuration-processor) is an annotation processor that works at compile time
    • You can click Port in IDEA to enter this field and see the prompt information of configuration
    • This is because you have a spring-configuration-metadata.json file in your resource file, which is the spring configuration metadata in JSON form

3. Write property classes

ConfigurationProperties you can define a configuration information class that maps to configuration files

@ConfigurationProperties(prefix = "ljw.config")
public class HelloProperties {

    private String name = "Hello default!";

    private int age = 8;

    public int getAge(a) {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName(a) {
        return name;
    }

    public void setName(String name) {
        this.name = name; }}Copy the code

4. Customize service classes

Here you can simulate some business classes that perform business operations with configuration file information

public class HelloService {

    private String name;

    private int age;

    public String getName(a) {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge(a) {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String hello(a) {
        return "HelloService{" +
                "name='" + name + ' '' + ", age=" + age + '}'; }}Copy the code

5. Write automatic configuration classes

Naming convention: XxxAutoConfiguration

@Configuration(proxyBeanMethods = false)
// This auto-configuration class takes effect only when a class exists
@ConditionalOnClass(value = {HelloService.class})
// Import our custom configuration class for use by the current class
@EnableConfigurationProperties(value = HelloProperties.class)
// This auto-configuration class only takes effect for non-Web applications
@ConditionalOnWebApplication
// Check whether the value of ljw.config.flag is "true", matchIfMissing = true: it will be loaded normally without this configuration attribute
@ConditionalOnProperty(prefix = "ljw.config", name = "flag", havingValue = "true", matchIfMissing = true)
public class HelloAutoConfiguration {

    / * * *@paramThe helloProperties direct method signature parameter is injected with helloProperties, or you can use property injection *@return* /
    @Bean
    @ConditionalOnMissingBean(HelloService.class)
    //@ConditionalOnProperty(prefix = "ljw.config", name = "flag", havingValue = "true", matchIfMissing = true)
    public HelloService helloService(HelloProperties helloProperties) {
        HelloService helloService = new HelloService();
        // Inject the obtained information
        helloService.setName(helloProperties.getName());
        helloService.setAge(helloProperties.getAge());
        returnhelloService; }}Copy the code

Note: In this case, only a Web application can be injected, and whether the value of ljw.config.flag is “true” or no key can be configured to inject the HelloService service

6. Write spring. Factories

The automatic configuration class HelloAutoConfiguration configuration to org. Springframework. Boot. Autoconfigure. EnableAutoConfiguration key, Springboot automatically loads the file and assembs it according to the conditions

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ljw.starter.config.HelloAutoConfiguration
Copy the code

7. Write a configuration prompt file (optional)

additional-spring-configuration-metadata.json

After configuring the addition-spring-configuration-metadata. json file, The developer’s IDE tool uses a personally written configuration read to effectively complete the prompt in the application.properties or application.yml file.

You can view the document for configuring detailed format parameters

My configuration:

{"properties": [{"name": "ljw.config.name"."type": "java.lang.String"."defaultValue": "Hello default! This is configured as a hint, the real default is in Properties."."description": "That's a string name."
    },
    {
      "name": "ljw.config.age"."defaultValue": 8."description": "That's an int age."."deprecation": {
              "reason": "Obsolete reasons."."replacement": "The alternative key is: ljw.config.age22"."level": "warning"}}}]Copy the code

Please refer to the following properties table for configuration understanding.

The name of the type purpose
name String The full name of the property. The name is period-delimited in lower case (for example, server.address). This property is mandatory.
type String Attribute data type full signature (such as Java. Lang. String), but it is also full of generic types (such as Java. Util. Map < Java. Util. String, acme. MyEnum >). You can use this property to guide users on the types of values they can enter. For consistency, specify the type of the primitive by using it to wrap the corresponding item (for example, Boolean becomes java.lang.boolean). Note that this class can be a complex type that is converted from the value of the Stringas binding.If the type is unknown or basic, it can be omitted.
description String A short description of the group that can be displayed to the user. If no description is available, omit it. It is recommended to write a short paragraph, with a brief summary in the first line. The last line of the description should end with a period (.) At the end.
sourceType String The class name of the source contributing this attribute. For example, if the property comes from the annotated class @ConfigurationProperties, the property will contain the fully qualified name of the class. If the source type is unknown, it can be omitted.
defaultValue Object Default value, used if no attribute is specified. If an attribute is of type array, it can be an array of values. If the default value is unknown, you can omit it.
deprecation An array of Outdated description.

Deprecation JSON objects contained in the attributes of each properties element can contain the following attributes:

The name of the type purpose
level String Deprecation level, which can be warning (default) or error. When a property has a warning deprecation level, it should still be bound to the environment. However, when it has the error deprecation level, the property is no longer managed and unconstrained.
reason String A brief description of why this property is deprecated. If no reason is available, omit it. It is recommended to write a short paragraph, with a brief summary in the first line. The last line of the description should end with a period (.) At the end.
replacement String Replaces the full name of the property for this deprecated property. If this property is not replaced, it can be omitted.

spring-configuration-metadata.json

The spring-configuration-metadata.json code is quite large. In order to facilitate us to generate it through IDE, idea is used here.

Search for Annotation Processors in the IDEA setting and check Enable Annonation Processing to complete the task. You can see the spring-configuration-metadata.json automatically generated in the compiled and packaged file. We don’t have to write this file

The following is generated automatically:

{
  "groups": [{"name": "ljw.config"."type": "com.ljw.starter.properties.HelloProperties"."sourceType": "com.ljw.starter.properties.HelloProperties"}]."properties": [{"name": "ljw.config.name"."type": "java.lang.String"."description": "That's a string name."."sourceType": "com.ljw.starter.properties.HelloProperties"."defaultValue": "Hello default! This is configured as a hint, the real default is in Properties."
    },
    {
      "name": "ljw.config.age"."type": "java.lang.Integer"."description": "That's an int age."."sourceType": "com.ljw.starter.properties.HelloProperties"."defaultValue": 8."deprecated": true."deprecation": {
        "level": "warning"."reason": "Obsolete reasons."."replacement": "The alternative key is: ljw.config.age22"}}]."hints": []}Copy the code

Testing the Starter

1. Pre-environment

Install Package the customized starter project: ljw-spring-boot-starter

New project: ljw-test-spring-boot-starter

2. Add dependencies

Introduce a custom starter with a package

<dependencies>
        
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <! -- Test your Web application -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <! Custom satrter-->
    <dependency>
        <groupId>com.ljw</groupId>
        <artifactId>ljw-spring-boot-starter</artifactId>
        <version>1.0</version>
    </dependency>
</dependencies>
Copy the code

3. The test class

@Service
public class TestController implements CommandLineRunner {

    /** * Inject the custom starter service */
    @Resource
    private HelloService helloService;

    @Override
    public void run(String... args) throws Exception { System.out.println(helloService.hello()); }}Copy the code

4. Modify the configuration file

Enter the prefix to see that there is already a prompt

ljw.config.name=ljw hello!
ljw.config.age=99
ljw.config.flag=true
# do not inject
#ljw.config.flag=true1
You can see what is automatically configured
debug=true
Copy the code

5. Run the program to print

HelloService{name='ljw hello! ', age=99}Copy the code
  • Conditions for injection
    • The HelloService cannot be injected without the spring-boot-starter-Web dependency
    • If ljw.config.flag is not true, service HelloService cannot be injected. If ljw.config.flag is not configured, it can be injected

6. View the method for the automatic configuration class to take effect

It is easy to know which auto-configuration classes are in effect by enabling the debug=true attribute to have the console print the auto-configuration report.

   HelloAutoConfiguration matched:
      - @ConditionalOnClass found required class 'com.ljw.starter.service.HelloService'(OnClassCondition) - the @ConditionalOnWebApplication (required) found 'session' scope (OnWebApplicationCondition) - the @ConditionalOnProperty (ljw.config.flag=true) matched (OnPropertyCondition)

   HelloAutoConfiguration#helloService matched:
      - @ConditionalOnMissingBean (types: com.ljw.starter.service.HelloService; SearchStrategy: all) did not find any beans (OnBeanCondition)
Copy the code
  • 👍🏻 : have harvest, praise encouragement!
  • ❤️ : Collect articles, easy to look back!
  • 💬 : Comment exchange, mutual progress!