背景

在做SpringBoot开发时,各种starter (场景启动器) 必不可少,它们就像可插拔式的插件,只要在pom文件中引用 springboot 提供的场景启动器, 再进行少量的配置就可以使用相应的功能,但SpringBoot并不能囊括我们的所有使用场景,这时候就需要我们自定义starter来实现定制化功能。

Spring Boot Starter工作原理

  • 1.SpringBoot在启动的时候会扫描项目所依赖的JAR包,寻找包含spring.factories文件的JAR包
  • 2.读取spring.factories文件获取配置的自动配置类AutoConfiguration
  • 3.将自动配置类下满足条件(@ConditionalOnXxx)的@Bean放入到Spring容器中(Spring Context)

自定义starter

1.创建一个maven工程

pom文件

<?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.6.RELEASE</version>        <relativePath/> <!-- lookup parent from repository -->    </parent>    <groupId>com.matins.starter</groupId>    <artifactId>hello-spring-boot-starter</artifactId>    <version>1.0-SNAPSHOT</version>    <packaging>jar</packaging>    <name>hello-spring-boot-starter</name>    <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>            <optional>true</optional>        </dependency>    </dependencies></project>

2.Proprerty类

创建类PersonProperties

@ConfigurationProperties注解的作用是告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定,prefix 指定配置文件里的前缀,配置了这个注解后会根据配置文件的所有属性进行一一映射

