Principle of automatic configuration in SpringBoot and making JAR package that can be assembled automatically

Posted by monkuar on Sat, 15 Jan 2022 02:29:47 +0100

In SpringBoot, classes with certain conditions will be automatically assembled into a Bean in the container. Here, you need to know about configuration classes: the classes wrapped by the following three annotations are configuration classes:

  • @SpringBootApplication
  • @SpringBootConfiguration
  • @Configuration

Personally, the Configuration class can be regarded as a container. The class defined by @ SpringBootApplication is the main Configuration class and a general container. The classes defined by @ SpringBootConfiguration and @ Configuration are Configuration classes. They can be regarded as a container and also have the characteristics of Bean. Externally, it can automatically assemble an attribute of its own class with another Bean outside the container. Internally, it is a container that can contain beans or search beans into its own container.

Configuration class: the configuration class formed by @ SpringBootConfiguration and @ configuration annotation can be packaged into a jar and made into a jar package with automatic assembly. After automatic assembly, all beans in the defined configuration class will be included in the general container. How to implement it will be described below.

1. Principle of automatic assembly and several automatic assembly methods:

When we customize some classes and package them into a jar file, we can automatically assemble the classes contained in the jar package into the configuration class, and the configuration class can be configured layer by layer. However, in the SpringBoot project, there is only one main configuration class, which is determined by the annotation @ SpringBootApplication, There can be multiple configuration classes defined with @ configuration and @ SpringBootConfiguration. They can Import classes from jar packages for configuration, generate beans through class methods, and scan the package generated beans in their own projects@ The configuration class defined by configuration and @ SpringBootConfiguration can also be regarded as a Bean in another container, and the properties in the configuration class can also be automatically assembled from this parent container. More importantly, configuration classes at the same level can be concentrated in a custom annotation through the @ Import annotation, and then defined on another custom configuration class. In this way, the container of the last custom configuration class contains all beans of other configuration classes at the same level imported with the @ Import annotation. In short: beans of all configuration classes can be summarized into the main configuration class through multiple method collections. The specific methods are as follows:

(1) Configure the Bean by calling an external jar wrapper

  • Export external jar package

Export the external jar package through the annotation: @ ComponentScan(basePackages = {"path under the package where the class is located"}). The specific steps are:

  1. Mark a user-defined class with @ Configuration to form a Configuration class;
  2. Use the annotation @ ComponentScan(basePackages = {"path under the package where the class is located"}) to guide the path where the class under the custom jar package is located. This path can be the general path or the specific sub path.

Note: for the customized jar package, if you want the classes in the package to be automatically assembled into the configuration class, you need to add the annotation @ Component on the class when customizing the class. This annotation needs to import the dependent package: Spring context. If the created package wants to be a third-party dependency, the scope of the imported spring context package should be set to: proposed (required during writing)

Specific code examples:

  • Guiding spring context dependencies:
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.8</version>
</dependency
  • Customize the classes to be packaged into jar, and the key point is to add the annotation @ Component
@Component
public class DreamClassImpl {

    public void showName() {
        System.out.println("this is showName method");
    }
    public int addNumber(int a, int b) {
        return a + b;
    }
}
  • Packaged into jar and invoked in the project.
@Configuration
@ComponentScan(basePackages = {"com.dream"})
public class DreamOneConfigImpl {
}

//Knowledge point: the test class is also a Bean in the general configuration class container, and the attributes in the test class can also be automatically assembled in the parent container
@SpringBootTest
class DemoApplicationTests {
    @Autowired
    private DreamClassImpl dreamClass;

    @Test
    void contextLoads() {
        System.out.println("hell java");
        dreamClass.showName();
        System.out.println(dreamClass.addNumber(20, 20));
    }
}

(2) Assemble beans with @ Import annotation

