编写自己的SpringBoot-starter

简介: 编写自己的SpringBoot-starter

关于原理和基本用法的介绍一篇文章讲得非常清楚


《编写自己的SpringBoot-starter》原文地址:https://www.cnblogs.com/yuansc/p/9088212.html


后半段根据遇到的一实际案例,对照讲解。



--------------分割线转载部分----开始-----------


一、前言

我们都知道可以使用SpringBoot快速的开发基于Spring框架的项目。


由于围绕SpringBoot存在很多开箱即用的Starter依赖,使得我们在开发业务代码时能够非常方便的、不需要过多关注框架的配置,而只需要关注业务即可。


例如我想要在SpringBoot项目中集成Redis,那么我只需要加入spring-data-redis-starter的依赖,并简单配置一下连接信息以及Jedis连接池配置就可以。这为我们省去了之前很多的配置操作。


甚至有些功能的开启只需要在启动类或配置类上增加一个注解即可完成。


那么如果我们想要自己实现自己的Starter需要做些什么呢?下面就开始介绍如何实现自己的SpringBoot-xxx-starter。


二、原理

首先说说原理,我们知道使用一个公用的starter的时候,只需要将相应的依赖添加的Maven的配置文件当中即可,免去了自己需要引用很多依赖类,并且SpringBoot会自动进行类的自动配置。那么 SpringBoot 是如何知道要实例化哪些类,并进行自动配置的呢? 下面简单说一下。


首先,SpringBoot 在启动时会去依赖的starter包中寻找 resources/META-INF/spring.factories 文件,然后根据文件中配置的Jar包去扫描项目所依赖的Jar包,这类似于 Java 的 SPI 机制。


第二步,根据 spring.factories配置加载AutoConfigure类。


最后,根据 @Conditional注解的条件,进行自动配置并将Bean注入Spring Context 上下文当中。


我们也可以使用@ImportAutoConfiguration({MyServiceAutoConfiguration.class}) 指定自动配置哪些类。


三、实现

终于到了代码实现的步骤,接下来就开始编码我们自己的SpringBoot-starter。


第一步创建一个SpringBoot 项目,并添加下面两个依赖到pom.xml文件当中


<dependencies>

   <dependency>

       <groupId>org.springframework.boot</groupId>

       <artifactId>spring-boot-configuration-processor</artifactId>

       <optional>true</optional>

   </dependency>

   <dependency>

       <groupId>org.springframework.boot</groupId>

       <artifactId>spring-boot-autoconfigure</artifactId>

   </dependency>

</dependencies>

其中 spring-boot-configuration-processor 的作用是编译时生成 spring-configuration-metadata.json ,此文件主要给IDE使用。如当配置此jar相关配置属性在 application.yml ,你可以用ctlr+鼠标左键点击属性名,IDE会跳转到你配置此属性的类中。


我们日常使用的Spring官方的Starter一般采取spring-boot-starter-{name} 的命名方式,如 spring-boot-starter-web 。


而非官方的Starter,官方建议 artifactId 命名应遵循{name}-spring-boot-starter 的格式。 例如:ysc-spring-boot-starter  。


<groupId>com.ysc</groupId>

<artifactId>simple-spring-boot-starter</artifactId>

<version>1.0.0-SNAPSHOT</version>

<packaging>jar</packaging>

第二步编写我们的Service类


这里讲一下我们的Starter要实现的功能,很简单,提供一个Service,包含一个能够将配置文件中配置的字符串根据传入的字符进行分割的方法String[] split(String separatorChar)。


public class StarterService {

   private String config;

   public StarterService(String config) {

       this.config = config;

   }

 

   public String[] split(String separatorChar) {

       return StringUtils.split(this.config, separatorChar);

   }

 

}

第三步编写配置文件读取类


@ConfigurationProperties("example.service")

public class StarterServiceProperties {

   private String config;

 

   public void setConfig(String config) {

       this.config = config;

   }

 

   public String getConfig() {

       return config;

   }

}

第四步,编写AutoConfigure类 ,这步是关键点


@Configuration

@ConditionalOnClass(StarterService.class)

