刚毕业第一批面试的时候,被问过如何手写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
特殊说明: 以上文章,均是我实际操作,写出来的笔记资料,不会盗用别人文章!烦请各位,请勿直接盗用!转载记得标注来源!