The biggest advantage of assembling beans with @ Import annotation is that this annotation can be used as a meta annotation for another user-defined annotation. The newly generated user-defined annotation is defined on other configuration classes or main configuration classes, so that beans in the configuration classes imported through @ Import annotation can be assembled into the newly defined configuration class. This method is suitable for configuring classes in different package paths and in dispersion! Specific examples are as follows:

  • Define a configuration class. This configuration class is equipped with external jar package class Bean, custom Bean and other class beans of its own project
//The beans imported by this configuration class come from several sources, one is the class Bean of the external jar package, and the other is the class path class Bean of the internal project. These classes must be described with @ Component annotation!
@Configuration
@ComponentScan(basePackages = {"com.dream"})
@ComponentScan(value = {"com.example.other"})
public class DreamOneConfigImpl {
}
  • Customize an annotation and Import the configuration class defined above into this annotation with the @ Import annotation. This customized annotation can be used as a reference. The configuration class modified with this annotation contains all beans in the previous configuration class
@Retention(RetentionPolicy.RUNTIME)//Used to describe the life cycle of the customized annotation, which is used in conjunction with RetentionPolicy
@Target(value = ElementType.TYPE) //Used to describe the scope of use of annotations (i.e. where the described annotations can be used)
@Documented //This annotation is only used to indicate whether the javadoc will be recorded when it is generated.
@Import(value = {DreamOneConfigImpl.class})
public @interface EnableImportOther {
}
  • Modify a configuration class or main configuration class with user-defined annotations. Here, modify a user-defined configuration class
@Configuration
@EnableImportOther
public class DreamAllBeanImpl {
}

(2) Import external jar package classes through @ import annotation

In the external jar package, if the customized class has no @ Component annotation, you can Import the external jar package class from the configuration class through the @ Import annotation. The specific usage is as follows:

@Import({"Dreamone.class","Dreamtow.class"})

In this way, a class in the external Jar package can form a corresponding Bean in the configuration class container

Summarize several ways to form beans in configuration classes

  • Scan the external jar package path and the class with @ Component annotation of the internal path of the project through the annotation @ ComponentScan(). There are three scanning directions: one is to specify the class path of the external jar package (the class path needs to be specified); Second, the project itself is not in the class path at or below the same level of the configuration class (the class path needs to be specified); The third is the class whose own project is at the same level or below the configuration class.

  • The @ Bean is defined from within the class by means of annotation

  • Import classes without @ Component annotation externally or internally through annotation @ Import()

@The Import annotation can Import not only the classes in the Jar package, but also the classes in the internal project. It is easy to form beans in the container when handling classes without @ Component annotation through this annotation

@SpringBootApplication
//@ComponentScan(value = {"com.two"})
@Import(value = {DreamClassImpl.class,DreamAllBeanImpl.class})
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

Summary: @ Import annotation can not only Import classes without @ Component annotation, but also Import configuration classes. It can also be used on an annotation to modify other configuration classes.

2. Custom classes are packaged into automatic assembly JAR packages by SPI mechanism

The automatic assembly of jar package is that after adding dependencies to the project, the application will automatically generate corresponding beans from the classes in the jar package in the container. This process is called automatic assembly (AutoConfiguration). Therefore, generally, custom automatic assembly classes will be named after XXXAutoConfiguration!

