Springboot starter详解

springboot starter封装

什么是springboot starter机制?

能够抛弃以前繁杂的配置,将其统一集成进starter,应用者只需要在maven中引入starter依赖,SpringBoot就能自动扫描到要加载的信息并启动相应的默认配置。

starter让我们摆脱了各种依赖库的处理,需要配置各种信息的困扰。SpringBoot会自动通过classpath路径下的类发现需要的Bean,并注册进IOC容器。SpringBoot提供了针对日常企业应用研发各种场景的spring-boot-starter依赖模块。
所有这些依赖模块都遵循着约定成俗的默认配置,并允许我们调整这些配置,即遵循“约定大于配置”的理念。

为什么需要自定义starter?

在我们的日常开发工作中,经常会有一些独立于业务之外的配置模块,我们经常将其放到一个特定的包下,然后如果另一个工程需要复用这块功能的时候,需要将代码硬拷贝到另一个工程,重新集成一遍,麻烦至极。 如果我们将这些可独立于业务代码之外的功配置模块封装成一个个starter,复用的时候只需要将其在pom中引用依赖即可,SpringBoot为我们完成自动装配,简直不要太爽。

什么时候需要自定义starter?

在我们的日常开发工作中,可能会需要开发一个通用模块,以供其它工程复用。SpringBoot就为我们提供这样的功能机制,我们可以把我们的通用模块封装成一个个starter,这样其它工程复用的时候只需要在pom中引用依赖即可,由SpringBoot为我们完成自动装配。

举一些常见场景:

通用模块-短信发送模块
基于AOP技术实现日志切面
分布式雪花ID,Long-->string,解决精度问题 jackson2/fastjson
微服务项目的数据库连接池配置
微服务项目的每个模块都要访问redis数据库,每个模块都要配置redisTemplate

自动加载核心注解说明

image-20220803154634046.png

自定义starter的开发流程

这里以封装 liteflow-spring-boot-starter 为例

(1)创建Starter项目

命名规范

SpringBoot官方命名方式
格式:spring-boot-starter-{模块名}
举例:spring-boot-starter-web

自定义命名方式
格式:{模块名}-spring-boot-starter
举例:mystarter-spring-boot-starter

引入必要的依赖

该依赖作用是在使用IDEA编写配置文件有代码提示, 作用在编译阶段




    org.springframework.boot
    spring-boot-configuration-processor
    true 
       

完整pom.xml



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.7.2
         
    
    com.unionman
    liteflow-spring-boot-starter
    0.0.1-SNAPSHOT
    liteflow-spring-boot-starter
    liteflow-spring-boot-starter
    
        1.8
        3.0.0
        2.0.8
    
    
        
            org.springframework.boot
            spring-boot-starter-web
        

        
            mysql
            mysql-connector-java
            runtime
        

        
        
            org.springframework.boot
            spring-boot-configuration-processor
            true
        
        
            org.projectlombok
            lombok
            true
        






        
            com.yomahub
            liteflow-spring-boot-starter
            2.8.3
        
        
            com.yomahub
            liteflow-script-groovy
            2.8.0
        
        
            com.baomidou
            mybatis-plus-boot-starter
            3.5.1
        







        
            io.springfox
            springfox-boot-starter
            ${swagger.fox.version}
        

        
        
        
            com.github.xiaoymin
            knife4j-spring-boot-starter
            ${knife4j.version}
        
        
        
            com.github.xiaoymin
            knife4j-micro-spring-boot-starter
            ${knife4j.version}
        
        
            jakarta.validation
            jakarta.validation-api
            2.0.2
        
        
            org.codehaus.groovy
            groovy
        
    


    
    
        
        
    


(2)编写相关属性类
package com.unionman.liteflow.config;


import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;


@ConfigurationProperties(prefix = "app.service")
@Data
public class LiteServiceConfig {

    private String serviceUid;
}

@ConfigurationProperties注解基本用法

以定义一个配置信息类,和配置文件进行映射

  • 前缀定义了哪些外部属性将绑定到类的字段上
  • 根据 Spring Boot 宽松的绑定规则,类的属性名称必须与外部属性的名称匹配
  • 我们可以简单地用一个值初始化一个字段来定义一个默认值
  • 类本身可以是包私有的
  • 类的字段必须有公共 setter 方法

注意:需要在配置类上加上 @EnableConfigurationProperties(LiteServiceConfig.class)

(3)编写Starter项目的业务功能
(4)编写自动配置类 xxxAutoConfiguration

命名规范: XXXAutoConfiguration,如 LiteFlowAutoConfiguration

package com.unionman.liteflow.config;


