首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

创建spring boot starter

背景介绍

Spring Boot为开发提供了极大的便利,开发者可以使用Spring Boot快速构建自己的应用。比如需要在Spring Boot中使用Rabbit MQ,我们只需要引入依赖以下依赖:

代码语言:javascript
复制

<dependency>

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

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

</dependency>x

然后在application.properties或application.yml中添加以下数据库配置信息:

代码语言:javascript
复制

 spring:  

  rabbitmq:

    addresses: ${RABBIT_ADDRESS:192.168.0.100:5672}

    username: ${RABBIT_USERNAME:guest}

    password: ${RABBIT_PASSWORD:guest}

然后我们就可以自动注入RabbitTemplate了。

代码语言:javascript
复制

@Service

public class MessageService {

  @Autowired

  RabbitTemplate rabbitTemplate;

}

这一切的核心,就是自动装配。


Spring Boot 自动装配原理

Spring Boot依赖spring-boot-autoconfigure,这个jar包是帮助spring boot自动装配所有需要的依赖。

代码语言:javascript
复制

<dependency>

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

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

</dependency>

创建一个spring boot工程,系统会自动引入这个jar包,查看jar包下的META-INF --> spring.factories文件,会发现默认自动添加了许多模块。比如我们上面讲到的RabbitAutoConfiguration。

代码语言:javascript
复制

# Initializers

org.springframework.context.ApplicationContextInitializer=\

org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\

org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners

org.springframework.context.ApplicationListener=\

org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners

org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\

org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters

org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\

org.springframework.boot.autoconfigure.condition.OnBeanCondition,\

org.springframework.boot.autoconfigure.condition.OnClassCondition,\

org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

# Auto Configure

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\

org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\

org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\

org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\  # 注意看这里...

org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\

# 其它的省略了

重要的事情说三遍,以下内容为:重点,重点,重点!

Spring Boot在启动时,会解析这些配置文件。我们看到,org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration这个类被加进去了,我们以这个作为列子讲解。

展开spring-boot-autoconfigure下的org.springframework.boot.autoconfigure --> amqp,找到RabbitAutoConfiguration,打开其源代码:

代码语言:javascript
复制

  @Configuration(proxyBeanMethods = false)

@ConditionalOnClass({ RabbitTemplate.class, Channel.class }) // 参见下边注释1

@EnableConfigurationProperties(RabbitProperties.class) // 参见下边注释2

@Import(RabbitAnnotationDrivenConfiguration.class)

public class RabbitAutoConfiguration {

		// 其它代码省略...

		@Bean

		@ConditionalOnSingleCandidate(ConnectionFactory.class)

		@ConditionalOnMissingBean(RabbitOperations.class)

		public RabbitTemplate rabbitTemplate(RabbitTemplateConfigurer configurer, ConnectionFactory connectionFactory) {

			RabbitTemplate template = new RabbitTemplate();

			configurer.configure(template, connectionFactory);

			return template;

		}

		@Bean

		@ConditionalOnSingleCandidate(ConnectionFactory.class)

		@ConditionalOnProperty(prefix = "spring.rabbitmq", name = "dynamic", matchIfMissing = true)

		@ConditionalOnMissingBean	// 参见下边注释3

		public AmqpAdmin amqpAdmin(ConnectionFactory connectionFactory) {

			return new RabbitAdmin(connectionFactory);

		}

	}

}

可以看到,在这个类里边,我们注入了RabbitTemplate和AmqpAdmin,这就是我们可以在自己的代码中自动注入这两个类实列的原因。

这里需要解析几个注解:

1. @ConditionalOnClass({ RabbitTemplate.class, Channel.class })

这个注解的意思是,如果Spring Boot应用程序中包含RabbitTemplate和Channel这两个类,RabbitAutoConfiguration配置文件才生效,否则不解析配置文件。所以当我们引入spring-boot-starter-amqp依赖的时候,这个配置就生效了,Spring Boot就会帮我们创建RabbitTemplate的实例。

2. @EnableConfigurationProperties(RabbitProperties.class)

这个注解就是注入RabbitProperties实例,这是一个读取配置文件的的配置类,代码如下:

代码语言:javascript
复制

@ConfigurationProperties(prefix = "spring.rabbitmq")

public class RabbitProperties {

	private static final int DEFAULT_PORT = 5672;

	private static final int DEFAULT_PORT_SECURE = 5671;

	/**

	 * RabbitMQ host. Ignored if an address is set.

	 */

	private String host = "localhost";

	/**

	 * RabbitMQ port. Ignored if an address is set. Default to 5672, or 5671 if SSL is

	 * enabled.

	 */

