Still curd? Encapsulate your own spring boot starter

Posted by nimbus on Wed, 05 Jan 2022 15:44:52 +0100

Reading harvest

  • 👍🏻 Learn to customize spring boot starter
  • 👍🏻 Understand the principle of SpringBoot auto configuration

Download the source code of this chapter

What is Starter

Starter is a very important concept in Spring Boot. Starter 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 (conditions).

Users only need to rely on the Starter of the corresponding function without too much configuration and dependence. Spring Boot can automatically scan and load the corresponding modules and set the default values to be used out of the box

Why use Starter

In our daily development work, there are often some configuration modules that are independent of the business. We often put them under a specific package. Then, if another project needs to reuse this function, we need to hard copy the code to another project and re integrate it. It is extremely troublesome.

If we package these function configuration modules that can be independent of the business code into starters and set the default values in the starters, we only need to reference the dependencies in the pom when reusing them. Spring Boot completes the automatic assembly for us to achieve out of the box use.

Springboot auto configuration

The starter in Spring Boot is a very important mechanism. It can discard the previous complicated configuration and integrate it into the starter. The application only needs to introduce the starter dependency in maven, and Spring Boot can automatically scan the spring.com of the classpath path under each jar package Factories file, load the automatic configuration class information, load the corresponding bean information, and start the corresponding default configuration.

Spring Boot provides Spring Boot starter dependency modules for various scenarios of daily enterprise application development. All these dependent modules follow the conventional default configuration and allow us to adjust these configurations, that is, follow the concept of "Convention is greater than configuration".

You can take a look at an article I wrote earlier to introduce the process of springboot automatic configuration in detail: Understand a text 🔥 SpringBoot auto configuration principle - Nuggets (juejin.cn)

spring.factories

Spring Boot will scan packages of the same level as the startup class by default. If our Starter and startup class are not in the same main package, we need to configure spring The configuration takes effect by using the factories file. By default, SpringBoot loads the spring. Com of the classpath path under each jar package In the factories file, the configured key is org springframework. boot. autoconfigure. EnableAutoConfiguration

Common notes for Starter development

The use of annotations has been greatly convenient for our development. We no longer need to write xml configuration files. Spring boot looks up spring The factories file loads the automatic configuration class, which defines various runtime judgment conditions, such as @ ConditionalOnMissingBean(A.class). The configuration file will take effect only if there is no specified bean information of type A in the ioc container.

@Conditional is a new annotation provided by spring 4. It is used to judge according to certain conditions and register bean s to the container if the conditions are met.

  • Attribute mapping annotation
    • @ConfigurationProperties: mapping of profile property values to entity classes
    • @EnableConfigurationProperties: used in conjunction with @ ConfigurationProperties to add the class modified by @ ConfigurationProperties to the ioc container.
  • Configuration bean annotation
    • @Configuration: identify this class as a configuration class and inject it into the ioc container
    • @Bean: it is generally used on methods to declare a bean. By default, the bean name is the method name and the type is the return value.
  • Conditional annotation
    • @Conditional: creates a specific Bean according to the Condition class. The Condition class needs to implement the Condition interface and rewrite the matches interface to construct judgment conditions.
    • @ConditionalOnBean: a bean will be instantiated only if the specified bean exists in the container
    • @ConditionalOnMissingBean: a bean will be instantiated only if the specified bean does not exist in the container
    • @ConditionalOnClass: only when there is a specified class in the system can a Bean be instantiated
    • @ConditionalOnMissingClass: a Bean will only be instantiated if no class is specified in the system
    • @ConditionalOnExpression: a Bean will be instantiated only when the SpEl expression is true
    • @AutoConfigureAfter: instantiate a bean after it is automatically configured
    • @Autoconfigurebeefore: instantiate a bean before it completes automatic configuration
    • @ConditionalOnJava: whether the version in the system meets the requirements
    • @ConditionalOnSingleCandidate: trigger instantiation when there is only one specified Bean in the container, or there are multiple beans but the preferred Bean is specified
    • @ConditionalOnResource: whether the specified resource file exists under the classpath
    • @ConditionalOnWebApplication: it is a web application
    • @Conditionalonnotvebapplication: not a web application
    • @ConditionalOnJndi: JNDI specifies that an entry exists
    • @ConditionalOnProperty: configure loading rules for Configuration
      • Prefix: prefix of the configuration attribute name
      • Value: array to obtain the value of the corresponding property name. It cannot be used with name at the same time
      • Name: array, which can be used in combination with prefix to form a complete configuration attribute name. It cannot be used together with value
      • havingValue: compare whether the obtained property value is the same as the value given by havingValue, and then load the configuration
      • matchIfMissing: whether the configuration property can be loaded when it is missing. If true, it will be loaded normally without the configuration attribute; Otherwise, it will not take effect

