SpringBoot自定义Spring-boot-Starter原理demo代码实现以及解决面试问题

34次阅读

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

github:https://github.com/Ccww-lx/Sp…
模块:spring-boot-starter-base-service

   SpringBoot 的方便快捷主要体现之一 starter pomSpring Boot 为我们提供了简化企业级开发绝大多数场景的
starter pom,只要使用了应用场景所需要的starter pom,只需要引入对应的starter 即可,即可以得到 Spring Boot 为我们提供的自动配置的Bean

  然而,可能在很多情况下,我们需要 自定义 stater,这样可以方便公司内部系统调用共同的配置模块的时候可以自动进行装载配置。比如,很多公司将生产数据库的密码托管在公司的另外一个专门管理生产密码的系统上,公司每个系统需要使用的时候都需要调用其方法进行使用,现在可以通过starter 自动配置的形式进行配置。

1. SpringBoot Starter 源码分析

Q:@SpringBootApplication 注解中核心注解 @EnableAutoConfiguration 注解在 starter 起什么作用呢?

@EnableAutoConfiguration源码分析:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    Class<?>[] exclude() default {};
    String[] excludeName() default {};}

  可以从源码看出关键功能是 @import 注解导入自动配置功能类 AutoConfigurationImportSelector 类,主要方法 getCandidateConfigurations() 使用了 SpringFactoriesLoader.loadFactoryNames() 方法加载 META-INF/spring.factories 的文件(spring.factories 声明具体自动配置)。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
            AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
        Assert.notEmpty(configurations,
                "No auto configuration classes found in META-INF/spring.factories. If you"
                        + "are using a custom packaging, make sure that file is correct.");
        return configurations;
}

Q:通常情况下,starter会根据条件进行操作处理,比如根据不同条件创建不同 Bean。在SpringBoot 有哪些注解可用呢?
可使用 org.springframwork.boot.autoconfigure.condition 的条件注解,具体如下所示:

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

2. 自定 starter

在此将模拟公司获取生产密码模块进行自定义starter demo

2.1 核心依赖

<dependencyManagement>
    <dependencies>
        <dependency>
            <!-- Import dependency management from Spring Boot -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.1.5.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-autoconfigure</artifactId>
    </dependency>
</dependencies>

2.2 服务类 service 以及属性配置注入

PasswordService服务类:

public class PasswordService {
    // 第三方系统获取密码所需的 key
    private String objectKey;
    @Autowired
    // 模拟的第三方系统 service
    private ThirdPartySystemService thirdPartySystemService;

    public String getSystemPassword(String objectKey,String originalPassord){if(StringUtils.isEmpty(objectKey)){return  originalPassord;}
            // 从第三方系统获取密码
            String password= thirdPartySystemService.getPassword(objectKey);
            // 返回密码
            return password!=null?password:originalPassord;

    }
}

// 模拟第三方系统 service
public class ThirdPartySystemService {public String getPassword(String objectKey){
        // 返回一个 32 位随机数
        return UUID.randomUUID().toString();
    }
}

属性配置类:

// 通过 @ConfigurationProperties 注解获取属性值
@ConfigurationProperties(prefix = "project.starter")
public class BaseServiceProperties {
    private String serviceName;
    private String serviceVersion;

    public String getServiceName() {return serviceName;}

    public void setServiceName(String serviceName) {this.serviceName = serviceName;}

    public String getServiceVersion() {return serviceVersion;}

    public void setServiceVersion(String serviceVersion) {this.serviceVersion = serviceVersion;}
}

配置属性使用类:

public class BaseStarterService {public void addServiceName(BaseServiceProperties baseServiceProperties){System.out.println("serviceName:"+baseServiceProperties.getServiceName()+"----"+"serviceVersion"+baseServiceProperties.getServiceVersion());
    }
}

其他类:

// 判断是否 windows 系统
public class WindowsCondition implements Condition {

    private final static String WINDOWS="Windows";

