写点什么

【原创】Spring Boot 如何手写 starter

用户头像
田维常
关注
发布于: 2020 年 11 月 04 日

关注公众号Java 后端技术全栈”**


回复“面试”获取全套大厂面试资料


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


spring-boot-starter-quartzspring-boot-starter-webspring-boot-starter-jdbcspring-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=truedemo.say-what=hellodemo.to-who=mystarter
复制代码


定义一个 DemoStarterController


import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;@RestControllerpublic 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 就这么轻松的搞定了。码字不易,点个 在看 +分享 再走呗。感谢!!!


推荐阅读


使用 ThreadLocal 一次解决老大难问题


一个月薪 12000 的北京程序员的真实生活


一波骚操作,我把 SQL 执行效率提高了 10,000,000 倍!



发布于: 2020 年 11 月 04 日阅读数: 53
用户头像

田维常

关注

关注公众号:Java后端技术全栈,领500G资料 2020.10.24 加入

关注公众号:Java后端技术全栈,领500G资料

评论

发布
暂无评论
【原创】Spring Boot 如何手写starter