Full mode and Lite lightweight mode

  • @Configuration parameter proxyBeanMethods:
    • Full full mode (default): @ Configuration(proxyBeanMethods = true)
      • Under the same configuration class, when you directly call the object injected by the method decorated with @ bean, the method will be called by the proxy to get the bean real column from the ioc container, so the real column is the same. That is, a single instance object. In this mode, SpringBoot will judge and check whether the component exists in the container every time it is started
    • Lite lightweight mode: @ Configuration(proxyBeanMethods = false)
      • Under the same configuration class, when you directly call the object injected by the method decorated with @ bean, the method will not be proxied. It is equivalent to directly calling an ordinary method. There will be construction methods, but there is no bean life cycle, and different instances will be returned.
  • Note: proxyBeanMethods is used to proxy methods annotated with @ Bean. Instead of setting parameters for single instance and multiple instances of @ Bean.
  • The test example is not shown here. You can download my code to view it
@Configuration(proxyBeanMethods = false)
public class AppConfig {
    
    //Put a copy of myBean into the ioc container
    @Bean
    public Mybean myBean() {
        return new Mybean();
    }

    //Put a copy of yourBean into the ioc container
    @Bean
    public YourBean yourBean() {
        System.out.println("==========");
        //Note: @ Configuration(proxyBeanMethods = false): the myBean() method does not proxy, but is called directly
        //Note: @ Configuration(proxyBeanMethods = true): myBean() method proxy, from ioc container
        return new YourBean(myBean());
    }
}
Copy code

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

  • When there are dependencies between bean instances injected into the container in the same Configuration class, it is recommended to use Full mode
  • When there is no dependency between the bean instances injected into the container in your same Configuration class, it is recommended to use Lite lightweight mode to improve the startup speed and performance of springboot

Starter naming convention

  • The official Spring Starter is usually named Spring boot Starter - {name}, such as Spring boot Starter web
  • Spring officially suggests that unofficial Starter naming should follow the format of {name} - spring boot Starter: for example, mybatis spring boot Starter.

Develop Starter

1. Create Starter project

  • After creating a new project, delete the main startup class

2. Add dependency

<?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 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 code for automatic configuration-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>

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

    </dependencies>

</project>
Copy code
  • We don't have a main entry. We need to remove the maven packaging plug-in spring boot maven plugin in the pom file
  • Spring boot configuration processor functions:
    • Spring boot configuration processor is actually an annotation processor. It works in the compilation stage. Generally, the declaration in maven is optional and true
    • You can click port in the idea to enter this field and see the configuration prompt
    • This is because there is a spring configuration metadata in your resource file json file, which is the metadata of spring configuration, is in the form of json

3. Write attribute class

@ConfigurationProperties can define a configuration information class and map it to a configuration file

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

    private String name = "hello Default value!";

    private int age = 8;

    public int getAge() {
        return age;
    }

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

    public String getName() {
        return name;
    }

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

4. User defined business class

Here, you can simulate some business classes for business operations that obtain the configuration file information

public class HelloService {

    private String name;

    private int age;

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

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

5. Write automatic configuration class

Naming convention: XxxAutoConfiguration

@Configuration(proxyBeanMethods = false)
// This auto configuration class takes effect only when a class exists
@ConditionalOnClass(value = {HelloService.class})
// Import our customized configuration class for use by the current class
@EnableConfigurationProperties(value = HelloProperties.class)
// This autoconfiguration class takes effect only for non web applications
@ConditionalOnWebApplication
//Judge ljw config. Whether the value of 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 {