	private Integer port;

	/**

	 * Login user to authenticate to the broker.

	 */

	private String username = "guest";

  // 省略其它代码...

这个类就是从我们的yml配置文件中读取Rabbit 连接等配置信息。

3. @ConditionalOnMissingBean , 这是放在创建Bean的方法上测注解,意思是如果Amqpadmin这个类存在,就不创建了。这给用户灵活的选择,可以创建自己的Ampqadmin来管理Rabbit MQ,也可以不创建,使用系统默认的。

重点结束啦,到这里,是不是理解了Spring Boot自动装配的原理了,这么核心的东西,是不是觉得也不难~~


创建spring boot starter

接下来手把手带领大家一起创建一个spring boot starter项目。

应用场景

为什么要创建一个spring boot starter项目呢,目的是为了使用方便和代码复用。举个例子,加入你有一个监控系统demo-monitor-center,是基于Socket IO开发的,很多系统都需要调用这个监控系统获取监控数据。这个监控系统使用OAuth 2.0进行授权的,在使用监控系统前,需要先使用账户密码登录安全中心,获取token,然后使用token访问监控系统。如果token过期,又需要访问安全中心,刷新token。之后是访问监控系统获取监控数据。

上边细节还挺繁琐的,如果每个监控应用都实现一遍,工作任务就太大了,于是我们考虑做一个demo-monitor-boot-starter,包装所有细节。然后提供一个MonitorService给客户使用。

创建项目

下面开始创建项目:

第一步,创建一个Spring boot工程,取名demo-monitor-boot-starter,引入如下依赖:

代码语言:javascript
复制

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.zengbiaobiao.demo</groupId>
    <artifactId>demo-monitor-boot-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo-monitor-boot-starter</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

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

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.springframework.boot</groupId>
                            <artifactId>spring-boot-configuration-processor</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

这里需要注意两个细节:

  • 命名规范,Spring官方的starter,都叫spring-boot-starter-<模块名称>,我们自己定义的start,都命名为 <模块名称>-boot-starter。
  • 添加了另一个依赖spring-boot-configuration-processor,并且在plugin的configuration中排除它,这在后边会讲到。

第二部,创建一个config包,添加以下三个类:

代码语言:javascript
复制

package com.zengbiaobiao.demo.monitor.autoconfig;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author zengsam
 */
@Configuration
@ConditionalOnClass(MonitorService.class)
@EnableConfigurationProperties(MonitorProperties.class)
public class MonitorAutoConfiguration {
    @Autowired
    private MonitorProperties properties;

    @Bean
    @ConditionalOnMissingBean
    public MonitorService monitorService() {
        return new MonitorService(properties);
    }
}

package com.zengbiaobiao.demo.monitor.autoconfig;

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

/**
 * @author zengsam
 */
@ConfigurationProperties(prefix = "demo.monitor")
public class MonitorProperties {
    private String loginUrl;
    private String username;
    private String password;
    private String serverUrl;

    public String getLoginUrl() {
        return loginUrl;
    }

    public void setLoginUrl(String loginUrl) {
        this.loginUrl = loginUrl;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getServerUrl() {
        return serverUrl;
    }

    public void setServerUrl(String serverUrl) {
        this.serverUrl = serverUrl;
    }
}
代码语言:javascript
复制

package com.zengbiaobiao.demo.monitor.autoconfig;

import java.util.Date;
import java.util.UUID;
import java.util.function.Consumer;

/**
 * @author zengsam
 */
public class MonitorService {
    private MonitorProperties properties;

    public MonitorService(MonitorProperties properties) {
        this.properties = properties;
    }

    public void subscribe(String url, Consumer<String> callback) {
        System.out.println("login to security center:");
        System.out.println("loginUrl=" + properties.getLoginUrl());
        System.out.println("username=" + properties.getUsername());
        System.out.println("password=" + properties.getPassword());
        System.out.println("connect to monitor:");
        System.out.println("serverUrl=" + properties.getServerUrl());
        System.out.println("receive monitor data");
        callback.accept("current time:" + new Date().toString());
        callback.accept("current time:" + new Date().toString());
        callback.accept("current time:" + new Date().toString());
    }