@EnableConfigurationProperties(StarterServiceProperties.class)

public class StarterAutoConfigure {

   @Autowired

   private StarterServiceProperties properties;

   @Bean

   @ConditionalOnMissingBean

   @ConditionalOnProperty(prefix = "example.service", value = "enabled", havingValue = "true")

   StarterService starterService (){

       return new StarterService(properties.getConfig());

   }

}

解释一下代码中用到的几个注解:


@ConditionalOnClass,当classpath下发现该类的情况下进行自动配置。

@ConditionalOnMissingBean,当Spring Context中不存在该Bean时。

@ConditionalOnProperty(prefix = "example.service",value = "enabled",havingValue = "true"),当配置文件中example.service.enabled=true时。

下面列举SpringBoot中的所有@Conditional注解及作用


@ConditionalOnBean:当容器中有指定的Bean的条件下  

@ConditionalOnClass:当类路径下有指定的类的条件下  

@ConditionalOnExpression:基于SpEL表达式作为判断条件  

@ConditionalOnJava:基于JVM版本作为判断条件  

@ConditionalOnJndi:在JNDI存在的条件下查找指定的位置  

@ConditionalOnMissingBean:当容器中没有指定Bean的情况下  

@ConditionalOnMissingClass:当类路径下没有指定的类的条件下  

@ConditionalOnNotWebApplication:当前项目不是Web项目的条件下  

@ConditionalOnProperty:指定的属性是否有指定的值  

@ConditionalOnResource:类路径下是否有指定的资源  

@ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者在有多个Bean的情况下,用来指定首选的Bean @ConditionalOnWebApplication:当前项目是Web项目的条件下  

最后一步,在resources/META-INF/下创建spring.factories文件,并添加如下内容:


org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.autocinfigure.StarterAutoConfigure

至此,我们的一个Starter代码部分就是完成了,下面将项目安装到本地Maven仓库中。


四、发布

在项目根目录执行 mvn install 进行打包安装。


五、测试

将Starter项目的依赖添加到我们自己的SpringBoot项目中


<dependency>

   <groupId>com.ysc</groupId>

   <artifactId>simple-spring-boot-starter</artifactId>

   <version>1.0-SNAPSHOT</version>

</dependency>

在application.yml 配置文件中添加配置信息:


example

 service

   enabled: true

   config: abc-des-dde,SSS-DRS-RE,SDR-SDFR-XXX

在本地使用JUnit进行代码测试


@Autowired

private StarterService starterService;

@Test

public void starterTest() {

   String[] splitArray = starterService.split(",");

   System.out.println(splitArray);

}

好,到这我们的一个自定义Stater就完成了


--------------分割线转载部分----结束-----------



下面以https://github.com/akihyro/orika-spring-boot-starter为例,对照学习一下


首先名称符合上文说的规则:orika-spring-boot-starter


然后pom.xml中添加了上文说的依赖:


https://github.com/akihyro/orika-spring-boot-starter/blob/master/orika-spring-boot-starter/pom.xml


配置项属性类


package net.rakugakibox.spring.boot.orika;

import lombok.Data;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**

* The configuration properties for Orika.

*/

@Data

@ConfigurationProperties("orika")

public class OrikaProperties {

   /**

    * Whether to enable auto-configuration.

    */

   private boolean enabled = true;

   /**

    * Whether to use built-in converters (MapperFactoryBuilder#useBuiltinConverters(boolean)).

    * Follows Orika's behavior by default.

    */

   private Boolean useBuiltinConverters;

   /**

    * Whether to use auto-mapping (MapperFactoryBuilder#useAutoMapping(boolean)).

    * Follows Orika's behavior by default.

    */

   private Boolean useAutoMapping;

   /**

    * Whether to map null values (MapperFactoryBuilder#mapNulls(boolean)).

    * Follows Orika's behavior by default.

    */

   private Boolean mapNulls;

   /**

    * Whether to dump the current state of the mapping infrastructure objects

    * upon occurrence of an exception while mapping (MapperFactoryBuilder#dumpStateOnException(boolean)).

    * Follows Orika's behavior by default.

    */