    /**
     * @param helloProperties Direct method signature input parameter injection HelloProperties, or property injection can be used
     * @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 acquired information into
        helloService.setName(helloProperties.getName());
        helloService.setAge(helloProperties.getAge());
        return helloService;
    }

}
Copy code

Note: only a web application can be configured here, and ljw config. Whether the value of flag is "true" or the key is not configured to inject HelloService service

6. Write spring factories

Configure the auto configuration class HelloAutoConfiguration to org springframework. boot. autoconfigure. Under the key of enableautoconfiguration, springboot will automatically load the file and assemble it according to conditions

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

7. Prepare configuration prompt file (not required)

additional-spring-configuration-metadata.json

Configure additional spring configuration metadata After reading the JSON file, the developer's IDE tool uses the configuration written by the individual, which is very effective in application Properties or application Complete the prompt under the YML file.

Configure detailed format parameters to view documents

My configuration:

{"properties": [
    {
      "name": "ljw.config.name",
      "type": "java.lang.String",
      "defaultValue": "hello Default value! The prompt is configured here. The real default value is Properties inside",
      "description": "This is a string name."
    },
    {
      "name": "ljw.config.age",
      "defaultValue": 8,
      "description": "This is int What kind of age.",
      "deprecation": {
              "reason": "Reasons for obsolescence.",
              "replacement": "replace key Yes: ljw.config.age22",
              "level": "warning"
            }
    }
]}
Copy code

Please refer to the following properties table for configuration understanding.

nametypeobjective
nameStringThe full name of the property. The names are in lowercase cycle separated form (for example, server.address). This property is mandatory.
typeStringProperty, but it is also a complete generic type (for example, Java. Util. Map < Java. Util. String, acme. Myenum >) (for example, boolean becomes java.lang.Boolean) to specify the type of primitive. Note that this class may be a complex type, which is converted from the value bound by Stringas. If the type is unknown or the basic type, it can be omitted.
descriptionStringA short description of the group that can be displayed to the user. If no description is available, it can be omitted. The recommended description is a short paragraph with a concise summary on the first line. The last line in the description should end with a period (.).
sourceTypeStringThe class name of the source that contributed this property. 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.
defaultValueObjectDefault value, which is used if no attribute is specified. If the type of the property is an array, it can be an array of values. If the default value is unknown, it can be omitted.
deprecationarrayOutdated description.

The JSON object contained in the properties of each property element of depredation can contain the following properties:

nametypeobjective
levelStringThe deprecation level 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 an error deprecation level, the property is no longer managed and unconstrained.
reasonStringA short description of why the property was deprecated. If no reason is available, it can be omitted. The recommended description is a short paragraph with a concise summary on the first line. The last line in the description should end with a period (.).
replacementStringReplace the full name of the property for this deprecated property. If this property is not replaced, it can be omitted.

spring-configuration-metadata.json

spring-configuration-metadata. There is a large amount of JSON code. For convenience, we can generate it through IDE. Here, idea is used.

Search for Annotation Processors in the idea settings, and then check enable annotation processing to complete it. In the compiled and packaged file, you can see the automatically generated spring configuration metadata json. We don't need to write this document

The following is automatically generated:

{
  "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": "This is a string name.",
      "sourceType": "com.ljw.starter.properties.HelloProperties",
      "defaultValue": "hello Default value! The prompt is configured here. The real default value is Properties inside"
    },
    {
      "name": "ljw.config.age",
      "type": "java.lang.Integer",
      "description": "This is int What kind of age.",
      "sourceType": "com.ljw.starter.properties.HelloProperties",
      "defaultValue": 8,
      "deprecated": true,
      "deprecation": {
        "level": "warning",
        "reason": "Reasons for obsolescence.",
        "replacement": "replace key Yes: ljw.config.age22"
      }
    }
  ],
  "hints": []
}
Copy code

Test Starter

1. Front environment

install package custom starter project: ljw-spring-boot-starter

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

2. Add dependency

Introduction of custom starter with package

<dependencies>
        
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <!--        test 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 code

3. Testing

@Service
public class TestController implements CommandLineRunner {

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

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

4. Modify the configuration file

You can see that there is already a prompt by entering the prefix

ljw.config.name=ljw hello!
ljw.config.age=99
ljw.config.flag=true
#No injection
#ljw.config.flag=true1
# You can see which are automatically configured
debug=true
 Copy code

5. Run program printing

HelloService{name='ljw hello!', age=99}
Copy code
  • Conditional injection
    • The HelloService service cannot be injected without a spring boot starter web dependency
    • If ljw is configured config. Flag, the value is not true, and the service HelloService cannot be injected; If you do not configure ljw config. Flag, you can inject

6. View the effective method of automatic configuration class

By enabling the debug=true attribute, let the console print the auto configuration report, so that you can easily know which auto configuration classes are effective.

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

   HelloAutoConfiguration#helloService matched:
      - @ConditionalOnMissingBean (types: com.ljw.starter.service.HelloService; SearchStrategy: all) did not find an

Topics: Spring Spring Boot Back-end Programmer