    public void unsubscribe(String url) {
        System.out.println("unsubscribe:" + url);
    }
}

每个类的功能和作用,都做了注释。

第三步,在resources目录下,创建META-INF文件夹,然后创建文件spring.factories,添加以下内容。

代码语言:javascript
复制

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.zengbiaobiao.demo.monitor.autoconfig.MonitorAutoConfiguration

Spring boot starter启动的时候,这个文件的的内容会被合并到spring-boot-autoconfigure的spring.factories中。这就告诉Spring Boot,系统启动时,除了扫描默认的自动装配类,也扫描com.zengbiaobiao.demo.monitor.autoconfig.MonitorAutoConfiguration这个类。

好啦,这个项目已经创建结束了,现在只需要打包安装就可以使用了。执行以下命令:

代码语言:javascript
复制
mvn clean compile install

使用spring boot starter

我们来创建一个项目,使用刚才我们自己创建的demo-monitor-boot-starter。

第一步,创建Spring Boot工程,取名demo,添加以下依赖:

代码语言:javascript
复制

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	<properties>
		<java.version>1.8</java.version>
	</properties>

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

		<dependency>
			<groupId>com.zengbiaobiao.demo</groupId>
			<artifactId>demo-monitor-boot-starter</artifactId>
			<version>0.0.1-SNAPSHOT</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>

</project>

第二部,在application.yml中,添加以下内容:

代码语言:javascript
复制

demo:
  monitor:
    login-url: https://www.demo-login.com
    username: samzeng
    password: 123456
    server-url: https://www.demo-monitor.com

当你在application.yml中输入demo的时候,是不是还带自动提示功能的,惊不惊喜,意不意外^..^。

第三步,在DemoApplication.java类中,注入MonitorService并调用subscribe方法。

代码语言:javascript
复制

package com.example.demo;

import com.zengbiaobiao.demo.monitor.autoconfig.MonitorService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author zengsam
 */
@SpringBootApplication
public class DemoApplication implements CommandLineRunner {

    @Autowired
    private MonitorService monitorService;

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        monitorService.subscribe("abc", data -> System.out.println("receive data:" + data));
    }
}

运行程序,得到输入如下:

代码语言:javascript
复制

login to demo security center:

loginUrl=https://www.demo-login.com

username=samzeng

password=123456

connect to demo monitor:

serverUrl=https://www.demo-monitor.com

receive monitor data

receive data:current time:Fri Sep 11 13:53:05 CST 2020

receive data:current time:Fri Sep 11 13:53:05 CST 2020

receive data:current time:Fri Sep 11 13:53:05 CST 2020

很好,虽然功能很简单,但一切都在我的掌握之中。


Spring Boot Configuration Metadata

刚才我们在使用demo-monitor-boot-starter的时候,居然还带自动提示的,这么骚包。这是为什么呢,现在来解释一下。

在我们工程demo-monitor-boot-starter的pom.xml中,我们添加了pring-boot-configuration-processor这个依赖,这个依赖的作用是,在编译时,为所有的添加了@ConfigurationProperties的类添加元数据,这些元数据是可以被IDE读到的,我们在application.yml中输入demo的时候,就会自动提示demo.monitor.login-url。

代码语言:javascript
复制

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.zengbiaobiao.demo</groupId>
    <artifactId>demo-monitor-boot-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo-monitor-boot-starter</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

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

</project>

当然根据官方的要求,需要在spring-boot-maven-plugin中排除掉spring-boot-configuration-processor,估计是为了避免版本冲突。具体请参见Configuring the Annotation Processor

Configuration Metadata还可以做的更加智能,不但给出智能提示,还把可选项都可以列出来。大家知道有这个功能就行了,用的时候再去查。这其实是另一个知识点,具体参考Configuration Metadata


总结

好吧,讲完了,总结一下,如果要创建一个spring boot starter,可以参考以下步骤:

1. 首先需要引入spring-boot-autoconfigure和spring-boot-configuration-processor依赖,后边这个依赖是用来产生配置文件元数据的。

2. 创建AutoConguration类,这里需要理解两个注解,@ConditionalOnClass(MonitorService.class)和@ConditionalOnMissingBean,第一个注解是放在自动装配类上的,表示只有当MonitorService类存在的时候才执行自动装配。第二个类是放在创建Bean的方法上,表示只有当前类实例不存在的时候才创建,这给用户很大的灵活性。可以自己创建,也可以使用系统默认的Bean。

3. 创建配置文件类,从application.yml中读取配置文件信息。

4. 在resources目录下,创建META-INF文件加,并在该文件夹下创建spring.properties,添加需要自动装配的类。Spring Boot会在启动时,合并spring.properties中的内容。

  1. 最后就是打包安装,然后使用啦。

实例代码在github上,希望可以帮到你。


所有文章在我的博客上同步。

  • 发表于:
  • 本文为 InfoQ 中文站特供稿件
  • 首发地址https://www.infoq.cn/article/ad4e275263e1729e986c51ca1
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券