import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration // 表示该类是配置类
@ComponentScan("com.unionman.liteflow") // 由于这里没有启动类,注入组件不能通过@SpringBootApplication,而需通过注解扫描
//@MapperScan("com.unionman.liteflow.mapper") // 不使用@Mapper注入mapper接口,则使用包扫描
@EnableConfigurationProperties(LiteServiceConfig.class) // 激活自动配置(指定文件中的配置)
public class LiteFlowAutoConfiguration {

//
//    @Bean(initMethod = "init", destroyMethod = "beforeDestory")
//    public InitLiteFlowBean initLiteFlowBean(){
//        return new InitLiteFlowBean();
//    }


}

@Configuration: 定义一个配置类

@EnableConfigurationProperties:作用是使@ConfigurationProperties注解生效。

如 @EnableConfigurationProperties(LiteServiceConfig.class) 让上面的LiteServiceConfig中的

@ConfigurationProperties生效

(5)编写spring.factories文件加载自动配置类

为什么需要spring.factories?

Spring Boot会默认扫描跟启动类平级的包,如果我们的Starter跟启动类不在同一个主包下,需要通过配置spring.factories文件来配置生效,SpringBoot默认加载各个jar包下classpath路径的spring.factories文件,配置的key为

org.springframework.boot.autoconfigure.EnableAutoConfiguration

image-20220803120012943.png

在 resources 目录下新建 META-INF文件夹,再创建 spring.factories 文件,加入以下内容

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.example.liteflowstarter.config.LiteFlowAutoConfiguration,\

注意:其中填的是配置类的全限定名,多个之间逗号分割,还可以 \ 进行转义即相当于去掉后面换行和空格符号

(6)maven去掉打包插件

starter 我们没有main入口,需要去除pom文件中maven打包插件spring-boot-maven-plugin,starter无需打成jar包

 
        
            



            
        
    
(7)在其他项目中引用

    com.unionman
    liteflow-spring-boot-starter
    0.0.1-SNAPSHOT

配置文件application.yml 添加starter所需的配置

liteflow:
  ruleSource: liteflow/*.el.xml

app:
  service:
    # umliteflow-spring-boot-starter 识别 不同服务的唯一标识
    serviceUid: demo
(8)在starter中使用@Component等注解无法注入问题

由于这里没有启动类,注入组件不能通过@SpringBootApplication,而需通过包路径注解扫描 @ComponentScan指定starter最外层包 ,如 @ComponentScan("com.unionman.liteflow"),即扫描 该包目录下的@Component,@Repository,@Service,@Controller

@Configuration 使用详解

Full全模式和Lite轻量级模式

@Configuration参数proxyBeanMethods:

Full 全模式(默认):@Configuration(proxyBeanMethods = true)

同一配置类下,当直接调用@Bean修饰的方法注入的对象,则调用该方法会被代理,从ioc容器中取bean实列,所以实例是一样的。即单实例对象在该模式下SpringBoot每次启动都会判断检查容器中是否存在该组件

Lite 轻量级模式:@Configuration(proxyBeanMethods = false)

同一配置类下,当直接调用@Bean修饰的方法注入的对象,则调用该方法不会被代理,相当于直接调用一个普通方法,会有构造方法,但是没有bean的生命周期,返回的是不同的实例

注:proxyBeanMethods 是为了让使用@Bean注解的方法被代理。而不是@Bean的单例多例的设置参数。

测试例子这里不展示,可以下载我的代码查看

@Configuration(proxyBeanMethods = false)
public class AppConfig {
    
    //放一份myBean到ioc容器
    @Bean
    public Mybean myBean() {
        return new Mybean();
    }
 
    //放一份yourBean到ioc容器
    @Bean
    public YourBean yourBean() {
        System.out.println("==========");
        //注意:@Configuration(proxyBeanMethods = false):myBean()方法不代理,直接调用
        //注意:@Configuration(proxyBeanMethods = true):myBean()方法代理,从ioc容器拿
        return new YourBean(myBean());
    }
}
什么时候用Full全模式,什么时候用Lite轻量级模式?

当在你的同一个Configuration配置类中,注入到容器中的bean实例之间有依赖关系时,建议使用Full全模式

当在你的同一个Configuration配置类中,注入到容器中的bean实例之间没有依赖关系时,建议使用Lite轻量级模式,以提高springboot的启动速度和性能

@ComponentScan 使用详解

原文链接:https://blog.csdn.net/u012326462/article/details/82765485

作用就是根据定义的扫描路径,把符合扫描规则的类装配spring容器中。

如果@ComponentScan配置了包,则扫描@ComponentScan配置的包

如果@ComponentScan没有配置包,则扫描其注解的类的包(如 @SpringbootApplication内部就有@ComponentScan,则扫描启动类所在的包,这就是为什么只要在该包下基本都能被扫到的原因)


image-20220803144331854.png
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.core.annotation.AliasFor;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
    @AliasFor("basePackages")
    String[] value() default {};

    @AliasFor("value")
    String[] basePackages() default {};

    Class[] basePackageClasses() default {};

    Class nameGenerator() default BeanNameGenerator.class;

    Class scopeResolver() default AnnotationScopeMetadataResolver.class;

    ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;

    String resourcePattern() default "**/*.class";

    boolean useDefaultFilters() default true;

    ComponentScan.Filter[] includeFilters() default {};

    ComponentScan.Filter[] excludeFilters() default {};

    boolean lazyInit() default false;

    @Retention(RetentionPolicy.RUNTIME)
    @Target({})
    public @interface Filter {
        FilterType type() default FilterType.ANNOTATION;

        @AliasFor("classes")
        Class[] value() default {};

        @AliasFor("value")
        Class[] classes() default {};

        String[] pattern() default {};
    }
}