   private Boolean dumpStateOnException;

   /**

    * Whether the class-map should be considered 'abstract' (MapperFactoryBuilder#favorExtension(boolean)).

    * Follows Orika's behavior by default.

    */

   private Boolean favorExtension;

   /**

    * Whether full field context should be captured (MapperFactoryBuilder#captureFieldContext(boolean)).

    * Follows Orika's behavior by default.

    */

   private Boolean captureFieldContext;

}

也使用了 @ConfigurationProperties注解


然后配置类:OrikaAutoConfiguration


package net.rakugakibox.spring.boot.orika;

import java.util.Collections;

import java.util.List;

import java.util.Optional;

import lombok.RequiredArgsConstructor;

import lombok.extern.slf4j.Slf4j;

import ma.glasnost.orika.MapperFacade;

import ma.glasnost.orika.MapperFactory;

import ma.glasnost.orika.impl.DefaultMapperFactory;

import ma.glasnost.orika.impl.DefaultMapperFactory.MapperFactoryBuilder;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;

import org.springframework.boot.context.properties.EnableConfigurationProperties;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

/**

* The auto-configuration for Orika.

*/

@RequiredArgsConstructor

@Slf4j

@ConditionalOnProperty(name = "orika.enabled", matchIfMissing = true)

@EnableConfigurationProperties(OrikaProperties.class)

@Configuration

public class OrikaAutoConfiguration {

   /**

    * The configuration properties for Orika.

    */

   private final OrikaProperties orikaProperties;

   /**

    * The configurers for {@link MapperFactoryBuilder}.

    */

   private final Optional<List<OrikaMapperFactoryBuilderConfigurer>> orikaMapperFactoryBuilderConfigurers;

   /**

    * The configurers for {@link MapperFactory}.

    */

   private final Optional<List<OrikaMapperFactoryConfigurer>> orikaMapperFactoryConfigurers;

   /**

    * Creates a {@link MapperFactoryBuilder}.

    *

    * @return a {@link MapperFactoryBuilder}.

    */

   @ConditionalOnMissingBean

   @Bean

   public MapperFactoryBuilder<?, ?> orikaMapperFactoryBuilder() {

       DefaultMapperFactory.Builder orikaMapperFactoryBuilder = new DefaultMapperFactory.Builder();

       if (orikaProperties.getUseBuiltinConverters() != null) {

           orikaMapperFactoryBuilder.useBuiltinConverters(orikaProperties.getUseBuiltinConverters());

       }

       if (orikaProperties.getUseAutoMapping() != null) {

           orikaMapperFactoryBuilder.useAutoMapping(orikaProperties.getUseAutoMapping());

       }

       if (orikaProperties.getMapNulls() != null) {

           orikaMapperFactoryBuilder.mapNulls(orikaProperties.getMapNulls());

       }

       if (orikaProperties.getDumpStateOnException() != null) {

           orikaMapperFactoryBuilder.dumpStateOnException(orikaProperties.getDumpStateOnException());

       }

       if (orikaProperties.getFavorExtension() != null) {

           orikaMapperFactoryBuilder.favorExtension(orikaProperties.getFavorExtension());

       }

       if (orikaProperties.getCaptureFieldContext() != null) {

           orikaMapperFactoryBuilder.captureFieldContext(orikaProperties.getCaptureFieldContext());

       }

       orikaMapperFactoryBuilderConfigurers

               .orElseGet(Collections::emptyList)

               .forEach(configurer -> configurer.configure(orikaMapperFactoryBuilder));

       log.debug("Created a MapperFactoryBuilder: [{}]", orikaMapperFactoryBuilder);

       return orikaMapperFactoryBuilder;

   }

   /**

    * Creates a {@link MapperFactory}.

    *

    * @param orikaMapperFactoryBuilder the {@link MapperFactoryBuilder}.

    * @return a {@link MapperFactory}.

    */

   @ConditionalOnMissingBean

   @Bean

