preface

When using the SpringBoot framework, the most powerful function of SpringBoot is to extract and encapsulate commonly used scenes into a starter, which is called the scene initiator. When building a project, I can quickly develop a Java Web application by introducing these scene initiators provided by SpringBoot, and then doing a little configuration. The principle is completely down to the Starter package of SpringBoot, which brings us a lot of automatic configuration, with these automatic configuration, make Java project development effortless, so do you feel that the Starter is magical? Next, demystify it and customize a Starter with your bare hands.


IO/Spring-boot… Springboot…

1. Core knowledge

1.1 Starter mechanism

Starter is a very important mechanism in SpringBoot. It abandons the complex configuration in the past and integrates it into one starter. Developers only need to introduce the starter dependency in Maven, and SpringBoot can automatically scan the information to be loaded and start the corresponding default configuration. Starter eliminates the need to rely on libraries for processing, compatibility between versions, and configuration information.

SpringBoot automatically finds the beans it needs through classes in the classpath path and registers them with the IOC container. SpringBoot 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.

1.2 Why customize starter

By default, the starter provided by SpringBoot is sufficient for daily development work. After understanding the principle of SpringBoot encapsulating the starter, we may consider that daily development often has some configuration modules that are independent of the business. Especially in the Spring Cloud microservice architecture, some highly reusable configuration classes are usually copied to different service modules. In fact, these configuration classes have the same code but are needed by a sub-service, which is very redundant and troublesome.

At this point, we can package these functional configuration modules that can be independent of business code into a starter. When reusing, we only need to reference the starter dependency in POM, and SpringBoot completes automatic assembly for us, which is not too cool.

1.3 Customizing the Starter

  • Dynamic data source switching

  • AOP aspect log interception processing

  • Master and slave libraries read and write separate data sources

  • Swagger Interface document configuration

    . Scenes, etc.

1.4 Customizing naming rules for the starter

Official naming rules

  • The prefix: spring – the boot – starter – {name}
  • Mode: spring-boot-starter- Module name
  • For example, spring-boot-starter-web and spring-boot-starter-thymeleaf

Custom naming rules

  • Suffix: {name} – spring – the boot – the starter
  • Mode: module-spring-boot-starter
  • Example: mybatis – spring – the boot – the starter

How to customize the starter

1. How to write automatic configuration

In order to customize our own starter, it is necessary to understand the automatic configuration provided by SpringBoot. Taking WebMvcAutoConfiguration as an example, learn the steps and annotations required to write the automatic configuration class.

