什么是Stater

Spring Boot Starter是在SpringBoot组件中被提出来的一种概念,stackoverflow上面已经有人概括了这个starter是什么东西,想看完整的回答戳

Starter POMs are a set of convenient dependency descriptors that you can include in your application. You get a one-stop-shop for all the Spring and related technology that you need, without having to hunt through sample code and copy paste loads of dependency descriptors. For example, if you want to get started using Spring and JPA for database access, just include the spring-boot-starter-data-jpa dependency in your project, and you are good to go.

大概意思就是说starter是一种对依赖的synthesize(合成),这是什么意思呢?我可以举个例子来说明。

传统的做法

在没有starter之前,假如我想要在Spring中使用jpa,那我可能需要做以下操作:

  1. 在Maven中引入使用的数据库的依赖(即JDBC的jar)
  2. 引入jpa的依赖
  3. 在xxx.xml中配置一些属性信息
  4. 反复的调试直到可以正常运行

    需要注意的是,这里操作在我们每次新建一个需要用到jpa的项目的时候都需要重复的做一次。也许你在第一次自己建立项目的时候是在Google上自己搜索了一番,花了半天时间解决掉了各种奇怪的问题之后,jpa终于能正常运行了。有些有经验的人会在OneNote上面把这次建立项目的过程给记录下来,包括操作的步骤以及需要用到的配置文件的内容,在下一次再创建jpa项目的时候,就不需要再次去Google了,只需要照着笔记来,之后再把所有的配置文件copy&paste就可以了。

像上面这样的操作也不算不行,事实上我们在没有starter之前都是这么干的,但是这样做有几个问题:

  1. 如果过程比较繁琐,这样一步步操作会增加出错的可能性
  2. 不停地copy&paste不符合Don’t repeat yourself精神
  3. 在第一次配置的时候(尤其如果开发者比较小白),需要花费掉大量的时间

使用Starter提升效率

starter的主要目的就是为了解决上面的这些问题。

Starter的理念

starter会把所有用到的依赖都给包含进来,避免了开发者自己去引入依赖所带来的麻烦。需要注意的是不同的starter是为了解决不同的依赖,所以它们内部的实现可能会有很大的差异,例如jpa的starter和Redis的starter可能实现就不一样,这是因为starter的本质在于synthesize,这是一层在逻辑层面的抽象,也许这种理念有点类似于Docker,因为它们都是在做一个“包装”的操作,如果你知道Docker是为了解决什么问题的,也许你可以用Docker和starter做一个类比。

Starter的实现

虽然不同的starter实现起来各有差异,但是他们基本上都会使用到两个相同的内容:ConfigurationProperties和AutoConfiguration。因为Spring Boot坚信“约定大于配置”这一理念,所以我们使用ConfigurationProperties来保存我们的配置,并且这些配置都可以有一个默认值,即在我们没有主动覆写原始配置的情况下,默认值就会生效,这在很多情况下是非常有用的。除此之外,starter的ConfigurationProperties还使得所有的配置属性被聚集到一个文件中(一般在resources目录下的application.properties),这样我们就告别了Spring项目中XML地狱。

Starter的整体逻辑

img

上面的starter依赖的jar和我们自己手动配置的时候依赖的jar并没有什么不同,所以我们可以认为starter其实是把这一些繁琐的配置操作交给了自己,而把简单交给了用户。除了帮助用户去除了繁琐的构建操作,在“约定大于配置”的理念下,ConfigurationProperties还帮助用户减少了无谓的配置操作。并且因为 application.properties 文件的存在,即使需要自定义配置,所有的配置也只需要在一个文件中进行,使用起来非常方便。

了解了starter其实就是帮助用户简化了配置的操作之后,要理解starter和被配置了starter的组件之间并不是竞争关系,而是辅助关系,即我们可以给一个组件创建一个starter来让最终用户在使用这个组件的时候更加的简单方便。基于这种理念,我们可以给任意一个现有的组件创建一个starter来让别人在使用这个组件的时候更加的简单方便,事实上Spring Boot团队已经帮助现有大部分的流行的组件创建好了它们的starter,你可以在这里查看这些starter的列表。

