【springboot 之自定义starter】

1. starter是什么?

在Spring Boot中,Starter是一种用于简化依赖管理和配置的方式。它是一个预定义的依赖关系集合,
包含了一组常用的依赖和配置,以便于快速启动和构建特定类型的应用程序。
Starter通常包含一下组件:
1.自动配置(Auto-configuration):包含了一组自动配置类,用于根据classpath中的依赖和配置,
自动配置和初始化相关的Bean和组件。
2.条件化配置(Conditional configuration):通过条件注解(例如@ConditionalOnClass、
@ConditionalOnProperty等)来控制自动配置类的生效条件。
3.配置属性(Configuration properties):用于定义应用程序的配置属性,可以通过application.properties
或application.yml文件进行配置。
4.启动器依赖(Starter dependencies):包含了一组常用的依赖,可以通过在项目中引入Starter依赖,自动
导入所需的依赖。

使用Starter可以大大简化项目的依赖管理和配置工作,提供了一种快速启动和构建特定类型应用程序的方式。
例如,Spring Boot提供了spring-boot-starter-web用于快速构建Web应用程序,它包含了常用的Web依赖(如
Spring MVC、Tomcat等)和相关的自动配置。

开发者也可以自定义自己的Starter,将常用的依赖和配置打包为一个Starter,方便在复用和共享。自定义
Starter可以提供一组特定领域的依赖和配置,以满足开发需求。

2. 为什么使用starter

这就不用说了,引入starter,简单配置就可以使用,方便开箱即用!

3. SpringBoot自动配置原理

开发者在pom.xml文件中依赖需要的starter,springboot在启动的时候,会扫描jar包下的spring.factories文件,找到自动配置类信息,加载相应的bean信息并启动相应的默认配置。

3.1 首先看一下springboot特点

  • 提供了固定的配置简化配置,即约定大于配置
  • 尽可能自动装配库,即能自动装配
  • 内嵌容器,创建独立的spring应用
  • 内嵌Junit、Springboot Test多种测试框架,方便测试
  • 不需要xml配置

3.2 从启动类分析装配原理

@SpringBootApplication
public class SpringbootWorkApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootWorkApplication.class, args);
    }
}

@SpringBootApplication

这是一个组合注解,主要包含了
	@SpringBootConfiguration(里面就是@Configuration)
	@EnableAutoConfiguration(开启自动配置)
	@ComponentScan(包扫描)

点 @SpringBootConfiguration

里面主要包含了@Configuration注解,表明这是一个配置类,可以向容器中注入组件

点@ComponentScan进去

可以`basePackeageClasses` 或`basePackages`来定义要扫描的特定包。如果没有定义特定的包,将从声明该
注解的类的包开始扫描(包含子包)

点@EnableAutoConfiguration

@EnableAutoConfiguration说人话 就是:开启自动导入配置
这个注解是重点,下面继续讨论

3.3 @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 {};
}

3.3.1 @AutoConfigurationPackage

自动导入配置包
点进去查看
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};
}

@Import为spring的注解,导入一个配置文件,在springboot中为容器导入一个组件,而导入的组件由AutoConfigurationPackages.class的内部类Registrar.class执行逻辑来决定是如何导入的。

@Import({Registrar.class})

点Registrar.class进去查看源码如下

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    Registrar() {
    }

    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) 
    {
        //断点
        AutoConfigurationPackages.register(registry, 
        (String[])(new AutoConfigurationPackages.PackageImports(metadata))
        .getPackageNames().toArray(new String[0]));
    }

    public Set<Object> determineImports(AnnotationMetadata metadata) {
        return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
    }
}

说明:

ImportBeanDefinitionRegistrar是Spring框架提供的一个接口,用于动态注册BeanDefinition到Spring容器中
,具体来说,ImportBeanDefinitionRegistrar接口有一个registerBeanDefinitions方法,该方法会在Spring容器
启动时被调用。在该方法中,我们可以通过BeanDefinitionRegistry接口向Spring容器注册BeanDefinition,从
而实现动态注册Bean。
通常情况下,ImportBeanDefinitionRegistrar会与@Import注解一起使用。通过@Import注解引入
ImportBeanDefinitionRegistrar的实现类,从而实现BeanDefinition的动态注册。