Excerpt from part of the source code as follows (example) :

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {

	public static final String DEFAULT_PREFIX = "";

	public static final String DEFAULT_SUFFIX = "";

	private static final String[] SERVLET_LOCATIONS = { "/" };

	@Bean
	@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
	@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
	public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter(a) {
		return new OrderedHiddenHttpMethodFilter();
	}

	@Bean
	@ConditionalOnMissingBean(FormContentFilter.class)
	@ConditionalOnProperty(prefix = "spring.mvc.formcontent.filter", name = "enabled", matchIfMissing = true)
	public OrderedFormContentFilter formContentFilter(a) {
		return new OrderedFormContentFilter();
	}
Copy the code

In this auto-configuration class, we need these annotations:The above notes are explained as follows:

@Configuration  // Specify that this class is a configuration class
@ConditionalOnXXX  // The auto-configuration class takes effect only if the specified condition is true
@AutoConfigureOrder // Specify the priority of the auto-configuration class
@AutoConfigureAfter  // Specify the order of the auto-configuration classes
@Bean  // Add components to the container

@ConfigurationPropertie // Bind the property prefix xx in the application.properties file to the property corresponding to the xxxproperties.java class
@EnableConfigurationProperties // Set the @configurationProperties (prefix = "xx") configuration class to work.
Copy the code

2. How to make configuration class loading work

In META‐INF/spring.factories, create a folder named Meta-INF under Resources. And create a file spring.factories under meta-INF. Spring. factories with spring-boot-autoconfigure:

3. Encapsulate the customized starter mode

In the starter defined by SpringBoot, each starter can only be used to import dependencies for external projects. The starter relies on an automatic configuration, so an automatic configuration module is written to the starter.

3.1 Custom Starter

  • Initiators are only used for dependency imports

  • Write an auto-configuration module specifically;

  • Initiators rely on automatic configuration modules, and introducing the corresponding starter in a project introduces all of the initiator’s delivery dependencies


Define automatic configuration module

1. pom.xml


      
<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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.wechat.boot</groupId>
    <artifactId>wechat-spring-boot-autoconfigure</artifactId>
    <version>1.0 the SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1. RELEASE</version>
        <relativePath/>
    </parent>

    <dependencies>
        <! -- Introducing spring‐boot‐starter; Basic configuration of all starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <! -- Can generate config class prompt file -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <! -- Lombok simplified code -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

</project>
Copy the code

2. Define configuration class binding properties

Once configured, we first create a WxProperties class that accepts the property values injected in Application.properties and the binding maps to this class.

/ * * *@desc:
 * @author: cao_wencao
 * @date: the 2020-08-25 16:04 * /
@Data
@Component
@ConfigurationProperties(prefix = "wx.config")
public class WxProperties {

    private String isOpen;
    private String appid;
    private String appsecret;
    private String appkey;
    private String token;
}
Copy the code

3. Business logic

This business logic class is mainly used to call properties in WxProperties. After the project starts, you can use @AutoWired to inject the WxDemoService class and call the initLoader method to test whether the automatic configuration has taken effect successfully.

/ * * *@desc:
 * @author: cao_wencao
 * @date: the 2020-08-25 sons of * /
@Slf4j
@Data
public class WxDemoService {

    private WxProperties wxProperties;

    public String initLoader(String msg) {
        String appId = wxProperties.getAppid();
        String appKey = wxProperties.getAppkey();
        String sercret = wxProperties.getAppsecret();
        String token = wxProperties.getToken();
        log.info("\ n" application ID ": {} \ n [application Key 】 : {} \ n [application secret Key 】 : {} \ n [application token 】 : {}",appId,appKey,sercret,token);
        return msg + ", start initialization:"+ appId + appKey+sercret +token; }}Copy the code

4. Define automatic configuration classes

/** * //prefix indicates the prefix in the configuration file. * //name indicates the configured name. * //havingValue indicates the ratio of the value to the configured value@desc: wechat automatic configuration class *@author: cao_wencao
 * @date: not * / 2020-08-25
@Configuration // indicates that this is a configuration class
// A class is instantiated only when there is an attribute in the configuration file prefixed with wx.config, the attribute name is IS-open, and its value is enabled. MatchIfMissing indicates whether the property is loaded when it is missing. If true, the property will be loaded normally without the property. If false, an error will be reported without the property.
@ConditionalOnProperty(prefix = "wx.config", name = "is-open", havingValue = "enabled",matchIfMissing = false)
@ConditionalOnClass(WxDemoService.class)// The configuration class takes effect when the WxDemoService class exists in the classpath of the current project (the specified class exists in the classloader)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@EnableConfigurationProperties(WxProperties.class)// Set the @configurationProperties we configured earlier to enable the configured properties to enter the Bean successfully.
public class WxAutoConfiguration {
    @Autowired
    private WxProperties properties;

    @Bean
    public WxDemoService wxDemoService(a){
        WxDemoService demoService = new WxDemoService();
        demoService.setWxProperties(properties);
        returndemoService; }}Copy the code

The comments on the above auto-configuration classes are as follows:

  • @configuration: indicates that this is a Configuration class.
  • @conditionalonProperty (prefix = “wx.config”, name = “is-open”, havingValue = “enabled”,matchIfMissing = false) : A class is instantiated only if there is a property in the configuration file prefixed with wx.config with the name IS-open and its value enabled. MatchIfMissing indicates whether the property is loaded when it is missing. If true, the property will be loaded normally without the property. If false, an error will be reported without the property.
  • ConditionalOnClass(wxDemoservice.class) : ConditionalOnClass takes effect when the WxDemoService class exists in the classpath of the current project.
  • @ ConditionalOnWebApplication (type = ConditionalOnWebApplication. Type. The SERVLET) : web applications to take effect
  • @ EnableConfigurationProperties (WxProperties. Class) : our configuration before @ ConfigurationProperties effect, make configuration properties successfully into the Bean.

5. @conditionalonProperty Annotation meaning

ConditionalOnProperty @conditionAlonProperty specifies whether a conditional annotation with an attribute condition is met, which controls whether the @Configuration auto-configuration class takes effect.

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {
    // Array, get the value of the corresponding property name, cannot be used with name
	String[] value() default {};
	
    // Configure the prefix of the attribute name, such as spring.http.encoding
	String prefix(a) default "";
	
    // Array that configures the full or partial names of the properties
    // Can be combined with prefix to form a complete configuration attribute name, and cannot be used together with value
	String[] name() default {};

    // Can be used in combination with name to compare the obtained attribute value with the given value of havingValue
	String havingValue(a) default "";

    // Whether it can be loaded without the configuration attribute. If true, it will load normally without the configuration property. Otherwise, it will not take effect
	boolean matchIfMissing(a) default false;
}
Copy the code
  • Prefix: prefix configured with application.properties
  • Name: Property reads property values from the application.properties configuration file
  • HavingValue: Compares the read attribute value to havingValue, and returns true if the value is the same; Otherwise return false.

If false is returned, the configuration does not take effect. True takes effect

  • MatchIfMissing = true: indicates that if the property is not set in application.properties and does not match, the condition is met by default and the project will be loaded normally without the property property. If matchIfMissing = false, an error is started when the property property is not present.

Such as:

@Configuration
public class WebConfig {
 
    @Bean
    @ConditionalOnProperty(prefix = "rest", name = "auth-open", havingValue = "true", matchIfMissing = true)
    public AuthFilter jwtAuthenticationTokenFilter(a) {
        return newAuthFilter(); }}Copy the code

The code above means: Rest. auth-open=true in the application. Properties file. If rest.auth-open is not set, Or set rest.auth-open=false, we will ignore this property through matchIfMissing = true, the project will start normally and load the configuration information of JWT, ignore this property, The value of havingValue = “true” is compared to the value of the rest.auth-open attribute.

Application.properties is configured as follows:

rest.auth-open=true # JWT authentication enabled (true or false)
Copy the code

6. Create meta-INF /spring.factories in the Resources folder

Create meta-INF/Spring. factories in the Resources folder. When the project starts, you can scan the auto-configuration classes from the meta-INF/Spring. factories file and read the auto-configuration classes through the full path of the classes.

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.wechat.boot.autoconfigure.config.WxAutoConfiguration
Copy the code

Define the starter initiator module

As mentioned earlier, the definition of a starter is very simple. The starter is only used for importing dependencies. The starter relies on the automatic configuration module. So we just need to create an empty Maven project and introduce auto-configuration dependencies.

1. pom.xml


      
<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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.wechat.boot</groupId>
    <artifactId>wechat-spring-boot-starter</artifactId>
    <version>1.0 the SNAPSHOT</version>

    <dependencies>
        <! -- Rely on a custom autoconfigure class, weike-spring-boot-autoconfigure -->
        <dependency>
            <groupId>cn.wechat.boot</groupId>
            <artifactId>wechat-spring-boot-autoconfigure</artifactId>
            <version>1.0 the SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>
Copy the code

Five. Package and install to the warehouse

In the previous two steps, the automatic configuration module and the starter module are defined and configured. Next, we only need to create a standard SpringBoot project and introduce the dependency of the starter module. Because no Maven dependency is transitive, after introducing the starter module, The auto-configuration module’s dependencies are automatically passed over. In this case, you need to package the automatic configuration module and the starter module to the local Maven repository.

  • Automatic configuration module packaging:

  • Starter initiator packaging:

Web projects rely on the starter

To create the SpringBoot project, the POM.xml file imports our custom starter and then configures the properties we need in our custom auto-configuration class in application.properties.

1. pom.xml

  
      
<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.1. RELEASE</version>
        <relativePath/> <! -- lookup parent from repository -->
    </parent>
    <groupId>com.thinkingcao</groupId>
    <artifactId>springboot-starter-test</artifactId>
    <version>0.0.1 - the SNAPSHOT</version>
    <name>springboot-starter-test</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-web</artifactId>
        </dependency>

        <dependency>
            <groupId>cn.wechat.boot</groupId>
            <artifactId>wechat-spring-boot-starter</artifactId>
            <version>1.0 the SNAPSHOT</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
Copy the code

2. Check the dependency status

3. application.properties

wx.config.is-open=enabled
wx.config.appid=111111
wx.config.appkey=222222
wx.config.appsecret=999999
wx.config.token=abcdefg
Copy the code

4. Create the Controller

/ * * *@desc:
 * @author: cao_wencao
 * @date: the 2020-08-25 16:51 * /
@RestController
public class DemoController {

    @Autowired
    private WxDemoService wxDemoService;

    @RequestMapping("/hello")
    public String hello(a){
        String msg = wxDemoService.initLoader("Hello");
        returnmsg; }}Copy the code

5. Start project access:http://localhost:8080/hello

The following information is displayed:IDEA console logs:

2020- 08 -26 17:39:17.466  INFO 9108 --- [nio-8080-exec-6] C.W.B.A.S ervice. WxDemoService: 【 application ID:111111【 Application Key】:222222[Application key] :999999[Application token] : abcdefgCopy the code

6. Customize the starter dependency diagram


Seven, the source code

1. Reference Documents:Springboot’s official documentation describes the starters

2. Source:Github.com/Thinkingcao…


Eight, summary

The above is what we want to talk about today, this article just briefly introduces how to customize a starter of our own, and the correct use of it, and SpringBoot provides us with a large number of starter can enable us to quickly deal with daily various web development, so in addition to the official SpringBoot provides, You can specify the starter initiator for a specific scenario based on the project scenario.