SpringBoot自定义Starter

12次阅读

共计 7991 个字符,预计需要花费 20 分钟才能阅读完成。

背景

在做 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=23
spring.person.name=ss
spring.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)
@SpringBootTest
public 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
 */
@Component
public 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 F
Hello from the default starter , name ss, age 23, sec F
正文完
 0