basePackages与value: 用于指定包的路径,进行扫描

basePackageClasses: 用于指定某个类所在的包的路径进行扫描

nameGenerator: bean的名称的生成器

useDefaultFilters: 是否开启对@Component,@Repository,@Service,@Controller的类进行检测,默认true

includeFilters: 包含的过滤条件

        FilterType.ANNOTATION:按照注解过滤

        FilterType.ASSIGNABLE_TYPE:按照给定的类型

        FilterType.ASPECTJ:使用ASPECTJ表达式

        FilterType.REGEX:正则

        FilterType.CUSTOM:自定义规则

excludeFilters: 排除的过滤条件,用法和includeFilters一样

DEMO项目目录


image-20220803142954185.png

应用默认的过滤器,扫描service包:

@Configuration
@ComponentScan(value = "com.xhx.spring.service",
        useDefaultFilters = true
)
public class MyConfig {
}

HelloController所在的包的类也被扫描了进去

@Configuration
@ComponentScan(value = "com.xhx.spring.service",
        useDefaultFilters = true,
        basePackageClasses = HelloController.class
)
public class MyConfig {
}

把默认的过滤器关掉,扫描带Controller注解的。

@Configuration
@ComponentScan(value = "com.xhx.spring",
        useDefaultFilters = false,
        includeFilters = {
            @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
        }
)
public class MyConfig {
}

按照类的类型扫描,虽然HelloController没有加注解,但是被注入到了spring容器中

@Configuration
@ComponentScan(value = "com.xhx.spring",
        useDefaultFilters = false,
        includeFilters = {
            @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes = {HelloController.class})
        }
)
public class MyConfig {
}

自定义扫描过滤器

package com.xhx.spring.componentscan.config;
 
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;
 
import java.io.IOException;
 
/**
 * xuhaixing
 * 2018/9/18 23:07
 **/
public class MyTypeFilter implements TypeFilter {
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        String className = metadataReader.getClassMetadata().getClassName();
        // 满足类名包含Controller的类 会被扫描
        if(className.contains("Controller")){
            return true;
        }
        return false;
    }
}

注意: 要设置useDefaultFilters = false(系统默认为true,需要手动设置) includeFilters包含过滤规则才会生效。

@Configuration
@ComponentScan(value = "com.xhx.spring",
        useDefaultFilters = false,
        includeFilters = {
            @ComponentScan.Filter(type = FilterType.CUSTOM,classes = {MyTypeFilter.class})
        }
)
public class MyConfig {
}

输出spring容器中的bean的测试类:只过滤输出了名字中含有hello的类。

package com.xhx.spring.componentscan;
 
import com.xhx.spring.componentscan.config.MyConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.test.context.junit4.SpringRunner;
 
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
 
@RunWith(SpringRunner.class)
@SpringBootTest
public class ComponentScanApplicationTests {
 
    @Test
    public void testLoads() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        List hello = Arrays.stream(context.getBeanDefinitionNames()).collect(Collectors.toList());
        hello.stream().filter(name->name.contains("hello")).peek(System.out::println).count();
    }
 
}

[demo]SpringBoot自定义多数据源starter组件

https://blog.csdn.net/weixin_45840947/article/details/123892165#t0

【有空看看】mybatis-spring-boot-starter

mybatis自动配置原理

https://jishuin.proginn.com/p/763bfbd5ad27

参考资料

  1. Springboot官方文档
  2. 一文搞懂SpringBoot自动配置原理
  3. SpringBoot starter的理解与使用
  4. 还在curd吗?封装属于自己的Spring-Boot-Starter
  5. SpringBoot自定义多数据源starter组件
  6. MyBaits自动配置原理

你可能感兴趣的:(Springboot starter详解)