深刻理解 Spring Boot Starters 原理

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

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.git

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

传统的作法

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

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

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

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

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

使用Spring Boot Starter提高效率

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

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

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

starter的总体逻辑:
ui

上面的starter依赖的jar和咱们本身手动配置的时候依赖的jar并无什么不一样,因此咱们能够认为starter实际上是把这一些繁琐的配置操做交给了本身,而把简单交给了用户。除了帮助用户去除了繁琐的构建操做,在“约定大于配置”的理念下,ConfigurationProperties还帮助用户减小了无谓的配置操做。而且由于 application.properties 文件的存在,即便须要自定义配置,全部的配置也只须要在一个文件中进行,使用起来很是方便。

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

建立本身的Spring Boot Starter

若是你想要本身建立一个starter,那么基本上包含如下几步

  1. 建立一个starter项目,关于项目的命名你能够参考这里
  2. 建立一个ConfigurationProperties用于保存你的配置信息(若是你的项目不使用配置信息则能够跳过这一步,不过这种状况很是少见)
  3. 建立一个AutoConfiguration,引用定义好的配置信息;在AutoConfiguration中实现全部starter应该完成的操做,而且把这个类加入spring.factories配置文件中进行声明
  4. 打包项目,以后在一个SpringBoot项目中引入该项目依赖,而后就可使用该starter了

咱们来看一个例子(例子的完整代码位于https://github.com/RitterHou/learn-spring-boot-starter

首先新建一个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">
    <modelVersion>4.0.0</modelVersion>
    <artifactId>http-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <!-- 自定义starter都应该继承自该依赖 -->
    <!-- 若是自定义starter自己须要继承其它的依赖,能够参考 https://stackoverflow.com/a/21318359 解决 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starters</artifactId>
        <version>1.5.2.RELEASE</version>
    </parent>

    <dependencies>
        <!-- 自定义starter依赖此jar包 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!-- lombok用于自动生成get、set方法 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.10</version>
        </dependency>
    </dependencies>

</project>

建立proterties类来保存配置信息:

@ConfigurationProperties(prefix = "http") // 自动获取配置文件中前缀为http的属性,把值传入对象参数
@Setter
@Getter
public class HttpProperties {

    // 若是配置文件中配置了http.url属性,则该默认属性会被覆盖
    private String url = "http://www.baidu.com/";

}

上面这个类就是定义了一个属性,其默认值是 http://www.baidu.com/,咱们能够经过在 application.properties 中添加配置 http.url=https://www.zhihu.com 来覆盖参数的值。

建立业务类:

@Setter
@Getter
public class HttpClient {

    private String url;

    // 根据url获取网页数据
    public String getHtml() {
        try {
            URL url = new URL(this.url);
            URLConnection urlConnection = url.openConnection();
            BufferedReader br = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), "utf-8"));
            String line = null;
            StringBuilder sb = new StringBuilder();
            while ((line = br.readLine()) != null) {
                sb.append(line).append("\n");
            }
            return sb.toString();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "error";
    }

}

这个业务类的操做很是简单,只包含了一个 url 属性和一个 getHtml 方法,用于获取一个网页的HTML数据,读者看看就懂了。

建立AutoConfiguration

@Configuration
@EnableConfigurationProperties(HttpProperties.class)
public class HttpAutoConfiguration {

    @Resource
    private HttpProperties properties; // 使用配置

    // 在Spring上下文中建立一个对象
    @Bean
    @ConditionalOnMissingBean
    public HttpClient init() {
        HttpClient client = new HttpClient();

        String url = properties.getUrl();
        client.setUrl(url);
        return client;
    }

}

在上面的AutoConfiguration中咱们实现了本身要求:在Spring的上下文中建立了一个HttpClient类的bean,而且咱们把properties中的一个参数赋给了该bean。
关于@ConditionalOnMissingBean 这个注解,它的意思是在该bean不存在的状况下此方法才会执行,这个至关于开关的角色,更多关于开关系列的注解能够参考这里

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

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.nosuchfield.httpstarter.HttpAutoConfiguration

到此,咱们的starter已经建立完毕了,使用Maven打包该项目。以后建立一个SpringBoot项目,在项目中添加咱们以前打包的starter做为依赖,而后使用SringBoot来运行咱们的starter,代码以下:

@Component
public class RunIt {

    @Resource
    private HttpClient httpClient;

    public void hello() {
        System.out.println(httpClient.getHtml());
    }

}

正常状况下此方法的执行会打印出url http://www.baidu.com/ 的HTML内容,以后咱们在application.properties中加入配置:

http.url=https://www.zhihu.com/

再次运行程序,此时打印的结果应该是知乎首页的HTML了,证实properties中的数据确实被覆盖了。

相关文章
相关标签/搜索