1、缘起

什么是springboot的starter?比如我们在spring里面要引入redis,那么我们需要在pom中引入以下内容

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

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

在我们的日常开发工作中,经常会有一些独立于业务之外的模块,我们经常将其放到一个特定的包下,然后如果另一个工程需要复用这块功能的时候,需要将代码硬拷贝到另一个工程,重新集成一遍,麻烦至极。如果我们将这些可独立于业务代码之外的功配置模块封装成一个个starter,复用的时候只需要将其在pom中引用依赖即可,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文件
如何在SpringBoot自定义一个starter_加载
文件内容如下(NettyServer的全路径名)
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.bxoon.service.NettyServer
上面这句话的意思就是SpringBoot启动的时候会去加载我们的NettyServer到IOC容器中。这其实是一种变形的SPI机制,什么是SPI呢?可以我的这篇文章

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

2.4、实现ServletContextInitializer接口,使得某个方法在初始化时执行

我们使得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注解吗?
如何在SpringBoot自定义一个starter_加载_02
其实这个@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注解
如何在SpringBoot自定义一个starter_实例化_03
到此热插拔就实现好了
当加了@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时触发实例化。