    /**
     * ConditionContext:判断条件能使用的上下文(环境)* AnnotatedTypeMetadata:注释信息
     */
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        // 获取当前环境变量
        Environment environment=conditionContext.getEnvironment();
        // 获取 bean 注册器
        BeanDefinitionRegistry registry = conditionContext.getRegistry();
        // 能获取到 ioc 使用的 beanfactory
        ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
        // 获取环境变量中操作系统
        String property = environment.getProperty("os.name");
        // 判断操作系统是否为 windows
        if(property.contains(WINDOWS)){
            // 判断是否存在 baseWindowsSevice 类,不存在则进行 bean 注册
            boolean isWindowsSevice = registry.containsBeanDefinition("baseStarterService");
            if(!isWindowsSevice){
                // 指定 Bean 定义信息;(Bean 的类型,Bean 的一系列信息)RootBeanDefinition beanDefinition = new RootBeanDefinition(BaseStarterService.class);
                // 注册一个 Bean,指定 bean 名
                registry.registerBeanDefinition("baseStarterService", beanDefinition);
                BaseStarterService windowsSevice = (BaseStarterService)beanFactory.getBean("baseStarterService");
            }
            return true;
        }
        return false;
    }
}

2.3 自动配置类

代码解读:

  • @EnableConfigurationProperties:读取配置文件的属性
  • @Import:导入其他配置类或者自定义类
  • @Conditional:判断当前环境是否为 windows,是则注册该类
  • @ConditionalOnProperty:判断属性 spring.project.ThirdPartySystemService.isPassword 是否等于 true,不为true 则不注册该类
  • @ConditionalOnClass:判断 IOC 容器中是否存在 ThirdPartySystemService 类,存在则创建PasswordService bean

@Configuration
// 自动加载配置文件属性值
@EnableConfigurationProperties(BaseServiceProperties.class)
@Import(BeanConfiguration.class)
// 判断当前环境是否为 windows
@Conditional(WindowsCondition.class):// 判断属性 spring.project.ThirdPartySystemService.isPassword 是否等于 true
@ConditionalOnProperty(prefix = "spring.project.ThirdPartySystemService",value = "enablePassword", havingValue = "true",matchIfMissing = true)
public class AutoConfigurationPassoword {
    @Autowired
    private BaseServiceProperties baseServiceProperties;
    @Autowired
    private BaseStarterService baseWindowsService;

    // 加载第三方系统 service
    @Bean("thirdPartySystemService")
    public ThirdPartySystemService thirdPartySystemService(){baseWindowsService.addServiceName(baseServiceProperties);
        return new ThirdPartySystemService();}
    @Bean
    // 判断 IOC 容器中是否存在 ThirdPartySystemService 类,存在则创建 PasswordService bean
    @ConditionalOnClass(ThirdPartySystemService.class)
    public PasswordService passwordService(){baseWindowsService.addServiceName(baseServiceProperties);
        return new PasswordService();}
}

2.4 注册配置

  想自动配置生效,需要注册自动配置类,即在 src/main/resources 下新建 METAINF/spring.factories。在spring.factorie 配置如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.cn.ccww.configuration.AutoConfigurationPassoword

若有多个自动配置,则用“”隔开,此处“”是为了换行后还能够读取到属性。

3. 测试自定义 starter

3.1 import 依赖

 <dependencies>
        <dependency>
            <artifactId>spring-boot-starter-base-service</artifactId>
            <groupId>com.cn.ccww</groupId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
</dependencies>

3.2 application.properties 属性

application.properties文件有对应的字段是否启动自定义 starter,还可以设置 starter 所需的属性。如下所示:

// 自定义 Starter 配置
// 当该属性的值不为 true 时,才不会启动自定义 starter
spring.project.ThirdPartySystemService.enablePassword=true
project.starter.serviceName=ccww
project.starter.serviceVersion=1.0

4. 总结

由上所述,starter的大体的工作流程:

  • SpringBoot启动时会自动搜索包含 spring.factories 文件的 JAR 包;
  • 根据 spring.factories 文件加载自动配置类AutoConfiguration
  • 通过 AutoConfiguration 类,加载满足条件 (@ConditionalOnXxx)beanSpring IOC 容器中;
  • 使用者可以直接使用自动加载到 IOCbean

正文完
 0