   public MapperFactory orikaMapperFactory(MapperFactoryBuilder<?, ?> orikaMapperFactoryBuilder) {

       MapperFactory orikaMapperFactory = orikaMapperFactoryBuilder.build();

       orikaMapperFactoryConfigurers

               .orElseGet(Collections::emptyList)

               .forEach(configurer -> configurer.configure(orikaMapperFactory));

       log.debug("Created a MapperFactory: [{}]", orikaMapperFactory);

       return orikaMapperFactory;

   }

   /**

    * Creates a {@link MapperFacade}.

    *

    * @param orikaMapperFactory the {@link MapperFactory}.

    * @return a {@link MapperFacade}.

    */

   @ConditionalOnMissingBean

   @Bean

   public MapperFacade orikaMapperFacade(MapperFactory orikaMapperFactory) {

       MapperFacade orikaMapperFacade = orikaMapperFactory.getMapperFacade();

       log.debug("Created a MapperFacade: [{}]", orikaMapperFacade);

       return orikaMapperFacade;

   }

}


resources/META-INF/spring.factories 文件


# Spring Boot Auto-configuration

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\

net.rakugakibox.spring.boot.orika.OrikaAutoConfiguration

使用时引用jar包直接声明即可


因为在新的项目中没有声明这个bean,而且配置项类中的enable默认为ture,因此 starter配置中的bean生效。


https://github.com/akihyro/orika-spring-boot-starter/blob/master/orika-spring-boot-starter-sample/src/main/java/net/rakugakibox/spring/boot/orika/sample/Application.java

————————————————

版权声明:本文为CSDN博主「明明如月学长」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/w605283073/article/details/89303756

相关文章
|
Java Spring
Springboot starter开发之traceId请求日志链路追踪
能标识一次请求的完整流程,包括日志打印、响应标识等,以便于出现问题可以快速定位并解决问题。
1815 0
Springboot starter开发之traceId请求日志链路追踪
|
消息中间件 IDE Java
|
XML Java 程序员
保姆级教程,手把手教你实现SpringBoot自定义starter
保姆级教程,手把手教你实现SpringBoot自定义starter
9197 1
保姆级教程,手把手教你实现SpringBoot自定义starter
|
SpringCloudAlibaba NoSQL Java
1.1 w字,18 张图,彻底说透 springboot starter 机制
1.1 w字,18 张图,彻底说透 springboot starter 机制
796 1
1.1 w字,18 张图,彻底说透 springboot starter 机制
|
Java 程序员 网络安全
自定义spring boot starter三部曲之二:实战开发
《自定义spring boot starter三部曲》的第二篇,开始编码实战,开发并使用starter库
287 0
自定义spring boot starter三部曲之二:实战开发
|
NoSQL Java Redis
如何自定义一个SpringBoot中的starter
如何自定义一个SpringBoot中的starter
215 0
如何自定义一个SpringBoot中的starter
|
NoSQL Java 应用服务中间件
SpringBoot项目为什么需要引入大量的starter?如何自定义starter?
为什么我们在使用SpringBoot框架开发Java Web应用需要引入大量的starter?例如,我们引入Redis就在Maven中导入spring-boot-starter-data-redis。大家都知道SpringBoot的核心功能是自动装配,简化配置,我们通过starter实现SpringBoot自动装配的功能。那么我们如何去构建自己的starter呢?
311 0
|
NoSQL Java Redis
如何使用SpringBoot写一个属于自己的Starter
SpringBoot以其自动装配的能力被广泛应用,我们在写代码时肯定遇到过很多spring-boot-starter命名的依赖,比如spring-boot-starter-web,在pom文件中引入这些starter依赖后,SpringBoot就能通过自动装配的技术扫描到这些类并装载到Bean容器中。
|
Java 程序员 开发者
自定义spring boot starter三部曲之三:源码分析spring.factories加载过程
分析Spring和Spring boot源码,了解spring.factories自动加载原理
342 0
自定义spring boot starter三部曲之三:源码分析spring.factories加载过程
|
监控 Java 应用服务中间件
SpringBoot——四大核心之起步依赖(自定义starter)
这篇文章呢,我来和大家聊聊起步依赖这个东西。
1205 0
SpringBoot——四大核心之起步依赖(自定义starter)