【SpringBoot2.x】-自定义Spring boot Starter(原理、demo代码实现以及解决面试问题)

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

github:github.com/Ccww-lx/Spr…


模块: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


> 各位看官还可以吗?喜欢的话,动动手指点个💗,点个关注呗!!谢谢支持! >


目录
相关文章
|
1月前
|
JavaScript 前端开发
【Vue面试题二十五】、你了解axios的原理吗?有看过它的源码吗?
这篇文章主要讨论了axios的使用、原理以及源码分析。 文章中首先回顾了axios的基本用法,包括发送请求、请求拦截器和响应拦截器的使用,以及如何取消请求。接着,作者实现了一个简易版的axios,包括构造函数、请求方法、拦截器的实现等。最后,文章对axios的源码进行了分析,包括目录结构、核心文件axios.js的内容,以及axios实例化过程中的配置合并、拦截器的使用等。
【Vue面试题二十五】、你了解axios的原理吗?有看过它的源码吗?
|
1月前
|
安全 Java 容器
【Java集合类面试二十七】、谈谈CopyOnWriteArrayList的原理
CopyOnWriteArrayList是一种线程安全的ArrayList,通过在写操作时复制新数组来保证线程安全,适用于读多写少的场景,但可能因内存占用和无法保证实时性而有性能问题。
|
1月前
|
JavaScript 前端开发
【Vue面试题二十七】、你了解axios的原理吗?有看过它的源码吗?
文章讨论了Vue项目目录结构的设计原则和实践,强调了项目结构清晰的重要性,提出了包括语义一致性、单一入口/出口、就近原则、公共文件的绝对路径引用等原则,并展示了单页面和多页面Vue项目的目录结构示例。
|
6天前
|
缓存 前端开发 Java
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
Soring Boot的起步依赖、启动流程、自动装配、常用的注解、Spring MVC的执行流程、对MVC的理解、RestFull风格、为什么service层要写接口、MyBatis的缓存机制、$和#有什么区别、resultType和resultMap区别、cookie和session的区别是什么?session的工作原理
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
|
16天前
|
消息中间件 Java 编译器
面试官:说说Lambda表达式底层原理?
面试官:说说Lambda表达式底层原理?
19 2
面试官:说说Lambda表达式底层原理?
|
1月前
|
存储 缓存 JavaScript
【Vue面试题十八】、你知道vue中key的原理吗?说说你对它的理解
这篇文章详细介绍了Vue中的`keep-alive`组件,解释了其作用是缓存不活动的组件实例以避免重复渲染DOM,并阐述了`keep-alive`的使用场景、props属性配置、以及如何通过源码理解其缓存机制和原理。
【Vue面试题十八】、你知道vue中key的原理吗?说说你对它的理解
|
1月前
|
JavaScript 算法
【Vue面试题十七】、你知道vue中key的原理吗?说说你对它的理解
这篇文章深入探讨了Vue中`key`的原理及其作用,解释了`key`是为每个虚拟DOM节点提供的唯一标识符,作为`diff`算法的优化策略,有助于更快速准确地识别和重用DOM元素。文章通过实际代码示例展示了使用`key`与否在列表渲染时对DOM操作的影响,分析了`key`在不同场景下的性能表现,并提供了源码层面的分析,解释了Vue如何通过`key`来识别和更新虚拟DOM节点。
【Vue面试题十七】、你知道vue中key的原理吗?说说你对它的理解
|
1月前
|
Java
【Java集合类面试二十一】、请介绍TreeMap的底层原理
TreeMap基于红黑树实现,能够根据键的自然顺序或提供的Comparator排序,其基本操作的时间复杂度为O(log N)。
|
1月前
|
Java
【Java集合类面试二十】、请介绍LinkedHashMap的底层原理
LinkedHashMap的底层原理是在HashMap的基础上,通过维护一条双向链表来保持键值对的插入和遍历顺序,同时继承HashMap的多数方法并重写部分方法以维护链表。
|
1月前
|
前端开发 IDE Java
"揭秘前端转Java的秘径:SpringBoot Web极速入门,掌握分层解耦艺术,让你的后端代码飞起来,你敢来挑战吗?"
【8月更文挑战第19天】面向前端开发者介绍Spring Boot后端开发,通过简化Spring应用搭建,快速实现Web应用。本文以创建“Hello World”应用为例,展示项目基本结构与运行方式。进而深入探讨三层架构(Controller、Service、DAO)下的分层解耦概念,通过员工信息管理示例,演示各层如何协作及依赖注入的使用,以此提升代码灵活性与可维护性。
34 2