编写你的第一个spring-boot-starter

前言

我们在使用spring-boot的时候,会经常用到各种各样的starter,比如spring-boot-starter-web,不知道各个小伙伴有没有好奇过这些starter到底是怎么定义出来的,反正我好奇过,但是一直没有去深入了解过,最近在项目开发中,我们需要封装一个mq的通用组件,有个同事就封装成一个starter,然后就勾起了学习和研究的好奇心,所以想着趁今天的时间做一个小demo,写一个属于自己的starter

下面我们就来看下具体如何实现。

手写spring-boot-starter

创建项目

首先我们要创建一个maven项目,根据自己的需要引入项目依赖,因为我们要写的是sprin-bootstarter,所以spring-boot-starter的依赖必须引入:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.3.7.RELEASE</version>
    <scope>compile</scope>
    <optional>true</optional>
</dependency>

因为starter的核心其实是配置类,而这些配置类注解都在spring-boot-starter包下。创建完成后的项目结构如下:

编写你的第一个spring-boot-starter

我的这个组件是一个通用的消息发送组件,所以我还引入activemq的相关包,对demo感兴趣的小伙伴可以直接去看项目源码,文末有地址。

配置类

这里就是starter的核心了,这里我们要对组件进行配置,主要是bean的注入:

@Configuration
@EnableJms
@ConditionalOnClass(JmsMessageServiceImpl.class)
public class AutoConfigurationClass {

    @Value("${spring.activemq.broker-url}")
    private String brokerURL;

    @ConditionalOnMissingBean
    @Bean
    public JmsMessageService jmsMessageService(JmsMessagingTemplate jmsTemplate){
        return new JmsMessageServiceImpl(jmsTemplate);
    }

    @ConditionalOnMissingBean
    @Bean
    public JmsMessagingTemplate jmsMessagingTemplate(ConnectionFactory connectionFactory) {
        return new JmsMessagingTemplate(connectionFactory);
    }

    @ConditionalOnMissingBean
    @Bean
    public ConnectionFactory connectionFactory() {
        return new ActiveMQConnectionFactory(brokerURL);
    }
}

前面说了我的组件是通用的消息组件,所以我这里主要是针对ActiveMq的一些配置,包括JmsMessagingTemplateJmsMessageServiceConnectionFactory

这里还通过@Value注解注入了mq的地址,这个地址需要在starter的使用项目中注入,后面我们会演示。

@EnableJms注解的作用是启用消息组件,如果没有这个注解,整个消息组件就没啥用了;

@ConditionalOnClass注解的作用是,只有在至指定的class(这里的JmsMessageServiceImpl.class)存在的时候(也就是依赖了这个类对应的包),配置才会生效(这样看我这里的这个配置没啥用),类似的配置还有好几个,后面研究下;

@ConditionalOnMissingBean注解起的是标记作用,通常和@Bean一起使用,如果加了这个注解,这个类就不允许重复注入了。

业务实现

这里的业务实现就是普通的java实现,前面我们已经在配置类中以及注入过这个类的实例了,后面在引用当前starterspring-boot项目中就可以直接通过@AutoWired注解使用了

public class JmsMessageServiceImpl implements JmsMessageService {
    private final Logger logger = LoggerFactory.getLogger(JmsMessageServiceImpl.class);

    private JmsMessagingTemplate jmsTemplate;

    public JmsMessageServiceImpl(JmsMessagingTemplate jmsTemplate) {
        this.jmsTemplate = jmsTemplate;
    }



    @Override
    public void sendMessage(String mqQueueName, String message) {
        logger.info("method: [sendMessage] input parameter: mqQueueName = {}, message = {}", mqQueueName, message);
        jmsTemplate.convertAndSend(mqQueueName, message);
    }

    @Override
    public MessageReceiveVO sendAndReceive(String mqQueueName, String message) {
        logger.info("method: [sendMessage] input parameter: mqQueueName = {}, message = {}", mqQueueName, message);
        Message<?> messageBack = jmsTemplate.sendAndReceive(mqQueueName, new StringMessage(message));
        String payload = (String) messageBack.getPayload();
        logger.info("method: [sendMessage] return result: payload = {}", payload);
        return JSON.parseObject(payload, MessageReceiveVO.class);
    }

    class StringMessage implements Message<String> {

        private String payload;
        private MessageHeaders messageHeaders;

        public StringMessage(String payload) {
            this.payload = payload;
        }

        @Override
        public String getPayload() {
            return this.payload;
        }

        @Override
        public MessageHeaders getHeaders() {
            return this.messageHeaders;
        }
    }
}

META-INF文件编写

这里才是starter组件的重中之重,如果没有这里的配置文件,你的组件并不会被spring-boot发现。

我们创建一个名字叫spring.factories的文件,然后在文件中添加如下内容:

#-------starter自动装配---------
org.springframework.boot.autoconfigure.EnableAutoConfiguration=io.github.syske.starter.demo.config.AutoConfigurationClass

这里文件名字是固定的,其他名称是无法识别的,文件中的org.springframework.boot.autoconfigure.EnableAutoConfiguration也是固定的,就是一个键名,后面的io.github.syske.starter.demo.config.AutoConfigurationClass是我们配置类的名称,根据spring-boot使用经验,这个配置应该是支持多个类的,用逗号分隔应该就好了,我还没来得及试验,有兴趣的小伙伴自己尝试下。

然后到这里我们的starter就编写完成了,下面我们要打包,然后测试。

spring-boot-starter打包安装

这里打包也很简单,我们直接使用maveninstall工具就可以,需要注意的是,我们要在pom.xml中指定打包类型:

编写你的第一个spring-boot-starter

点击install菜单后,我们的start会被安装到本地maven仓库中

编写你的第一个spring-boot-starter

测试

因为stater是要引入spring-boot项目中才能使用的,所以我们要先创建一个spring-boot项目,然后引入我们刚才打的starter

编写你的第一个spring-boot-starter

这里我们还要在配置文件中添加mq的地址:

编写你的第一个spring-boot-starter

然后我们直接在单元测试中测试下我们的stater:

@SpringBootTest
class SpringBootSraterTestApplicationTests {

    @Autowired
    private JmsMessageService jmsMessageService;

    @Test
    void contextLoads() {
        jmsMessageService.sendMessage("spring_boot_starter", "hello spring-boot-start");
    }

}

直接运行这个方法,然后我们登录mq的管理台看下:

编写你的第一个spring-boot-starter

可以看到我们的消息已经成功发送到mq中了,说明我们的starter组件已经运行成功了。

总结

spring-boot-starter确实用起来很方便,感觉就像一个插座一样,随插即用,可以说通过spring-boot-starter我们可以真正做到组件化的模块编程,而且在我们的演示项目中,如果我们mq的地址也是固定的话,那我们甚至连配置文件都不需要了,只需要引入starter依赖即可使用其中的spring-boot组件,简直不要太方便。

好了,手写starter就到这里吧,踩坑过程确实比较费时间,所以今天也就更的有点晚了,不过还好,总算完了

上一篇:Sentinel


下一篇:java -springboot+springcloud+zookeeper