SpringBoot常见的Stater

springboot项目会有很多spring-boot-starter打头的引用,这些引用是干什么用的,都有哪些,带着这些问题去官网看了一下官方文档,现将结果整理一下。

引用 说明
spring-boot-starter 核心启动器,包括自动配置支持,日志记录和YAML
spring-boot-starter-activemq 使用Apache ActiveMQ的消息队列
spring-boot-starter-amqp 使用Spring AMQP和Rabbit MQ消息队列
spring-boot-starter-aop 使用SpringAOP和AspectJ进行切面编程包
spring-boot-starter-artemis 使用Apache Artemis的消息队列
spring-boot-starter-batch 使用轻量级的大数据量的并行处理(批处理)框架SpringBatch
spring-boot-starter-cache Spring缓存框架
spring-boot-starter-cloud-connectors Spring Cloud Connectors,云连接器,便于PaaS应用在各种平台上连接到后端像数据库和消息经纪服务
spring-boot-starter-data-cassandra NoSQL数据库cassandra
spring-boot-starter-data-cassandra-reactive NoSQL数据库cassandra响应式编程
spring-boot-starter-data-couchbase NoSQL数据库Couchbase
spring-boot-starter-data-couchbase-reactive NoSQL数据库Couchbase响应式编程
spring-boot-starter-data-elasticsearch 使用Elasticsearch搜索和分析引擎
spring-boot-starter-data-jdbc 使用jdbc连接数据库进行数据支持久化
spring-boot-starter-data-jpa 使用jpa连接数据库进行数据支持久化
spring-boot-starter-data-ldap 使用ldap目录服务
spring-boot-starter-data-mongodb 使用NoSQL数据库mongodb
spring-boot-starter-data-mongodb-reactive 使用NoSQL数据库mongodb响应式编程
spring-boot-starter-data-neo4j 使用NoSQL数据库neo4j
spring-boot-starter-data-redis 使用NoSQL数据库redis
spring-boot-starter-data-redis-reactive 使用NoSQL数据库redis响应式编程
spring-boot-starter-data-rest rest风格使用springData
spring-boot-starter-data-solr 使用solr搜索和分析引擎
spring-boot-starter-freemarker 使用FreeMarker视图构建MVC Web应用程序
spring-boot-starter-groovy-templates 使用Groovy模板视图构建MVC Web应用程序
spring-boot-starter-hateoas 使用SpringMVC和SpringHATEOAS构建基于超媒体的RESTful Web应用程序
spring-boot-starter-integration 使用SpringIntegration,类似消息队列,用于企业集成的框架
spring-boot-starter-jdbc 使用jdbc连接池
spring-boot-starter-jersey 使用jersey构建基于超媒体的RESTful Web应用程序
spring-boot-starter-jooq 使用jooq数据库ORM框架
spring-boot-starter-json 支持json读写
spring-boot-starter-jta-atomikos 使用atomikos解决分布式事务
spring-boot-starter-jta-bitronix 使用bitronix解决分布式事务
spring-boot-starter-mail 使用Java Mail发送邮件
spring-boot-starter-mustache 使用mustache轻量级模板语言视图构建MVC Web应用程序
spring-boot-starter-oauth2-client 使用oauth2授权框架客户端
spring-boot-starter-oauth2-resource-server 使用oauth2授权框架服务端
spring-boot-starter-quartz 使用quartz任务调度框架
spring-boot-starter-security 使用security安全框架
spring-boot-starter-test 使用单元测试包含JUnit、 Hamcrest和 Mockito
spring-boot-starter-thymeleaf 使用thymeleaf模板语言视图构建MVC Web应用程序
spring-boot-starter-validation 使用基于Hibernate Validator的类数据校验
spring-boot-starter-web 构建网站开发,包括RESTful,Spring MVC,使用Tomcat作为默认嵌入容器
spring-boot-starter-web-services 使用WebServices
spring-boot-starter-webflux 使用WebFlux响应式编程,与springMVC功能类似
spring-boot-starter-websocket 使用websocket通讯服务
spring-boot-starter-actuator 使用actuator监控应用健康
spring-boot-starter-jetty 使用jetty作为嵌入式容器
spring-boot-starter-log4j2 使用log4j2记录日志
spring-boot-starter-logging 使用logging记录日志 默认
spring-boot-starter-reactor-netty 使用netty作为嵌入式容器
spring-boot-starter-tomcat 使用tomcat作为嵌入式容器 默认
spring-boot-starter-undertow 使用undertow作为嵌入式容器