总结:
        @AutoConfigurationPackage 就是将主配置类(@SpringBootApplication 标注的类)所在的包下面所有的组件都扫描注冊到 spring 容器中。

3.3.2 @Import({AutoConfigurationImportSelector.class})

作用: AutoConfigurationImportSelector开启自动配置类的导包的选择器,即是带入哪些类,有选择性的导入

点AutoConfigurationSelector.class进入源码
1、selectImports:选择需要导入的组件

public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    } else {
        AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
}

2、getAutoConfigurationEntry

根据导入的@Configuration类的AnnotationMetadata返回AutoConfigurationImportSelector
.AutoConfigurationEntry
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    } else {
        AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
         // 这打个断点,看看 返回的数据
        List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
        //删除重复项
        configurations = this.removeDuplicates(configurations);
        Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
        //检查
        this.checkExcludedClasses(configurations, exclusions);
        //删除需要排除的依赖
        configurations.removeAll(exclusions);
        configurations = this.getConfigurationClassFilter().filter(configurations);
        this.fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
    }
}

this.getCandidateConfigurations(annotationMetadata, attributes) 这里断点查看

在这里插入图片描述
configurations数组长度为133,并且文件后缀名都为 **AutoConfiguration

结论: 这些都是候选的配置类,经过去重,去除需要的排除的依赖,最终的组件才是这个环境需要的所有组件。有了自动配置,就不需要我们自己手写配置的值了,配置类有默##认值的。

继续看如何返回需要配置的组件的

getCandidateConfigurations(annotationMetadata, attributes)
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}

这里有句断言: 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.”);
意思是:“在 META-INF/spring.factories 中没有找到自动配置类。如果您使用自定义包装,请确保该文件是正确的。“
结论: 即是要loadFactoryNames() 方法要找到自动的配置类返回才不会报错。

getSpringFactoriesLoaderFactoryClass()

我们点进去发现:this.getSpringFactoriesLoaderFactoryClass()返回的是EnableAutoConfiguration.class这个注解。这个注解和@SpringBootApplication下标识注解是同一个注解。

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
}

结论: 获取一个能加载自动配置类的类,即SpringBoot默认自动配置类为EnableAutoConfiguration

SpringFactoriesLoader

SpringFactoriesLoader工厂加载机制是Spring内部提供的一个约定俗成的加载方式,只需要在模块的META-INF/spring.factories文件,这个Properties格式的文件中的key是接口、注解、或抽象类的全名,value是以逗号 “ , “ 分隔的实现类,使用SpringFactoriesLoader来实现相应的实现类注入Spirng容器中。

注:会加载所有jar包下的classpath路径下的META-INF/spring.factories文件,这样文件不止一个。

loadFactoryNames()
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
   ClassLoader classLoaderToUse = classLoader;
   if (classLoaderToUse == null) {
      classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
   }
   String factoryTypeName = factoryType.getName();
   return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}

断点查看factoryTypeName:
在这里插入图片描述
先将EnableAutoConfiguration.class传给了factoryType,然后String factoryTypeName = factoryType.getName();所以factoryTypeName 值为 org.springframework.boot.autoconfigure.EnableAutoConfiguration

loadSpringFactories()

接着查看loadSpringFactories方法的引用

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    //断点查看
   Map<String, List<String>> result = cache.get(classLoader);
   if (result != null) {
      return result;
   }

   result = new HashMap<>();
   try {
      //注意这里:META-INF/spring.factories
      Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
      while (urls.hasMoreElements()) {
         URL url = urls.nextElement();
         UrlResource resource = new UrlResource(url);
         Properties properties = PropertiesLoaderUtils.loadProperties(resource);
         for (Map.Entry<?, ?> entry : properties.entrySet()) {
            String factoryTypeName = ((String) entry.getKey()).trim();
            String[] factoryImplementationNames =
                  StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
            for (String factoryImplementationName : factoryImplementationNames) {
            //断点
               result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
                     .add(factoryImplementationName.trim());
            }
         }
      }

      // Replace all lists with unmodifiable lists containing unique elements
      //去重,断点查看result值
      result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
            .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
      cache.put(classLoader, result);
   }
   catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load factories from location [" +
            FACTORIES_RESOURCE_LOCATION + "]", ex);
   }
   return result;
}

这里的 FACTORIES_RESOURCE_LOCATION 在上面有定义:META-INF/spring.factories