(1) What is SPI mechanism

  • There is a very decoupled extension mechanism in Spring Boot: Spring Factories. This extension mechanism is actually implemented by imitating the SPI extension mechanism in Java.
  • The full name of the mechanism spi.java interface is spi.service provider Most developers may not be familiar with this because it is for vendors or plug-ins. In Java util. Serviceloader is described in detail in the documentation. Simply summarize the idea of java spi mechanism. Each abstract module in our system often has many different implementation schemes, such as the scheme of log module, xml parsing module, jdbc module, etc. In object-oriented design, we generally recommend interface programming between modules, and no hard coding of implementation classes between modules. Once a specific implementation class is involved in the code, it violates the principle of pluggability. If you need to replace an implementation, you need to modify the code. In order to realize the module assembly without dynamic indication in the program, a service discovery mechanism is needed. java spi provides such a mechanism: a mechanism to find a service implementation for an interface. Similar to the idea of IOC, it is to move the control of assembly outside the program. This mechanism is particularly important in modular design.
  • The specific convention of java spi is that after the service provider provides an implementation of the service interface, a file named after the service interface is created in the META-INF/services / directory of the jar package. This file is the specific implementation class that implements the service interface. When the external program assembles the module, you can find the specific implementation class name through the configuration file in the jar package META-INF/services /, load and instantiate to complete the module injection. Based on such a convention, we can find the implementation class of the service interface without making it in the code. jdk provides a tool class for service implementation lookup: Java util. ServiceLoader
  • There is also a loading mechanism similar to Java SPI in Spring. It's in meta-inf / Spring Configure the implementation class name of the interface in the factories file, and then read these configuration files and instantiate them in the program. This custom SPI mechanism is the basis for the implementation of Spring Boot Starter.

(2)spring. The specific writing of factories:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.dream.seeker.DreamTwoImpl,\
com.dream.seeker.config.NewDreamClass 

Note: observe carefully. When different classes are separated, pay attention to commas

3. Conditions and restrictions of custom configuration class

A configuration class can attach many conditions, which determine whether the configuration class takes effect or what kind of role it plays. In this section, in combination with the configuration class, you can understand the following notes:

  • @EnableConfigurationProperties({HttpEncodingProperties.class})

The function of this annotation: specify a specific class to configure its properties. The class configuring the properties is not decorated with the @ Component annotation, so it cannot form a Bean in the container. However, through this annotation, the class can form a Bean in the container and inject data according to the @ ConfigurationProperties annotation in the class

@PropertySource(value = {"classpath:application-dev.properties"})
@ConfigurationProperties(prefix = "new.five")
public class NewFive {
    private String username;
    private Integer age;
}
@ComponentScan  //Add this annotation to scan the classes containing @ Component annotation in the directory and subdirectory where the configuration class is located, and form beans in the configuration class
@EnableImportOther
@SpringBootConfiguration
@EnableConfigurationProperties(value = {NewFive.class})
public class DreamAllBeanImpl {
}
  • @ConditionalOnWebApplication
@ConditionalOnWebApplication( type = Type.SERVLET )  //If the current application is a Web application, the configuration class will take effect, otherwise it will not take effect 
  • @ConditionalOnClass
@ConditionalOnClass({CharacterEncodingFilter.class})  //If there is a CharacterEncodingFilter class in the current application, the configuration class will take effect, otherwise it will not take effect
  • @ConditionalOnProperty
//If spring. Net exists in the configuration file http. encoding. If enabled, the configuration item will take effect. Otherwise, it will not take effect. It has been set to True by default, so it will take effect by default
@ConditionalOnProperty( prefix = "spring.http.encoding", 
                       value = {"enabled"}, 
                       matchIfMissing = true )  

The specific code examples are summarized as follows:

@ComponentScan  //Add this annotation to scan the classes containing @ Component annotation in the directory and subdirectory where the configuration class is located, and form beans in the configuration class
@EnableImportOther  //This annotation is a custom annotation. The @ Import annotation is used to Import another configuration class. The configuration class added with this annotation will contain beans of other imported configuration classes
@SpringBootConfiguration  //This annotation clearly means that defining this class is a Configuration class, which has the same meaning as the annotation @ Configuration
@EnableConfigurationProperties(value = {NewFive.class})
//If the current application is a Web application, the configuration class will take effect, otherwise it will not take effect
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
//If there is a CharacterEncodingFilter class in the current application, the configuration class will take effect, otherwise it will not take effect
@ConditionalOnClass({DreamTwoImpl.class})
@ConditionalOnProperty(
        prefix = "spring.newfile",
        value = {"enable"},
        matchIfMissing = true
)
public class DreamAllBeanImpl {
}

Topics: Java Programming Maven Spring Spring Boot