面试 – 如何编写一个SpringBoot-Starter?

2023-01-19 09:42:00 浏览数 (1)

刚毕业第一批面试的时候,被问过如何手写MVC框架,但是感觉面试官在扯淡,我刚毕业的CRUD,你非要写尼玛MVC框架?面试第二家公司的时候遇到:如何手写SpringBoot-Starter?我感觉一样扯淡,我有必要写Boot-Starter么?但为了丰富技术点,当天晚上就研究一下,如何手写Boot-Starter?

Gitee项目地址:https://gitee.com/li_kun_zang/Spring-Boot-Starter 强烈推荐查看本文章讲解的内容,欢迎指错!

SpringBoot-Starter原理

说手写SpringBoot Starter 本质就是了解SpringBoot是如何启动的,以及Bean是如何自动配置的!

深入分析

SpringBoot 启动靠的是一个注解@SpringBootApplication,我们可以查看其源码:发现还有3个注解

代码语言:javascript复制
@SpringBootConfiguration // 触发自动配置和组件扫描
@EnableAutoConfiguration // 启用Spring Application Context的自动配置,尝试猜测和配置您可能需要的bean。
@ComponentScan // 配置用于@Configuration类的组件扫描指令。

我们来分析一下上面3个注解:

  • @EnableAutoConfiguration 启用SpringBoot 的自动配置机制
  • @SpringBootConfiguration = @Configuration:允许在上下文中注册额外的 bean 或导入其他配置类
  • @ComponentScan:扫描被@Component 注解的 bean(如:@Service,@Controller),注解默认会扫描启动类所在的包路径下所有的类 ,可以自定义不扫描某些 bean。

@EnableAutoConfiguration 是实现自动装配的重要注解,其实现了:@Import(AutoConfigurationImportSelector.class)

什么意思的,猜猜看,看名字:自动、配置、导入、选择器,那就是自动配置的,我们继续点AutoConfigurationImportSelector进去看实现,

代码语言:javascript复制
	/**
	 * 返回应该考虑的自动配置类名。Return the auto-configuration class names that should be considered. By default
	 * this method will load candidates using {@link SpringFactoriesLoader} with
	 * {@link #getSpringFactoriesLoaderFactoryClass()}.
	 * @param metadata the source metadata
	 * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
	 * attributes}
	 * @return a list of candidate configurations
	 */
	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;
	}

啥都不看,就看提示:没有自动配置字节码被发现与:META-INF/spring.factories文件夹下。如果你使用了自定义包,确保META-INF/spring.factories文件是正确的。

到此,本文讲述了我们手写自己的Starter必须创建自己的spring.factories。以实现Bean的加载。

1、先了解 @EnableConfigurationProperties是干嘛的:使用 @ConfigurationProperties 注解的类生效。

@ConfigurationProperties又是干嘛的:将其配置文件的内容注入到对象中

我们直接上案例

代码语言:javascript复制
# 一个配置文件
user.zhangsan.name=zhangsan
user.zhangsan.age=10
user.zhangsan.hobbits=??,??

一个实体

代码语言:javascript复制
@Data
@ConfigurationProperties("user.zhangsan") // 读取配置文件的内容
public class User {
    /**
     * 姓名
     */
    private String name;

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 爱好
     */
    private String[] hobbits;
}

添加一个配置类

代码语言:javascript复制
@Configuration
@EnableConfigurationProperties({User.class})
public class UserConfigration {
}

看起来没啥问题吧,但是想过一个问题么,User对象是IOC容器管理的对象么?很显然不是。我们要使用User对象,就必须将其弄到IOC容器中。我们要不在User类上添加@Component或@Configuration(当然用@Controller、@Service、@Mapper就不合适了)。要么就使用@EnableConfigurationProperties({类.class}) 实现加载到IOC容器中。

到这里跟我们手写Starter关系不大,我们更加去关注如何实现自动装配的。

接下来我们就需要去了解@Import实现了3中实现Bean注册的方式

方式一:直接@Import({User.class})

代码语言:javascript复制
@Import({User.class}) // 方式一:注册Bean

方式二:实现ImportSelector接口,同时需要在配置类中,@Import({DefaultImportSelector.class})

代码语言:javascript复制
/**
 * @Description :注入方式二:实现ImportSelector接口重写selectImports方法
 */
public class DefaultImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //return new String[0];
        return new String[]{"com.zanglikun.entity.User"}; // 指定IOC容器加载此Bean
    }
}

方式三:实现ImportBeanDefinitionRegistrar方法。自定义配置同时需要在配置类中,@Import({DefaultImportBeanDefinitionRegistrar.class})

代码语言:javascript复制
public class DefaultImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // ImportBeanDefinitionRegistrar.super.registerBeanDefinitions(importingClassMetadata, registry);
        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(User.class); // 配置Bean
        registry.registerBeanDefinition("userInstance",rootBeanDefinition); // 注册Bean
    }
}

如果你想你的starter被依赖项目的配置文件自动提示,请参考:https://cloud.tencent.com/developer/article/2162433

想了解spring.factories的作用么?

他的作用就是在IOC容器加载过程中,扫描项目依赖所有jar中META-INF文件夹下的spring.factories。然后初始化Bean。

文件内容长这样

代码语言:javascript复制
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.zanglikun.config.UserConfigration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.zanglikun.config.XXX
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.zanglikun.config.XXXX

注意,统一前缀是org.springframework.boot.autoconfigure.EnableAutoConfiguration,后面加上你的要加载的全限定类名。这句话的作用就是 相当于在类上,加入@Configuration。

有啥用?我们每个单体SpringBoot项目都是有包扫描的吧?这spring.factories玩意就是给包扫描用的。

更具体一点,我们项目是com.alibaba。但是我们引入一个starter,他是org.xxx开头的。我们包扫描只会扫描启动类包以下的Bean对象。很明显,我们引入的starter不在扫描的范围,你@Auworied Starter的对象能成功嘛?肯定不行。如果我们配置了spring.factories。他就会扫描jar所有的此文件,然后再去加载Bean对象!这就是SpringBoot不用我们额外添加@ComponentScan的原因!

原则上,我们上文分析了所有手写BootStarter的内容了。我们闭眼想一下。

我们首先弄一个SpringBoot的依赖。

创建一个配置类(创建多个也无所谓)。

ps:如果你想弄一些复杂的Bean初始化,通过自己的配置类@Import这些配置。

resources文件夹下创建一个META-INF文件夹再创建一个spring.factories文件,指向我们的配置类。

打成jar包

另一个项目去依赖它。然后启动测试!

说白了,本篇文章只是简单说明了从应用的角度讲述了手写SpringBoot Starter。深度原理SPI压根没设计。等待我翻源码的时候,在补充,丰富本文章。

项目地址:https://gitee.com/li_kun_zang/Spring-Boot-Starter

特殊说明: 以上文章,均是我实际操作,写出来的笔记资料,不会盗用别人文章!烦请各位,请勿直接盗用!转载记得标注来源!

0 人点赞