public final class SpringFactoriesLoader {

   /**
    * The location to look for factories.
    * <p>Can be present in multiple JAR files.
    */
   public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

META-INF/spring.factories文件在哪里?? 在所有引入的java包的当前类路径下的META-INF/spring.factories文件都会被读取,如:
在这里插入图片描述
断点查看result值如下:
在这里插入图片描述
该方法作用是加载所有依赖的路径META-INF/spring.factories文件,通过map结构保存,key为文件中定义的一些标识工厂类,value就是能自动配置的一些工厂实现的类,value用list保存并去重。
在这里插入图片描述
在回看 loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
因为 loadFactoryNames 方法携带过来的第一个参数为 EnableAutoConfiguration.class,所以 factoryType 值也为 EnableAutoConfiguration.class,那么 factoryTypeName 值为 EnableAutoConfiguration。拿到的值就是META-INF/spring.factories文件下的key为
org.springframework.boot.autoconfigure.EnableAutoConfiguration的值
在这里插入图片描述
getOrDefault 当 Map 集合中有这个 key 时,就使用这个 key值,如果没有就使用默认值空数组.
结论:

  • loadSpringFactories()该方法就是从“META-INF/spring.factories”中加载给定类型的工厂实现的完全限定类名放到map中
  • loadFactoryNames()是根据SpringBoot的启动生命流程,当需要加载自动配置类时,就会传入org.springframework.boot.autoconfigure.EnableAutoConfiguration参数,从map中查找key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的值,这些值通过反射加到容器中,之后的作用就是用它们来做自动配置,这就是Springboot自动配置开始的地方
  • 只有这些自动配置类进入到容器中以后,接下来这个自动配置类才开始进行启动
  • 当需要其他的配置时如监听相关配置:listenter,就传不同的参数,获取相关的listenter配置

3.4 流程图总结:

在这里插入图片描述

3.5 常用的Condition注解

  • 在加载自动配置类的时候,并不是将spring.factories的配置全部加载进来,而是通过@Conditional等注解的判断进行动态加载
  • @Conditional其实是spring底层注解,意思就是根据不同的条件,来进行自己不同的条件判断,如果满足指定的条件,那么配置类里边的配置才会生效。
  • 常用的Conditional注解:
    • @ConditionalOnClass : classp ath中存在该类时起效
    • @ConditionalOnMissingClass : classpath中不存在该类时起效
    • @ConditionalOnBean : DI容器中存在该类型Bean时起效
    • @ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效
    • @ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或 @Primary的只有一个时起效
    • @ConditionalOnExpression : SpEL表达式结果为true时
    • @ConditionalOnProperty : 参数设置或者值一致时起效
    • @ConditionalOnResource : 指定的文件存在时起效
    • @ConditionalOnJndi : 指定的JNDI存在时起效
    • @ConditionalOnJava : 指定的Java版本存在时起效
    • @ConditionalOnWebApplication : Web应用环境下起效
    • @ConditionalOnNotWebApplication : 非Web应用环境下起效

3.6 @Import支持导入的三种方式

  1. 带有@Configuration的配置类
  2. ImportSelector 的实现
  3. ImportBeanDefinitionRegistrar 的实现

4. spring.factories

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

5.starter开发常用注解

注解配置大大简化了开发,我们不在用写xml配置文件了,SpringBoot经过查找spring.factories文件,加载自动配置类,而自动配置类中定义了各种运行时判断条件,如@ConditionOnMissingBean(A.class)等,只要ioc容器中没有指定的A类型的bean信息,该配置文件才会生效。
@Condition是Spring4新提供的注解,它的作用是按照一定的条件进行判断,满足条件给容器注册bean。

