如何在SpringBoot自定义一个starter

Posted 诺浅

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何在SpringBoot自定义一个starter相关的知识,希望对你有一定的参考价值。

1、什么是SpringBootStarter

还记得我们在SpringBoot项目里面是如何使用Redis的吗?我们需要在pom中引入以下内容

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

他的名称是spring-boot-starter-data-redis,同理如果你要引入mq,你需要在这里填的是spring-boot-starter-data-xxmq
他们的名称一般是以spring-boot-starter开头,这其实就是一个starter

starter就是一个外部的项目,我们需要使用它的时候就可以在当前springboot项目中引入它。

在我们的日常开发工作中,经常会有一些独立于业务之外的模块,我们经常将其放到一个特定的包下,然后如果另一个工程需要复用这块功能的时候,需要将代码硬拷贝到另一个工程,重新集成一遍,这样做虽然可以解决问题,但始终不够优雅。

如果我们将这些可独立于业务代码之外的功配置模块封装成一个个starter,复用的时候只需要将其在pom.xml中引用依赖即可,SpringBoot为我们完成自动装配,简直不要太爽。

2、如何自定义一个Starter

SpringBoot提供的starter以spring-boot-starter-xxx的方式命名的。官方建议自定义的starter使用xxx-spring-boot-starter命名规则。以区分SpringBoot生态提供的starter。

2.1、新建一个工程im
里面有一个类NettyServer,这个类的功能是启动一个Netty服务(做什么事本身并不重要,本文讲的是如何与springboot集成)

public class NettyServer
    public void run() 
        System.out.println("启动netty服务");
    

2.2、新建一个spingboot工程service并引入im项目的依赖

我们想在这个service工程中引用im工程NettyServer,然后启动一个Netty服务。那么应该怎么做呢?

先在pom中引入im的工程

<dependency>
  <groupId>com.bxoon</groupId>
  <artifactId>im-spring-boot-starter</artifactId>
  <version>0.0.1-SNAPSHOT</version>
</dependency>

2.3、改造im工程

首先我们需要在resources下面新建META-INF文件夹,然后创建spring.factories文件

文件内容如下(NettyServer的全路径名)
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.bxoon.service.NettyServer
上面这句话的意思就是SpringBoot启动的时候会去加载我们的NettyServer到IOC容器中。这其实是一种变形的SPI机制,什么是SPI呢?可以我的这篇文章

理论上这个时候我们启动SpringBoot,Sping就会把NettyServer类加载到Bean容器中。

2.4、如何使得Stater中的代码在项目初始化时被执行?

我们使得NettyServer实现ServletContextInitializer接口,然后实现这个接口的onStartup方法中调用run方法,最终NettyServer的代码如下所示

public class NettyServer implements ServletContextInitializer 

    public void run() 
        System.out.println("启动netty服务");
    

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException 
        run();
    

如此我们就可以在项目启动的时候NettyServerrun方法被执行。
到此为止,启动service工程,就会看到控制台输出下面这句话

启动netty服务

说明启动netty服务成功了。

注意:实现ServletContextInitializer 的方法只会在使用java -jar这种方式启动时才会执行(idea里面也会执行),如果你的项目是打包成war包并放入tomcat等符合servlet3.1规范的容器中运行,则需要实现WebApplicationInitializer方法。所以我觉得吧,两个都实现吧(不知道会不会有问题,比如会加载两次?讲道理应该是不会)

public class NettyServer implements ServletContextInitializer,WebApplicationInitializer 

3、热插拔技术的实现

如果有一天我们不想要启动service工程的时候启动netty服务怎么办呢?有人说那简单啊,我们去pom中把im的依赖注释掉,的确,这是一种方案,但Spring提供了更好的解决方案。
还记得我们经常会在启动类Application上面加@EnableXXX注解吗?

其实这个@Enablexxx注解就是一种热拔插技术,加了这个注解就可以启动对应的starter,当不需要对应的starter的时候只需要把这个注解注释掉就行,是不是很优雅呢?那么这是如何实现的呢?

3.1、改造im工程新增热插拔支持类

新增标记类ImConfigMarker

public class ImConfigMarker 
    public ImConfigMarker() 
    

新增EnableImRegisterServer注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ImConfigMarker.class)
public @interface EnableImRegisterServer 

改造NettyServer新增条件注解@ConditionalOnBean(ImConfigMarker.class)@ConditionalOnBean这个是条件注解,前面的意思代表只有当上下文中含有ImConfigMarker对象,被标注的类才会被实例化。

@ConditionalOnBean(ImConfigMarker.class)
public class NettyServer implements ServletContextInitializer 

    public void run() 
        System.out.println("启动netty服务");
    

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException 
        run();
    

3.2、改造service工程

在启动类上新增@EnableImRegisterServer注解

到此热插拔就实现好了
当加了@EnableImRegisterServer注解的时候,由于这个注解使用了@Import(ImConfigMarker.class),所以会导致Spring去加载ImConfigMarker到上下文中,而又因为条件注解@ConditionalOnBean(ImConfigMarker.class)的存在,所以NettyServer类就会被实例化。
而当没有@EnableImRegisterServer注解的时候,就不会加载ImConfigMarker到上下文中,就不满足条件注解@ConditionalOnBean(ImConfigMarker.class),进而导致NettyServer不会被加载。

4、关于Spring中的条件注解的讲解

前面我们在热插拔小结中说的@ConditionalOnBean其实是Spring中的一个条件注解,除此之外,Spring还提供了很多条件注解。

  • @ConditionalOnBean:仅仅在当前上下文中存在某个对象时,才会实例化一个Bean。
  • @ConditionalOnClass:某个class位于类路径上,才会实例化一个Bean。
  • @ConditionalOnExpression:当表达式为true的时候,才会实例化一个Bean。基于SpEL表达式的条件判断。
  • @ConditionalOnMissingBean:仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean。
  • @ConditionalOnMissingClass:某个class类路径上不存在的时候,才会实例化一个Bean。
  • @ConditionalOnNotWebApplication:不是web应用,才会实例化一个Bean。
  • @ConditionalOnWebApplication:当项目是一个Web项目时进行实例化。
  • @ConditionalOnNotWebApplication:当项目不是一个Web项目时进行实例化。
  • @ConditionalOnProperty:当指定的属性有指定的值时进行实例化。
  • @ConditionalOnJava:当JVM版本为指定的版本范围时触发实例化。
  • @ConditionalOnResource:当类路径下有指定的资源时触发实例化。
  • @ConditionalOnJndi:在JNDI存在的条件下触发实例化。
  • @ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时触发实例化。

以上是关于如何在SpringBoot自定义一个starter的主要内容,如果未能解决你的问题,请参考以下文章

自定义SpringBoot Starter 实现请求日志打印

自定义SpringBoot Starter 通过注解启动装配

SpringBoot自定义starter

springboot 源码解析## 如何自定义 starter 让 springboot 扫描到你的配置

如何禁用 TomcatServletWebServerFactory 的 SpringBoot 自动配置以便自定义 spring-starter 提供它?

自定义的Spring Boot starter如何设置自动配置注解