如何编写自动配置

规则

@Configuration //指定这个类是一个配置类
@ConditionalOnXXX //在指定条件成立的情况下自动配置类生效
@AutoConfigureAfter //指定自动配置类的顺序
@Bean //给容器中添加组件
@ConfigurationPropertie//结合相关xxxProperties类来绑定相关的配置

自动配置类要能加载
将需要启动就加载的自动配置类,配置在META‐INF/spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\

模式

启动器只用来做依赖导入,专门来写一个自动配置模块,启动器依赖自动配置;别人只需要引入启动器(starter),mybatis-spring-boot-starter;自定义启动器名-spring-boot-starter

启动器(starter)

启动器模块是一个空JAR 文件,仅提供辅助性依赖管理,这些依赖可能用于自动装配或者其他类库

Starter命名规约

推荐使用以下命名规约

官方命名空间
  • 前缀:“spring-boot-starter-”
  • 模式:spring-boot-starter-模块名
  • 举例:spring-boot-starter-web、spring-boot-starter-actuator、spring-boot-starter-jdbc
自定义命名空间
  • 后缀:“-spring-boot-starter”
  • 模式:模块-spring-boot-starter
  • 举例:mybatis-spring-boot-starter
图解

img

自定义Starter

如果你想要自己创建一个starter,那么基本上包含以下几步

  1. 创建一个starter项目,关于项目的命名你可以参考这里
  2. 创建一个ConfigurationProperties用于保存你的配置信息(如果你的项目不使用配置信息则可以跳过这一步,不过这种情况非常少见)
  3. 创建一个AutoConfiguration,引用定义好的配置信息;在AutoConfiguration中实现所有starter应该完成的操作,并且把这个类加入spring.factories配置文件中进行声明
  4. 打包项目,之后在一个SpringBoot项目中引入该项目依赖,然后就可以使用该starter了

创建autoconfiguration项目

项目名称是 httpclient-spring-boot-autoconfiguration

对HttpClient封装一个Starter

导入POM

首先新建一个Maven项目,设置 pom.xml 文件如下:

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">  
	<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>httpclient-spring-boot-autoconfiguration</artifactId>


    <dependencies>
        <!-- 自定义starter依赖此jar包 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpcore</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.11</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.4</version>
        </dependency>

    </dependencies>

    <build>

        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>META-INF/*</include>
                </includes>
            </resource>
        </resources>
    </build>
</project>
创建相关类
创建proterties类

创建proterties类来保存配置信息

//自动获取配置文件中前缀为httprequest的属性,把值传入对象参数
@ConfigurationProperties(prefix = "httprequest")
public class HttpRequestProperties {
	//是否启动
    private boolean enabled;

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }
}
创建工具类
/**
 * Http工具类
 */
