前言
本来这篇文章去年就应该发了,但是有些事情被耽搁了,最近整理了下发了出来,关于spring源码相关有想沟通的小伙伴可以随时联系我。
本篇文章内容有点臃肿,可以选择性跳跃去看。干货还是有点的,尤其总结部分。
本篇承接上篇末尾之讨论spring源码案例分析之健康检查,有兴趣的读者可以去看看,说不定有对你帮助的惊喜。
本篇开始之前,我们先看下我项目总体的结构,如下图
4.0.0
com.xxq
spring-source-analysis
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-parent
1.5.13.RELEASE
org.springframework.boot
spring-boot-starter-web
pom文件很简单,引入父工程spring-boot-starter-parent,因为这次我们分析借助于web切入点,因此我们只简单引入一个starter-web就够了,其他没什么可说的,再看下代码结构:
很简单,一个springboot启动类,我们看下面一段代码:
@Configuration
@AutoConfigureAfter(HttpMessageConvertersAutoConfiguration.class)
public class WebClientAutoConfiguration {
@Configuration
@ConditionalOnClass(RestTemplate.class)
public static class RestTemplateConfiguration {
private final ObjectProvider messageConverters;
private final ObjectProvider> restTemplateCustomizers;
public RestTemplateConfiguration(
ObjectProvider messageConverters,
ObjectProvider> restTemplateCustomizers) {
this.messageConverters = messageConverters;
this.restTemplateCustomizers = restTemplateCustomizers;
}
@Bean
@ConditionalOnMissingBean
public RestTemplateBuilder restTemplateBuilder() {
RestTemplateBuilder builder = new RestTemplateBuilder();
HttpMessageConverters converters = this.messageConverters.getIfUnique();
if (converters != null) {
builder = builder.messageConverters(converters.getConverters());
}
List customizers = this.restTemplateCustomizers
.getIfAvailable();
if (!CollectionUtils.isEmpty(customizers)) {
customizers = new ArrayList(customizers);
AnnotationAwareOrderComparator.sort(customizers);
builder = builder.customizers(customizers);
}
return builder;
}
}
}
org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration.RestTemplateConfiguration#restTemplateBuilder
这段代码spring自动装配了一个RestTemplateBuilder,我们注意到一个注解,@ConditionalOnMissingBean,这里我大致说下作用,不做深入研究,以后有机会我会单独写篇文章去分析这个注解,这个注解的大致作用就是如果容器中没有这个RestTemplateBuilder,那么这个@Bean的配置就生效,简单来说,就是没有这个bean,spring就会默认生成一个,有的话就不生成,这个注解是spring自动装配的一个核心点。
我们看下面这段代码:
@Configuration
public class Appconfig {
@Bean
public RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder();
}
}
这段代码,注入了一个RestTemplateBuilder,那么问题来了,如果我也写了这样一个注入方法,那么spring用的RestTemplateBuilder到底是我们的还是spring自己的,有的人说是我们自己的,有的人说是spring默认的,那么,原因呢?下面我们来看下到底使用的是哪个。
获取容器中的bean
可以看到容器中的bean的内存地址和自定义的内存地址是一致的,所以可以确定的是,spring确实初始化的是我们定义的的,我们稍后我具体分析这个流程。
在正式开始分析之前,我们先区分下俩个注解,这对我们下面的分析有点铺垫,@Order和@Primary。
- @Order一般用于拦截器和过滤器,是控制同类型bean的执行顺序,一个容器可以有多个filter和interceptor,每个承担不同的角色,但是可能有的先执行,有的要后执行,这时候@Order就排上用场了,在ssm架构体系下,如果想要控制过滤器的执行顺序,是通过在web.xml中配置过滤器的顺序来实现的,如今使用springboot方式,已经没有了web.xml,所以spring就创造了这种方式,不过现在已经不仅仅局限于过滤器和拦截器,包括spring command以及其他使用场景。
- @Primary,是在bean注入的时候起作用的。如果一个容器中,有多个类型一样bean,不使用相关注解去处理,那么容器初始化的时候并不会报错,但是在注入的时候就会报错,这是由于spring并不知道你想要使用的是哪个。我们看下面的代码。
@Configuration
public class Appconfig {
@Bean
public RestTemplateBuilder restTemplateBuilder() {
RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
System.out.println(restTemplateBuilder.hashCode());
return restTemplateBuilder;
}
@Bean
public RestTemplateBuilder restTemplateBuilder1() {
RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
System.out.println(restTemplateBuilder.hashCode());
return restTemplateBuilder;
}
}
上面这段代码,在容器中配置了俩个RestTemplateBuilder类型的bean,不过他们的beanName一个是restTemplateBuilder,一个是restTemplateBuilder1。这样直接去启动工程是不会报错的。
我们可以看到容器中有俩个restTemplateBuilder,这时候正常启动,当我加入下面这段代码:
@Autowired
RestTemplateBuilder templateBuilder;
spring就会报出如下错误:
***************************
APPLICATION FAILED TO START
***************************
Description:
Field templateBuilder in com.xxq.web.MyApplicationContextAwaer required a single bean, but 2 were found:
- restTemplateBuilder: defined by method 'restTemplateBuilder' in class path resource [com/xxq/appconfig/Appconfig.class]
- restTemplateBuilder1: defined by method 'restTemplateBuilder1' in class path resource [com/xxq/appconfig/Appconfig.class]
Action:
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
异常大致翻译:找到了俩个bean,无法确定使用哪个,建议我们可以使用@Primary或者@Qualifier。于是我试着加入@Primary
@Bean
@Primary
public RestTemplateBuilder restTemplateBuilder() {
RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
System.out.println(restTemplateBuilder.hashCode());
return restTemplateBuilder;
}
可以看到,spring注入的就是我们加上@Primary注解的bean。
举这么个例子是想告诉大家,@Primary不会改变spring容器中的bean的初始化优先级,只会改变注入时候的优先级,这里牵扯到spring容器的概念,springbean容器虽然是一个map,但是不是普通意义上的map,只要你的beanName不一样,那么都会放入到这个map中,在同一个config中的@Bean模式下,如果你定义了俩个beanName一样的bean,无论类型,只会初始化一个到容器中,至于是哪个,spring会以在appconfig类中最先读到的那个为准进行覆盖。如果是在不同的config中配置,name一致就算类型不一致,会使用最后读取到的。这一块内容也还是比较复杂,场景略微有点多,且处理的方式也都不一样,后续有时间再解析。
这时候就有小伙伴问了(其实是我自己),既然上面你说spring先读到那个就初始化哪个这种情况,为什么就不可能先读到spring框架默认的bean,然后初始化呢,?接下来,我们开始分析。
1. 首先第一步,我们分析springboot启动类的加载
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
public static ConfigurableApplicationContext run(Object source, String... args) {
// 重点部分,这里把启动类作为class类型传递给source变量,后续会用到这个。
return run(new Object[] { source }, args);
}
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
analyzers = new FailureAnalyzers(context);
// 重点代码,这里会进行容器的初始化工作
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//重点代码,后续会分析到
refreshContext(context);
afterRefresh(context, applicationArguments);
listeners.finished(context, null);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
}
上面代码是spring启动类的核心代码部份,我们这次只关心关于我们研究的部分:
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context);
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
// Load the sources
Set
看上面的倒数第二行代码,这边把sources作为一个对象数组传递进去了,这个sources是来自于getSources方法,这个最终还是我上面提到的来源于启动类的传参。
protected void load(ApplicationContext context, Object[] sources) {
if (logger.isDebugEnabled()) {
logger.debug(
"Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
}
BeanDefinitionLoader loader = createBeanDefinitionLoader(
getBeanDefinitionRegistry(context), sources);
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
loader.load();
}
private int load(Class> source) {
if (isGroovyPresent()) {
// Any GroovyLoaders added in beans{} DSL can contribute beans here
if (GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source,
GroovyBeanDefinitionSource.class);
load(loader);
}
}
if (isComponent(source)) {
//重点部份,此处会把启动类进行加载进spring容器
this.annotatedReader.register(source);
return 1;
}
return 0;
}
private boolean isComponent(Class> type) {
// This has to be a bit of a guess. The only way to be sure that this type is
// eligible is to make a bean definition out of it and try to instantiate it.
// 一般都会走这个分支,因为springboot启动类似加上这个@SpringBootApplication注解
// 就算不走这个分支,下面的分支也绝大数多情况不会走,最终默认还是返回true
if (AnnotationUtils.findAnnotation(type, Component.class) != null) {
return true;
}
// Nested anonymous classes are not eligible for registration, nor are groovy
// closures
if (type.getName().matches(".*\\$_.*closure.*") || type.isAnonymousClass()
|| type.getConstructors() == null || type.getConstructors().length == 0) {
return false;
}
return true;
}
上面的load方法最终会调用annotatedReader.register(source)方法,
public void register(Class>... annotatedClasses) {
for (Class> annotatedClass : annotatedClasses) {
registerBean(annotatedClass);
}
}
public void registerBean(Class> annotatedClass, String name, Class extends Annotation>... qualifiers) {
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
return;
}
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
abd.setScope(scopeMetadata.getScopeName());
String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
if (qualifiers != null) {
for (Class extends Annotation> qualifier : qualifiers) {
if (Primary.class == qualifier) {
abd.setPrimary(true);
}
else if (Lazy.class == qualifier) {
abd.setLazyInit(true);
}
else {
abd.addQualifier(new AutowireCandidateQualifier(qualifier));
}
}
}
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
// 此处会将启动类注册进容器的beanDefinitionMap中
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}
上面最后一行方法,就是将启动类加载进容器中,这里仅仅是beanDefinition的加载,还没有到真正初始化的阶段。
2. 首先第一步,我们简单分析spring容器的生命周期
refreshContext(context);
上面有提到过这个方法很重要,我们现在来分析。
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
prepareRefresh();
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
prepareBeanFactory(beanFactory);
try {
postProcessBeanFactory(beanFactory);
// 重点代码,后置处理器的调用
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
destroyBeans();
cancelRefresh(ex);
throw ex;
}
finally {
resetCommonCaches();
}
}
}
我相信对spring如果有稍微了解的话,对于上述核心代码应该都比较熟悉,上述代码算得上是spring最最最最核心的代码了,以后有时间我们全部都分析一边,这次我们重点分析这段代码
invokeBeanFactoryPostProcessors(beanFactory);
分析spring源码,要记住最关键的一点是,spring永远都是分俩步走的:
1. 将所有要初始化的bean信息放入到beanDefinitionMap中。
2. 遍历扫描上面的map,进行bean的实例化以及初始化操作。
spring容器的构建几乎所有的操作都是围绕上述俩步来处理的,大家可以想想为什么要这么去做,我直接生成一个beanDefinition初始化一个bean难道不行吗?
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
// Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
// (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}
}
第一行代码是最重要的,看方法名就知道这里可能会调用后置处理器。
public static void invokeBeanFactoryPostProcessors(
ConfigurableListableBeanFactory beanFactory, List beanFactoryPostProcessors) {
// Invoke BeanDefinitionRegistryPostProcessors first, if any.
Set processedBeans = new HashSet();
if (beanFactory instanceof BeanDefinitionRegistry) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
List regularPostProcessors = new LinkedList();
List registryProcessors = new LinkedList();
for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
BeanDefinitionRegistryPostProcessor registryProcessor =
(BeanDefinitionRegistryPostProcessor) postProcessor;
registryProcessor.postProcessBeanDefinitionRegistry(registry);
registryProcessors.add(registryProcessor);
}
else {
regularPostProcessors.add(postProcessor);
}
}
// Do not initialize FactoryBeans here: We need to leave all regular beans
// uninitialized to let the bean factory post-processors apply to them!
// Separate between BeanDefinitionRegistryPostProcessors that implement
// PriorityOrdered, Ordered, and the rest.
List currentRegistryProcessors = new ArrayList();
// First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.
String[] postProcessorNames =
beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
processedBeans.add(ppName);
}
}
sortPostProcessors(currentRegistryProcessors, beanFactory);
registryProcessors.addAll(currentRegistryProcessors);
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
currentRegistryProcessors.clear();
// Next, invoke the BeanDefinitionRegistryPostProcessors that implement Ordered.
postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
processedBeans.add(ppName);
}
}
sortPostProcessors(currentRegistryProcessors, beanFactory);
registryProcessors.addAll(currentRegistryProcessors);
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
currentRegistryProcessors.clear();
// Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear.
boolean reiterate = true;
while (reiterate) {
reiterate = false;
postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
if (!processedBeans.contains(ppName)) {
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
processedBeans.add(ppName);
reiterate = true;
}
}
sortPostProcessors(currentRegistryProcessors, beanFactory);
registryProcessors.addAll(currentRegistryProcessors);
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
currentRegistryProcessors.clear();
}
// Now, invoke the postProcessBeanFactory callback of all processors handled so far.
invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);
}
else {
// Invoke factory processors registered with the context instance.
invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);
}
// Do not initialize FactoryBeans here: We need to leave all regular beans
// uninitialized to let the bean factory post-processors apply to them!
String[] postProcessorNames =
beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);
// Separate between BeanFactoryPostProcessors that implement PriorityOrdered,
// Ordered, and the rest.
List priorityOrderedPostProcessors = new ArrayList();
List orderedPostProcessorNames = new ArrayList();
List nonOrderedPostProcessorNames = new ArrayList();
for (String ppName : postProcessorNames) {
if (processedBeans.contains(ppName)) {
// skip - already processed in first phase above
}
else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));
}
else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
orderedPostProcessorNames.add(ppName);
}
else {
nonOrderedPostProcessorNames.add(ppName);
}
}
// First, invoke the BeanFactoryPostProcessors that implement PriorityOrdered.
sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);
// Next, invoke the BeanFactoryPostProcessors that implement Ordered.
List orderedPostProcessors = new ArrayList();
for (String postProcessorName : orderedPostProcessorNames) {
orderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
}
sortPostProcessors(orderedPostProcessors, beanFactory);
invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory);
// Finally, invoke all other BeanFactoryPostProcessors.
List nonOrderedPostProcessors = new ArrayList();
for (String postProcessorName : nonOrderedPostProcessorNames) {
nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
}
invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);
// Clear cached merged bean definitions since the post-processors might have
// modified the original metadata, e.g. replacing placeholders in values...
beanFactory.clearMetadataCache();
}
这一段代码是非常的长,而且也非常的复杂,没有认真分析过这段的,这里一时半会也无法说清,总而言之,这段代码就是spring进行分类整合,把所有的后置处理器进行区分成以下几种
- 扫描BeanDefinitionRegistry的所有bean
1. 使用BeanDefinitionRegistryPostProcessor应用于对BeanDefinitionRegistry对bean的信息进行调整和解析 - 调用所有后置处理器(BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor)
1. 应用于BeanDefinitionRegistry的后置处理器
2. 应用于BeanFactory的后置处理器
3. 以及PriorityOrdered, Ordered排序执行的后置处理器
在我看来这俩步最大的区别就是,第一步是把所有外部的类加载解析到spring容器中,在第一步执行前,spring容器中几乎没有什么bean,除了手动放进去的,第二步处理的就是第一步处理完的spring容器。也就是说第一步是基础,第二步是升华。因此对于本文的探究点,显而易见是第一步的解析,我们的bean为什么能优先于spring默认的,很大概率是在第一步已经处理好的。
上述截图,是容器第一次执行后置处理器的地方
以下分析 org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions
可以看到除了第六个是我们程序自定义的,其他都是spring默认的,也就是说,到这一步容器就这7个bean。然后看263行代码,这边有个判断,我们看下具体作用是什么。
这边有个spring得fullConfiguration以及liteConfigurationClass一说,一般来说一个springbean要么是full或者lite,经过上面的过滤后,只剩下我们程序得启动类符合了,其中具体逻辑可以自行去查看。
上图第388最终调用到下面方法块
第一部分 主要解析当前类得内部类
第二部分 主要解析一些propertySource
第三部分 以当前类为基类扫描包下面得所有类
首先他会判断我们启动类上有没有componentScan注解,有的话进行获取参数。
我们可以看到springbootApplication注解默认包含了ComponentScan注解,因此是可以解析到的。
在第289行,对这个componentScan进行扫描,如果这个注解value为null,那么就直接获取当前类得所在包名进行扫描,而我们所定义的俩个restTemplateBuild就是在当前启动类的包名下,所以理所当然能够被扫描到。
根据上面的大致分析,可以得到,spring为何优先扫描我们程序自定义的类,是因为他会先默认扫描启动类上componentScan注解得包名,如果包名未被设置,那么默认使用启动得包名进行扫描,这就是为什么很多博客或者新手使用springboot时候,大家都说把启动类放在最外层得原因所在
至于spring自身定义的bean为何没有被初始化,是因为主要原因如下:
看到没,先解析启动类componentScan的元数据,再去解析启动类的上的import的注解元数据,正是因为这个先后顺序,等去解析自动装配也就是一开始spring内置bean的那里的时候,就会发现内部已经有了当前名为restTemplateBuilder的bean,就会跳过当前内置bean的解析流程。
得到了上面的结果,那么我们可以对代码进行如下改造:
这时候就会发现spring容器中就只有一个restTemplateBuilder且是spring自身的。
到此为止,我们可以得出一个非常有意思的结论,当然也算是spring的规范,我们在启动类上加了componentScan注解后,整个springboot只会扫描我们加的这个注解指定的包名,在也不是默认当前启动类的默认包名,因此在启动类上加这个注解一定要尤其小心,否则就会放一些不易发现的错误。
至于为什么,或者原理是什么,可以细看下如下代码:
// Process any @ComponentScan annotations
Set componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
写在文后:
关于下篇文章,我暂时还没想好写哪些,其实有时候底层知识很重要,有时候框架知识也很重要,框架一般工作中用的最多,底层考验的是面试和基本功。后续可能会陆续出一些底层相关的,spring源码相关的网上有太多了,我写的话也只会写我比较关注的,或者大家有什么想了解的我也可以去写一下。