package com.matins.starter.property;import org.springframework.boot.context.properties.ConfigurationProperties;/** * @Description: * @author: wei.wang * @since: 2019/12/18 23:46 * @history: 1.2019/12/18 created by wei.wang */@ConfigurationProperties(prefix = "spring.person")public class PersonProperties {    // 姓名    private String name;    // 年龄    private int age;    // 性别    private String sex = "M";    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }    public String getSex() {        return sex;    }    public void setSex(String sex) {        this.sex = sex;    }    @Override    public String toString() {        return "PersonProperties{" +                "name='" + name + '\'' +                ", age=" + age +                ", sex='" + sex + '\'' +                '}';    }}

3.Service类

接口PersonService

package com.matins.starter.service;/** * @Description: * @author: wei.wang * @since: 2020/6/24 16:37 * @history: 1.2020/6/24 created by wei.wang */public interface PersonService {    void hello();}

类PersonServiceImpl

package com.matins.starter.service.impl;import com.matins.starter.service.PersonService;/** * @Description: * @author: wei.wang * @since: 2020/6/24 16:38 * @history: 1.2020/6/24 created by wei.wang */public class PersonServiceImpl implements PersonService {    // 姓名    private String name;    // 年龄    private int age;    // 性别    private String sex = "M";    public PersonServiceImpl(String name, int age, String sex) {        this.name = name;        this.age = age;        this.sex = sex;    }    @Override    public void hello() {        System.out.println(String.format("Hello from the default starter , name %s, age %s, sec %s", name, age, sex));    }}

4.自动配置类

@Configuration //指定这个类是一个配置类
@ConditionalOnXXX //指定条件成立的情况下自动配置类生效
@AutoConfigureOrder //指定自动配置类的顺序
@Bean //向容器中添加组件
@ConfigurationProperties //结合相关xxxProperties来绑定相关的配置
@EnableConfigurationProperties //让xxxProperties生效加入到容器中
@ConditionalOnClass:当类路径classpath下有指定的类的情况下进行自动配置
@ConditionalOnMissingBean:当容器(Spring Context)中没有指定Bean的情况下进行自动配置
@ConditionalOnProperty(prefix = “example.service”,name = "auth", value = “enabled”, matchIfMissing = true),当配置文件中example.service.auth.enabled=true时进行自动配置,如果没有设置此值就默认使用matchIfMissing对应的值
@ConditionalOnMissingBean,当Spring Context中不存在该Bean时。
@ConditionalOnBean:当容器(Spring Context)中有指定的Bean的条件下
@ConditionalOnMissingClass:当类路径下没有指定的类的条件下
@ConditionalOnExpression:基于SpEL表达式作为判断条件
@ConditionalOnJava:基于JVM版本作为判断条件
@ConditionalOnJndi:在JNDI存在的条件下查找指定的位置
@ConditionalOnNotWebApplication:当前项目不是Web项目的条件下
@ConditionalOnWebApplication:当前项目是Web项目的条件下
@ConditionalOnResource:类路径下是否有指定的资源
@ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者在有多个Bean的情况下,用来指定首选的Bean

自动配置类PersonServiceAutoConfiguration

package com.matins.starter.configuration;import com.matins.starter.property.PersonProperties;import com.matins.starter.service.PersonService;import com.matins.starter.service.impl.PersonServiceImpl;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;import org.springframework.boot.context.properties.EnableConfigurationProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;/** * @Description: * @author: wei.wang * @since: 2020/6/24 16:38 * @history: 1.2020/6/24 created by wei.wang */@Configuration@EnableConfigurationProperties(PersonProperties.class)@ConditionalOnClass(PersonService.class)public class PersonServiceAutoConfiguration {    @Autowired    private PersonProperties properties;    @Bean(name = "person")    public PersonService helloService() {        return new PersonServiceImpl(properties.getName(), properties.getAge(), properties.getSex());    }}

5.配置文件

在resources/META-INF/下创建文件spring.factories,SpringBoot在启动的时候会扫描项目所依赖的JAR包,寻找包含spring.factories文件的JAR包,读取spring.factories文件获取配置的自动配置类AutoConfiguration,然后将自动配置类下满足条件(@ConditionalOnXxx)的@Bean放入到Spring容器中(Spring Context),这样使用者就可以直接用来注入,因为该类已经在容器中了

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.matins.starter.configuration.PersonServiceAutoConfiguration

6.测试

新建一个SpringBoot项目,将Starter工程打包并引入

添加本地jar包到Maven

以管理员身份运行CMD

mvn install:install-file -Dfile="F:\GitCode\zero\hello-spring-boot-starter\target\hello-spring-boot-starter-1.0-SNAPSHOT.jar" -DgroupId=com.matins.starter -DartifactId=hello-spring-boot-starter -Dversion=0.0.1-SNAPSHOT -Dpackaging=jar

然后在Maven中引用

        <dependency>            <groupId>com.matins.starter</groupId>            <artifactId>hello-spring-boot-starter</artifactId>            <version>0.0.1-SNAPSHOT</version>        </dependency>

配置文件

在application.properties中添加,PersonProperties中使用了 @ConfigurationProperties(prefix = "spring.person"),prefix 指定配置文件里的前缀,会根据配置的信息将属性一一映射

spring.person.age=23spring.person.name=ssspring.person.sex=F

测试方法

可以直接@Autowired自动注入,或者使用getBean的方法获取bean

package com.zero.auth.controller;import com.matins.starter.property.PersonProperties;import com.matins.starter.service.PersonService;import com.zero.auth.config.SpringUtil;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;/** * @Description: * @author: wei.wang * @since: 2019/12/19 0:34 * @history: 1.2019/12/19 created by wei.wang * <p> * Spring Boot在启动时扫描项目所依赖的JAR包,寻找包含spring.factories文件的JAR包, * 然后读取spring.factories文件获取配置的自动配置类AutoConfiguration, * 然后将自动配置类下满足条件(@ConditionalOnXxx)的@Bean放入到Spring容器中(Spring Context) * 这样使用者就可以直接用来注入,因为该类已经在容器中了 */@RunWith(SpringRunner.class)@SpringBootTestpublic class TestControllerTest {    @Autowired    PersonService personService1;    @Autowired    private PersonProperties properties;    @Test    public void auth() {        PersonService personService = (PersonService) SpringUtil.getBean("person");        personService.hello();        personService1.hello();    }}

SpringUtil

package com.zero.auth.config;import org.springframework.beans.BeansException;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.stereotype.Component;/** * @Description: * @author: wei.wang * @since: 2020/6/25 16:56 * @history: 1.2020/6/25 created by wei.wang */@Componentpublic class SpringUtil implements ApplicationContextAware {    private static ApplicationContext applicationContext;    @Override    public void setApplicationContext(ApplicationContext applicationContextParam) throws BeansException {        applicationContext = applicationContextParam;    }    public static Object getObject(String id) {        Object object = null;        object = applicationContext.getBean(id);        return object;    }    public static <T> T getObject(Class<T> tClass) {        return applicationContext.getBean(tClass);    }    public static Object getBean(String tClass) {        return applicationContext.getBean(tClass);    }    public <T> T getBean(Class<T> tClass) {        return applicationContext.getBean(tClass);    }}

测试结果

Hello from the default starter , name ss, age 23, sec FHello from the default starter , name ss, age 23, sec F