A preface.

Hello, everyone. I haven’t seen you for a long time. Those of you who use SpringBoot are familiar with all kinds of springBoot starter. So in daily work, if we want to develop a starter for other colleagues to use, what should we do?

Ii. Concept explanation

Springboot poM files can be found in many poM packages with starter. This is also a typical feature of SpringBoot. What is a starter? How does it work?

The most common starter for springBoot development is:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
Copy the code

The introduction of this Maven dependency is basically sufficient for everyday Web interface development, as opposed to the introduction of Spring-Web, Spring-webMVC, Spring-AOP, and so on to support project development without SpringBoot.

Other starter implementations, such as Redis, JPA, etc., not only introduce dependencies, but also implement some initial Configuration, such as the @Configuration annotation, which deinstantiates the decorated class when SpringBoot starts. If @enableAutoConfiguration is configured for SpringBoot. The springBoot bootclass contains this annotation by default, so you don’t need to show the import. Finally, you need to add it in the meta-INF /spring.factories** of the starter project:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.xxx.Xxx
Copy the code

In this way, the configuration of the custom starter can be loaded correctly when SpringBoot starts.

Starter simply introduces some dependencies and some initial configurations.

Why do I need to configure meta-INF /spring.factories with the ** @configuration annotation? By default, the SpringBoot project will scan only the @configuration classes ** in the project. If you don’t have a custom starter, you can’t load it, so you need to configure the meta-INF /spring.factories Configuration file.

Why does the meta-INF /spring.factories configuration file load? This is the key to springBoot’s implementation of the starter. Springboot’s configuration loading method is the SPI (Service Provider Interface) type. SPI can configure the implementation class of the Interface extension in the meta-INF/Services. In Springboot, the principle is similar, except the name is changed to spring.factories.

For more information on how springBoot implements the SPI mechanism, it is highly recommended that you read another blog post by the blogger: the @SpringBootApplication’s past and present life, and how springBoot performs fancy bean loading.

Configure class definitions

From the second concept we know that if we want to write a stater that provides automatic configuration managed by SpringBoot, the first thing we need to do is write a configuration class.

To clarify how Statrer is used, this article uses the common-Interaction module of the public microservices framework, which is open source on Github, as an example

Project address: github.com/louyanfeng2…

In our daily work, we need to do certain formatting processing for the date data returned to the front end or the front end to the back end, and show it as YYYY-MM-DD HH: MM :ss format. ** @jsonFormat (pattern = “YYYY-MM-DD HH: MM :ss”) is used in springBoot. In this way, the string yyyY-MM-DD HH: MM :ss passed in on the front end can be mapped to the LocalDateTime** type on the back end.

But the trouble with this is that I have a lot of places to format the input and output of the date, and if I had to deal with each place separately, I would end up with a lot of annotations on the entity class. Springboot convention over configuration principle, we must be a front-end convention, for the date interaction, we are in yyyY-MM-DD HH: MM :ss format, so how do we handle the global?

Added global Jackson serialization configuration ** [SpringBoot uses Jackson for serialization and deserialization by default] **

/ * * * global time format * / @ Bean public Jackson2ObjectMapperBuilderCustomizer customizer () {return builder - > { builder.simpleDateFormat(dateTimeFormat); / / date serialization builder. Serializers (new LocalTimeSerializer (DateTimeFormatter. OfPattern (timeFormat))); builder.serializers(new LocalDateSerializer(DateTimeFormatter.ofPattern(dateFormat))); builder.serializers(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(dateTimeFormat))); / / date deserialization builder. Deserializers (new LocalTimeDeserializer (DateTimeFormatter. OfPattern (timeFormat))); builder.deserializers(new LocalDateDeserializer(DateTimeFormatter.ofPattern(dateFormat))); builder.deserializers(new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(dateTimeFormat))); }; }Copy the code

Ok, at this point, the date formatting input and output have been uniformly configured in the current project. If other projects want to use this functionality, it would be silly to define such a bean inside each project. This configuration is the best choice for the unified basic configuration in starter.

Defined beans are not yet loaded by SpringBoot, and require **@Configuration** to work with them.

@Configuration public class CommonJacksonConfig { public static final String timeFormat = "HH:mm:ss"; public static final String dateFormat = "yyyy-MM-dd"; public static final String dateTimeFormat = "yyyy-MM-dd HH:mm:ss"; / * * * global time format * / @ Bean public Jackson2ObjectMapperBuilderCustomizer customizer () {return builder - > { builder.simpleDateFormat(dateTimeFormat); / / date serialization builder. Serializers (new LocalTimeSerializer (DateTimeFormatter. OfPattern (timeFormat))); builder.serializers(new LocalDateSerializer(DateTimeFormatter.ofPattern(dateFormat))); builder.serializers(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(dateTimeFormat))); / / date deserialization builder. Deserializers (new LocalTimeDeserializer (DateTimeFormatter. OfPattern (timeFormat))); builder.deserializers(new LocalDateDeserializer(DateTimeFormatter.ofPattern(dateFormat))); builder.deserializers(new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(dateTimeFormat))); }; }}Copy the code

Configure spring.factories

After defining the configuration class, you need to expose the bean. This is also done because classes annotated by @springBootApplication will be scanned by default in the same package path as the class annotated by @springBootApplication ** unless a package path is defined. This is why it is recommended that the startup class be placed in the outermost package, to ensure that any beans defined in the current project can be scanned and loaded by SpringBoot.

Create the **/ meta-INF /spring.factories** file in the resources directory and add the configuration

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.baiyan.common.interaction.config.CommonJacksonConfig
Copy the code

5. Optional loading and additional configuration

After the above steps, the autoconfiguration class is ready. But there are two problems.

5.1. Selective loading

The JAR package is provided uniformly, and all the configurations in the JAR package are loaded, but there are some configurations that are invalid for me and I don’t want to turn on, such as the global formatting processing in this article. I don’t want it to load in, but in addition to excluding the class directly where the class is relaunched, we can also annotate the configuration on top of the configuration class

@ConditionalOnProperty(value = "common.config.jackson.enable", havingValue = "true")
Copy the code

So that only the configuration file is configured with common. Config. Jackson. Enable to true when the configuration class is loaded, no configuration or the default configuration is not false is not loaded.

5.2. Additional configuration

If we define a Start with a particularly large number of beans. But most of these beans are interdependent:

  • For example, A depends on B, and B depends on C

  • For example, the validity of A depends on the existence of class B or bean C in the project

  • .

    This can be done by using the @import annotation to load the associated configuration class, or by using ** @ConditionalonBean ** to determine whether to load the configuration.

6. Push

By the fifth step, the starter has been created. You can use Maven to push your defined starter to the company’s private repository so that other projects can directly import the maven configuration you have pushed. If you do not make conditional decisions about loading beans, you can simply start the project and load the configuration. Otherwise, add the corresponding configuration.

Vii. References and references

Introduction to the starter in SpringBoot

Contact me

If you think the article is good, you can like it, comment it, and follow it

Nailing: louyanfeng25

WeChat: baiyan_lou