public class HttpRequestClient {
    /**
     * 发送POST请求
     *
     * @param url  请求url
     * @param data 请求数据
     * @return 结果
     */
    @SuppressWarnings("deprecation")
    public static String doPost(String url, String data) {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpPost httpPost = new HttpPost(url);
        RequestConfig requestConfig = RequestConfig.custom()
                .setSocketTimeout(10000).setConnectTimeout(20000)
                .setConnectionRequestTimeout(10000).build();
        httpPost.setConfig(requestConfig);
        String context = StringUtils.EMPTY;
        if (!StringUtils.isEmpty(data)) {
            StringEntity body = new StringEntity(data, "utf-8");
            httpPost.setEntity(body);
        }
        // 设置回调接口接收的消息头
        httpPost.addHeader("Content-Type", "application/json");
        CloseableHttpResponse response = null;
        try {
            response = httpClient.execute(httpPost);
            HttpEntity entity = response.getEntity();
            context = EntityUtils.toString(entity, HTTP.UTF_8);
        } catch (Exception e) {
            e.getStackTrace();
        } finally {
            try {
                response.close();
                httpPost.abort();
                httpClient.close();
            } catch (Exception e) {
                e.getStackTrace();
            }
        }
        return context;
    }

    /**
     * 解析出url参数中的键值对
     *
     * @param url url参数
     * @return 键值对
     */
    public static Map<String, String> getRequestParam(String url) {

        Map<String, String> map = new HashMap<String, String>();
        String[] arrSplit = null;

        // 每个键值为一组
        arrSplit = url.split("[&]");
        for (String strSplit : arrSplit) {
            String[] arrSplitEqual = null;
            arrSplitEqual = strSplit.split("[=]");

            // 解析出键值
            if (arrSplitEqual.length > 1) {
                // 正确解析
                map.put(arrSplitEqual[0], arrSplitEqual[1]);
            } else {
                if (arrSplitEqual[0] != "") {
                    map.put(arrSplitEqual[0], "");
                }
            }
        }
        return map;
    }
}

这个工具类很简单就是一个HttpClient的操作工具类

创建自动配置类
@Configuration
@EnableConfigurationProperties(HttpRequestProperties.class)
public class HttpAutoConfiguration {
    
    // 在Spring上下文中创建一个对象
    @Bean
    //只有当httprequest.enabled=true时才创建对象
    @ConditionalOnProperty(prefix = "httprequest", name = "enabled", havingValue = "true")
    @ConditionalOnMissingBean(HttpRequestClient.class)
    public HttpRequestClient getHttpRequestClient() {
        return new HttpRequestClient();
    }
}

在上面的AutoConfiguration中我们实现了自己要求:在Spring的上下文中创建了一个HttpClient类的bean,并且我们把properties中的一个参数赋给了该bean。

最后,我们在 resources 文件夹下新建目录 META-INF,在目录中新建 spring.factories 文件,并且在 spring.factories 中配置AutoConfiguration:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.autoconfiguration.HttpAutoConfiguration

img

到此,我们的starter已经创建完毕了,使用Maven打包该项目。之后创建一个SpringBoot项目,在项目中添加我们之前打包的starter作为依赖,然后使用SringBoot来运行我们的starter,代码如下:

创建Starter项目

导入POM

这个项目中只引入autoconfiguration项目,其他什么也不做

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>httpclient-springboot-starter</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>httpclient-spring-boot-autoconfiguration</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>

测试Starter

创建项目

导入POM

导入Starter的坐标

<?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 http://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.2.2.RELEASE</version>
    </parent>

    <groupId>org.example</groupId>
    <artifactId>HttpAutoConfigTest</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>httpclient-springboot-starter</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>

</project>
创建启动类
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }
}
创建配置文件
httprequest.enabled=true
创建测试类
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = {Application.class})
public class ApplicationTest {

    @Resource
    private HttpRequestClient httpRequestClient;
    @Test
    public void test() {
        String str = httpRequestClient.doPost("http://www.baidu.com", null);
        System.out.println(str);
        Assert.assertNotNull(str);
    }
}

运行测试

运行的时候我们发现直接就能够注入使用HttpRequestClient,不需要我们在每一个项目中进行配置httpclient了,很方便,如果不需要注入可以将httprequest.enabled设置为false或者去掉