专栏首页Java后端技术栈cwnait【原创】Spring Boot 如何手写stater

【原创】Spring Boot 如何手写stater

在文章中经常会看到一个-starter-,比如:

spring-boot-starter-quartz
spring-boot-starter-web
spring-boot-starter-jdbc
spring-boot-starter-data-jpa
...

很多人可能会觉得这种starter方式很牛B,添加一个starter就搞定了很多事情。今天咱们也来搞一个自己的starter。

starter的原理

先来说说starter的原理,我们知道使用一个公用的starter的时候,只需要将相应的依赖添加的Maven的配置文件当中即可,免去了自己需要引用很多依赖类,并且SpringBoot会自动进行类的自动配置。那么 SpringBoot 是如何知道要实例化哪些类,并进行自动配置的呢?下面简单说一下。

第一步,SpringBoot 在启动时会去依赖的starter包中寻找

resources/META-INF/spring.factories

文件,然后根据文件中配置的Jar包去扫描项目所依赖的Jar包,这类似于 Java 的 SPI 机制(后面会专门写一篇关于java 的SPI机制)。

第二步,根据 spring.factories配置加载AutoConfigure类。

最后,根据 @Conditional注解的条件,进行自动配置并将Bean注入Spring Context 上下文当中。

我们也可以使用@ImportAutoConfiguration({MyAutoConfiguration.class}) 指定自动配置哪些类。

starter的机制

SpringBoot中的starter是一种非常重要的机制,能够抛弃以前繁杂的配置,将其统一集成进starter,应用者只需要在maven中引入starter依赖,SpringBoot就能自动扫描到要加载的信息并启动相应的默认配置。starter让我们摆脱了各种依赖库的处理,需要配置各种信息的困扰。SpringBoot会自动通过classpath路径下的类发现需要的Bean,并注册进IOC容器。SpringBoot提供了针对日常企业应用研发各种场景的spring-boot-starter依赖模块。所有这些依赖模块都遵循着约定成俗的默认配置,并允许我们调整这些配置,也就是大家所说的“约定大于配置”。

starter的好处

在我们的日常开发工作中,经常会有一些独立于业务之外的配置模块,我们经常将其放到一个特定的包下,然后如果另一个工程需要复用这块功能的时候,需要将代码硬拷贝到另一个工程,重新集成一遍,麻烦至极。如果我们将这些可独立于业务代码之外的功配置模块封装成一个个starter,复用的时候只需要将其在pom中引用依赖即可,SpringBoot为我们完成自动装配,简直不要太爽。

实战自定义starter

创建一个项目名称为demo-spring-boot-starter

分别创建好对应目录。下面来说说几个类的内容。

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>
   <parent>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-parent</artifactId>
       <version>2.1.3.RELEASE</version>
   </parent>

    <groupId>com.demo</groupId>
    <artifactId>demo-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
    </dependencies>
</project>

DemoProperties读取配置文件内容,前缀为demo

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

@ConfigurationProperties(prefix = "demo")
public class DemoProperties {
    private String sayWhat;
    private String toWho;

    public String getSayWhat() {
        return sayWhat;
    }

    public void setSayWhat(String sayWhat) {
        this.sayWhat = sayWhat;
    }

    public String getToWho() {
        return toWho;
    }

    public void setToWho(String toWho) {
        this.toWho = toWho;
    }
}

创建一个service类DemoService

public class DemoService {
    public String sayWhat;
    public String toWho;

    public DemoService(String sayWhat, String toWho) {
        this.sayWhat = sayWhat;
        this.toWho = toWho;
    }

    public String say() {
        return this.sayWhat + " " + toWho;
    }
}

接下来的这个类是最关键的类

import com.demo.properties.DemoProperties;
import com.demo.service.DemoService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;

@Configuration
@ConditionalOnClass(DemoService.class)
@EnableConfigurationProperties(DemoProperties.class)
public class DemoConfig {

    @Resource
    private DemoProperties demoProperties;

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = "demo", value = "enabled", havingValue = "true")
    public DemoService demoService() {
        return new DemoService(demoProperties.getSayWhat(), demoProperties.getToWho());
    }
}

解释一下代码中用到的几个注解:

  • @ConditionalOnClass,当classpath下发现该类的情况下进行自动配置。
  • @ConditionalOnMissingBean,当Spring Context中不存在该Bean时。
  • @ConditionalOnProperty(prefix = "example.service",value = "enabled",havingValue = "true"),当配置文件中example.service.enabled=true时。

下面列举SpringBoot中的所有@Conditional注解及作用

