你写过starter么,怎么写的?

273 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情

前言

大概在一个多月前,我参加面试,一个身穿格子衫牛仔裤大概三十多岁的中年男人缓步走来。

先做个自我介绍吧

面试官你好,我叫。。。我巴拉巴拉介绍完。

你了解springboot的自动装配吧。

我当然说了解喽。

那你写过starter么,怎么写的?

我当然说我不知道啦。那是个什么东西,我怎么会写。。。

starter是个啥

我们可以把starter看成一个模块。他整合了模块所需要的依赖和自动化配置模块内的bean。只需要依赖这个starter,Spring Boot就能自动扫描并加载相应的模块。

给他一个总结吧

  • 整合了所需依赖库
  • 提供自动配置类对模块内的Bean进行自动装配
  • 提供对模块的配置项,使用者可以自定义配置

写个starter

开发步骤

  • 创建maven工程并在pom文件添加所需要的依赖
  • 创建配置类,定义配置项,指明配置前缀
  • 创建自动装配类
  • 在resources下面创建META-INF文件夹,在META-INF下面创建spring.factories,然后在这个文件里面定义需要自动装配的类

正式开始

我们就写个简单的线程池工厂来试试如何写一个starter

在pom文件添加所需要的依赖

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

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-autoconfigure</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

<!-- 这个插件要求项目必须要有main方法才能编译 -->
<!--<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>-->

创建配置类,定义配置项,指明配置前缀

定义一个创建线程池所需要的配置类

创建线程池需要那些配置项

  • 核心线程数
  • 最大线程数
  • 存活时间
  • 时间单位TimeUnit
  • 阻塞队列
  • 线程工厂
  • 拒绝策略

我们将前三位参数定义到配置项(后面的都是对象,不好在配置文件配置;如果有好的方法可以配置的,可以交流一下),前缀名定义为:pool。

涉及到一个注解

  • @ConfigurationProperties 指定配置类

看下代码

@ConfigurationProperties(prefix = ThreadPoolProperties.PREFIX)
public class ThreadPoolProperties {

    public static final String PREFIX = "thread";

    /**
     * 线程工厂名
     */
    private String factoryName;

    /**
     * 业务线程名
     */
    private String businessName;

    /**
     * 线程池配置
     */
    private PoolConfig poolConfig;

    public String getFactoryName() {
        return factoryName;
    }

    public void setFactoryName(String factoryName) {
        this.factoryName = factoryName;
    }

    public String getBusinessName() {
        return businessName;
    }

    public void setBusinessName(String businessName) {
        this.businessName = businessName;
    }

    public PoolConfig getPoolConfig() {
        return poolConfig;
    }

    public void setPoolConfig(PoolConfig poolConfig) {
        this.poolConfig = poolConfig;
    }

    public static class PoolConfig{
        /**
         * 核心线程数
         */
        private Integer corePoolSize;
        /**
         * 最大线程数
         */
        private Integer maxPoolSize;
        /**
         * 超出核心线程数的线程存活时间
         */
        private Long keepAliveTime;

        public Integer getCorePoolSize() {
            return corePoolSize;
        }

        public void setCorePoolSize(Integer corePoolSize) {
            this.corePoolSize = corePoolSize;
        }

        public Integer getMaxPoolSize() {
            return maxPoolSize;
        }

        public void setMaxPoolSize(Integer maxPoolSize) {
            this.maxPoolSize = maxPoolSize;
        }

        public Long getKeepAliveTime() {
            return keepAliveTime;
        }

        public void setKeepAliveTime(Long keepAliveTime) {
            this.keepAliveTime = keepAliveTime;
        }
    }
}

factoryName和businessName是为了给线程池的线程去个有区别度的名字,看下面的线程工厂就知道了。

public class DefaultThreadFactory implements ThreadFactory {

    /**
     * 线程名前缀
     */
    private final StringBuilder namePrefix = new StringBuilder();

    /**
     * 线程数量计数器
     */
    private final AtomicInteger nextId = new AtomicInteger(1);

    /**
     *
     * @param factoryName 工厂名
     * @param businessName 业务名
     */
    public DefaultThreadFactory(String factoryName, String businessName) {
        namePrefix.append("pool-").append(factoryName).append("-").append(businessName).append("-worker-");
    }

    @Override
    public Thread newThread(Runnable r) {
        return new Thread(null,r,namePrefix.toString() + nextId.getAndIncrement(),0);
    }
}

创建自动装配类

线程池的配置已经做好了,接下来我们就要实例化一个线程池工厂了。需要添加两个注解:

  • @Configuration 定义实例化配置类
  • @EnableConfigurationProperties 自动配置属性
@Configuration
@EnableConfigurationProperties(ThreadPoolProperties.class)
public class ThreadPoolConfiguration {

    @Bean
    public ThreadPoolFactory threadPoolFactory(ThreadPoolProperties threadPoolProperties){
        return new ThreadPoolFactory(threadPoolProperties);
    }
}

往spring.factories添加需要自动装配的类

首先创建spring.factories文件,如图

image.png

然后就是添加自动装配的类了

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com/mumu/wen/threadPool/config/ThreadPoolConfiguration

最后我们编译一下这个maven项目,推送到maven仓库就可以引入使用了。

如何使用这个自定义starter

引入依赖

<dependency>
    <groupId>com.mumu.wen</groupId>
    <artifactId>thread-pool-springboot-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

在application.yml配置属性

thread:
  business-name: test
  factory-name: test-factory
  pool-config:
    core-pool-size: 2
    max-pool-size: 10
    keep-alive-time: 100

项目中使用

@Autowired
private ThreadPoolFactory threadPoolFactory;


threadPoolFactory.newDefaultThreadPool().execute(() -> {
    System.out.println("#######################自定义线程池starter已启动!" + Thread.currentThread().getName() + "########################");
});

结尾

starter是不是并没有想象的那么遥不可及,如果下次还有面试官问这个问题。直接把项目地址甩给他。哈哈哈。