  • 属性映射注解:
    • @ConfigurationProperties: 配置文件属性值和实体类的映射
    • @EnableConfigurationProperties: 和@ConfigurationProperties配合使用,把@ConfigurtionProperties修饰的类加入ioc容器
  • 配置bean注解
    • @Configuration: 标识该类为配置类,并把该类注入ioc容器
    • @Bean: 一般放在方法上,声明一个bean,bean名称默认是方法名称,类型为返回值
  • 条件注解
    • @Conditional: 根据条件类创建特定的Bean,条件类需要实现Condition接口,并重写matches接口来构造判断条件
    • @ConditionalOnBean: 容器中存在指定bean,才会实例化一个Bean
    • @ConditionalOnMissingBean:容器中不存在指定bean,才会实例化一个Bean
    • @ConditionalOnClass:系统中有指定类,才会实例化一个Bean
    • @ConditionalOnMissingClass:系统中没有指定类,才会实例化一个Bean
    • @ConditionalOnExpression:当SpEl表达式为true的时候,才会实例化一个Bean
    • @AutoConfigureAfter :在某个bean完成自动配置后实例化这个bean
    • @AutoConfigureBefore :在某个bean完成自动配置前实例化这个bean
    • @ConditionalOnJava :系统中版本是否符合要求
    • @ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时触发实例化
    • @ConditionalOnResource:类路径下是否存在指定资源文件
    • @ConditionalOnWebApplication:是web应用
    • @ConditionalOnNotWebApplication:不是web应用
    • @ConditionalOnJndi:JNDI指定存在项
    • @ConditionalOnProperty: 配置Configuration的加载规则
      • prefix :配置属性名称的前缀
      • value :数组,获取对应property名称的值,与name不可同时使用
      • name :数组,可与prefix组合使用,组成完整的配置属性名称,与value不可同时使用
      • havingValue :比较获取到的属性值与havingValue给定的值是否相同,相同才加载配置
      • matchIfMissing :缺少该配置属性时是否可以加载。如果为true,没有该配置属性时也会正常加 载;反之则不会生效

6.Full模式和Lite模式

当Configuration参数proxyBeanMethods分为如下两种情况:
Full模式:@Configuration(proxyBeanMethods = true)
Lite模式: @Configuration(proxyBeanMethods = false)

@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());
    }
}
// 注:proxyBeanMethods 是为了让使用@Bean注解的方法被代理。而不是@Bean的单例多例的设置参数。
  • 什么时候用Full全模式,什么时候用Lite轻量级模式?
1. 当在你的同一个Configuration配置类中,注入到容器中的bean实例之间有依赖关系时,建议使用Full全模式
2. 当在你的同一个Configuration配置类中,注入到容器中的bean实例之间没有依赖关系时,建议使用Lite轻量
级模式,以提高springboot的启动速度和性能

7.starter的命名规范

  • Spring官方Starter通常命名为spring-boot-starter-{name}如:spring-boot-starter-web
  • Spring官方建议非官方Starter命名应遵循{name}-spring-boot-starter的格式:如mybatis-spring-boot-starter。

8. 自定义自己的starter流程

8.1 创建starter项目

在这里插入图片描述

注意:新建项目后,需要删除main启动类

8.2 添加依赖

8.3 编写属性类

8.4 自定义业务类

8.5 编写自动配置类

8.6 编写spring.factories

8.7 编写配置提示文件(非必须)

9. 测试starter

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot是一个快速开发微服务的框架,它提供了大量的自动化配置和快速开发的工具。在Spring Boot中,我们可以使用Starter来快速集成一些常用的功能,如数据库、缓存、web等。而自定义Starter则可以让我们将自己的功能快速集成到Spring Boot中,下面是自定义Starter的流程: 1. 创建Maven项目 首先,我们需要创建一个Maven项目作为我们自定义Starter的项目。在项目的pom.xml中添加Spring Boot的依赖,以及其他需要集成的依赖。 2. 编写自动配置类 自动配置类是自定义Starter的核心,它负责将我们自定义的功能集成到Spring Boot中。在自动配置类中,我们可以使用@Conditional注解来判断是否需要进行配置。 3. 编写Starter类 Starter类是我们自定义Starter的入口,它负责将自动配置类注入到Spring容器中。在Starter类中,我们需要使用@EnableAutoConfiguration注解来启用自动配置。 4. 打包和发布Starter 当我们完成了自动配置类和Starter类的编写后,我们需要将自定义Starter打包成jar包,并发布到Maven仓库中,以便其他项目可以通过Maven依赖的方式使用我们的Starter。 5. 在项目中使用自定义Starter 在其他项目中使用自定义Starter非常简单,只需要在项目中的pom.xml中添加我们自定义Starter的依赖即可。Spring Boot会自动将我们的自定义Starter集成到项目中,并进行自动配置。 以上就是自定义Starter的流程,通过自定义Starter,我们可以将自己的功能快速集成到Spring Boot中,提高开发效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值