@ConditionalOnBean:当容器中有指定的Bean的条件下  
@ConditionalOnClass:当类路径下有指定的类的条件下  
@ConditionalOnExpression:基于SpEL表达式作为判断条件  
@ConditionalOnJava:基于JVM版本作为判断条件  
@ConditionalOnJndi:在JNDI存在的条件下查找指定的位置  
@ConditionalOnMissingBean:当容器中没有指定Bean的情况下  
@ConditionalOnMissingClass:当类路径下没有指定的类的条件下  
@ConditionalOnNotWebApplication:当前项目不是Web项目的条件下  
@ConditionalOnProperty:指定的属性是否有指定的值  
@ConditionalOnResource:类路径下是否有指定的资源  
@ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者在有多个Bean的情况下,用来指定首选的Bean @ConditionalOnWebApplication:当前项目是Web项目的条件下  

最后一步,在resources/META-INF/下创建spring.factories文件,并添加如下内容:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.demo.config.DemoConfig

至此,我们的一个Starter代码部分就是完成了,下面将项目mvn install安装到本地Maven仓库中。

测试自定义starter

在前面文章中的项目中添加咱们自定义的starter依赖

<dependency>
    <groupId>com.demo</groupId>
    <artifactId>demo-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

在application.properties中添加配置项

demo.isopen=true
demo.say-what=hello
demo.to-who=mystarter

定义一个DemoStarterController

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
public class DemoStarterController {
    @Resource
    private DemoService demoService;

    @GetMapping("/test/starter")
    public String sayWhat(){
        return demoService.say();
    }
}

启动项目。

请求:http:localhost:8080/test/starter

输出:hello mystarter

starter 的命名规范

a. spring 提供的 starter:

    spring-boot-starter-XXX-x.y.z.jar

    spring-boot-XXX-autoconfigure-x.y.z.jar

  b. 第三方提供的 jar

    XXX-spring-boot-starter-x.y.z.jar

    XXX-spring-boot-autoconfigure-x.y.z.jar

ok,自定义starter就这么轻松的搞定了。码字不易,点个 在看 +分享 再走呗。感谢!!!

本文分享自微信公众号 - Java后端技术全栈(jjs-2018),作者:田老师

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-08-28

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 如何自定义starter

    使用过Spring Boot的小伙伴都应该知道,一个Spring Boot 项目就是由一个一个 starter 组成的,一个 starter 代表该项目的 Sp...

    Java后端技术全栈
  • 再谈Java泛型---下

    表面上看起来没什么问题,这个方法声明确实没有任何问题,但问题是调用该方法传入的实际参数时可能不是我们所期望的,例如:

    Java后端技术全栈
  • 队列 | 如何使用数组和链表来实现“队列”

    与栈一样,队列(Queue)也是一种数据结构,它包含一系列元素。但是,队列访问元素的顺序不是后进先出(LIFO),而是先进先出(FIFO)。

    Java后端技术全栈
  • SpringBoot系列之自定义starter实践教程

    Springboot是有提供了很多starter的,starter翻译过来可以理解为场景启动器,所谓场景启动器配置了自动配置等等对应业务模块的一个工程,有需要时...

    SmileNicky
  • 手把手教你实现自定义Spring Boot的 Starter

    上篇文章《天天用SpringBoot,它的自动装配原理却说不出来》我们有说springBoot的自动装配怎么实现的(建议最好先看下篇文章,因为前后有关系),这篇...

    java金融
  • 保姆级教程,手把手教你实现一个SpringBoot的starter

    上篇文章《天天用SpringBoot,它的自动装配原理却说不出来》我们有说springBoot的自动装配怎么实现的(建议最好先看下篇文章,因为前后有关系),这篇...

    java金融
  • Springboot 系列(十五)如何编写自己的 Springboot starter

    Springboot 中的自动配置确实方便,减少了我们开发上的复杂性,那么自动配置原理是什么呢?之前我也写过了一篇文章进行了分析。 Springboot 系列(...

    未读代码
  • 《面向模式的软件架构分布式计算的模式语言 卷4》

    迄今为止,人们提出的软件开发模式有不少是关于分布式计算的,但人们始终无法以完整的视角了解分布式计算中各种模式是如何协同工作、取长补短的。构建复杂的分布式系统似乎...

    用户3157710
  • 常用前端资源收集

    经常看到很多好的前端框架,今天起凡是遇到就整理到这里,此文会不断更新。 1,avalon 组件--作者司徒正美,国产MVVM JS组件 https://segm...

    用户1177503
  • iOS 开发中利用socket请求百度网页

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010105969/article/details/...

    用户1451823

扫码关注云+社区

领取腾讯云代金券