Spring Boot核心原理分析
程序员文章站
2022-05-04 17:11:12
...
什么是SpringBoot
简而言之是一个服务于框架的框架,Spring全家桶的配置本身过于繁琐,所以SpringBoot提供了解决
思路,就是约定优于配置。SpringBoot本身并没有新技术,只是对于已有的框架进行封装,达到开箱即用。
约定优于配置
在SpringBoot中的体现在
1.Maven的目录结构(默认会以jar方式打包,默认会有resources文件夹)
2.Spring-boot-starter-web。(内置了Tomcat、resources\{static/templates}去放置静态资源与模板资源)
3.默认application.properties进行配置
SpringBoot.starter
SpringBootApplication注解
该注解是SpringBoot项目中的核心注解,该注解也是一个复合注解。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
@ConfigurationPropertiesScan
public @interface SpringBootApplication {
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
在这些注解中有几个有过Spring基础的都不会陌生:
1.ComponentScan
2.EnableAutoConfiguration
3.Configuration(SpringBootConfiguration)
这三个注解的功能也就决定了SpringBootApplication注解的功能,接下来分别看下这三个注解
Configuration
在以前使用IOC的时候需要基于xml来配置bean依赖关系,在Spirng3的时候提供了JavaConfig,
即任何一个标注了@Configuration的java类定义为以个JavaConfig配置类,在该类中的任何标注了
@Bean的方法,该返回值都会作为Bean定义到SpringIOC容器之中,Bean的id默认是方法名。
示例代码
public class DemoOne {
public void say(){
System.out.println("Say:hello boot");
}
}
@Configuration
public class ConfigurationDemo {
@Bean
public DemoOne demoOne(){
return new DemoOne();
}
}
public class ConfigurationMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext=new AnnotationConfigApplicationContext(ConfigurationDemo.class);
DemoOne demoOne=applicationContext.getBean(DemoOne.class);
demoOne.say();
}
}
ComponentScan
ComponentScan这个注解在使用过Spring的过程中接触最多了吧,相当于把xml文件中的
<context:componet-scan>,其主要作用就是扫描指定路径下的标识了需要装配的类,自动配置到
SpringIOC容器中。
标识需要装配的类的形式主要是:@Componet、@Reposity、@Service、@Controller这类注解标识类
ComponentScan默认会扫描当前package下的所有加了相关注解标识的类到IOC容器中。
@ComponentScan
public class ConfigurationMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext=new AnnotationConfigApplicationContext(ConfigurationMain.class);
String[] defName=applicationContext.getBeanDefinitionNames();
for (String s : defName) {
System.out.println(s);
}
}
}
需要注意的是AnnotationConfigApplicationContext构造方法中传入的是ComponentScan注解
的类, 并且清除了ConfigurationDemo 中的Bean,如果要扫描其他包在ComponentScan注解中添加
basePackages属性即可,打印结果如下
EnableAutoConfiguration
在EnableAutoConfiguration定义两个注解
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
在这个注解中@import是用于导入外部资源配置。在该注解中导入了
AutoConfigurationImportSelector.class
而在AutoConfigurationPackage注解中也导入了
AutoConfigurationPackages.Registrar.class
经过前面两个注解的分析,可以想到,SpringBoot的自动配置核心应该是在这两个类之中。
在看这两个类之前,我们前面所介绍的注解都是一个目的,把Bean交由SpringIOC去管理,可是当你想
配置不同的Bean或者根据条件去装载Bean却力有不逮,所以可以想到EnableAutoConfiguration注解与
AutoConfigurationImportSelector、AutoConfigurationPackages.Registrar两个Class目的都是
为了实现动态注入Bean
打开AutoConfigurationImportSelector的类关系图可以发现它顶层是继承了ImportSelector接口。
在此试着自己写一个类来继承该接口,看下该接口的作用。
public class CacheImprotSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//获取注解原信息
Map<String, Object> attributes =
importingClassMetadata.getAnnotationAttributes(EnableDefineService.class.getName());
return new String[]{CacheService.class.getName()}; //返回的是固定的CacheService
}
}
同时自定义了一个注解(仿照EnableAutoConfiguration)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(CacheImprotSelector.class)
public @interface EnableDefineService {
//配置一些方法
Class<?>[] exclude() default {};
}
重新写一个启动方法
@SpringBootApplication
@EnableDefineService(exclude={LoggerService.class})
public class EnableDemoMain {
public static void main(String[] args) {
ConfigurableApplicationContext ca= SpringApplication.run(EnableDemoMain.class,args);
System.out.println(ca.getBean(CacheService.class));
}
}
在CacheImprotSelector类的selectImports打上断点调试,可以发现可以通过
AnnotationMetadata获取注解的原信息,也可以通过
返回new String[]{CacheService.class.getName()}来决定启动哪个bean,
那么在此就已经很明朗了,SpringBoot通过EnableAutoConfiguration中的导入类去控制Bean加载。
上面有说导入两个Class,那么另一个——AutoConfigurationPackages.Registrar.class的作用是
什么呢?Registrar继承了ImportBeanDefinitionRegistrar接口,这个接口是另一种动态装载Bean
的方式。编写一个类继承ImportBeanDefinitionRegistrar介绍其使用。
继承接口类实现了registerBeanDefinitions方法
public class LoggerDefinitionRegistart implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Class beanClass=LoggerService.class;//需要注入的类
RootBeanDefinition beanDefinition=new RootBeanDefinition(beanClass);
String name=StringUtils.uncapitalize(beanClass.getSimpleName());
registry.registerBeanDefinition(name,beanDefinition);//传入beanName 和Bean
}
}
在前面的EnableDefineService 注解中重新修改下Import注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({CacheImprotSelector.class,LoggerDefinitionRegistart.class})
public @interface EnableDefineService {
//配置一些方法
Class<?>[] exclude() default {};
}
启动方法
@SpringBootApplication
@EnableDefineService
public class EnableDemoMain {
public static void main(String[] args) {
ConfigurableApplicationContext ca= SpringApplication.run(EnableDemoMain.class,args);
System.out.println(ca.getBean(CacheService.class));
System.out.println(ca.getBean(LoggerService.class));
}
}
可以看到两个类都被注入成功,在此也就明了SpringBoot启动时的Bean的自动注入。接下来我们去看一下AutoConfigurationImportSelector源码,这样也就不至于两眼一抹黑了。
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);//加载所有键所对应的值的类
configurations = removeDuplicates(configurations);//删除重复的
Set<String> exclusions = getExclusions(annotationMetadata, attributes);//排除已经在注解加过exclude的类
checkExcludedClasses(configurations, exclusions)
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);//过滤
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
/*
*这是个加载器,它会加载 META-INF/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;
}
上面两段代码就是为了一个选择性装配,通过loadMetadata读取注解元数据,就是决定要在EnableAutoConfiguration加载的Bean,再通过getAutoConfigurationEntry去条件删选哪些要加载,哪些不用加载。
推荐阅读
-
Spring Cloud动态配置实现原理与源码分析
-
详解Spring Boot下Druid连接池的使用配置分析
-
spring boot jar的启动原理解析
-
Spring Cloud动态配置实现原理与源码分析
-
Yii核心组件AssetManager原理分析
-
SpringBoot 源码解析 (七)----- Spring Boot的核心能力 - SpringBoot如何实现SpringMvc的?
-
SpringBoot 源码解析 (六)----- Spring Boot的核心能力 - 内置Servlet容器源码分析(Tomcat)
-
Spring Boot2.X整合消息中间件RabbitMQ原理简浅探析
-
Spring-AOP本质原理分析(七)
-
深入源码分析Spring注解的实现原理---@Import