【干货点】 此处是 【好好面试】 系列文的第13篇文章。 如今网上大把说IoC的文章,但是手把手调试源码的却很少,也不多会描述IoC容器的实现步骤,如今就让稀饭下雪手把手调试源码看看IoC是怎么个东西,顺便记住几个关键知识点,之后面试无忧;不懂如何看源码的兄弟们要跟上啦,能够仔细看看个人调试骚法。程序员
依稀记得,在不少年前,我在简历技能栏目上写着熟悉spring几个字,后来面试官瞄了一眼,熟悉spring?不错,说说看IoC容器的实现。面试
我当时就懵逼了,说实话,我一直都知道IoC是什么,原理是什么,我还能够用类比的方式和你说这个鬼东西,但是你让我说说IoC容器的实现,我还真TM一时回答不上来,由于我没有看过源码。而这也是我写这篇文章的初衷,为了可以说清楚这个过程,我将整个SpringBoot的启动过程和IoC容器的实现过程都仔仔细细看了一遍。spring
仔细浏览了源码后,得出了一个总结,同时也是**【面试热点】** ,IoC容器的实现能够分为如下几步:bash
使用过SpringBoot的都知道,关于组件的扫描是从主类所在的包开始扫描的,经过阅读源码咱们能够看到,在prepareContext()方法中,SpringBoot会先将主类,也就是使用了注解@SpringBootApplication的类,解析成BeanDefinition,以后在invokeBeanFactoryPostProcessors()方法中解析主类的BeanDefinition从而获取basePackage的路径,而这个路径也就是咱们所说的定位了,固然了,此处不讨论使用了注解@Import的状况。app
在取得了Resource的定位后,天然而然下一步确定是将各类BeanDefinition的载入到内存中了,这里看源码其实就调试了好久,可是其实流程极其简单。所谓的载入内存中,用脚指头想一想都知道其实就是经过Resource的定位,拼接了一下路径变成:classpath*:org/springframework/boot/demo/**/*.class这样的形式,以后再使用了一个比较屌的类PathMatchingResourcePatternResolver,将该路径下的全部.class的文件加载进来,以后再遍历判断是否有@Component注解,不过不要觉得能够直接经过@Component注解来反调源码,年轻的我也是这么作的,不过发现其实不是哟,想知道就继续看。若是有注解的话,就是该步骤要装载的BeanDefinition了。ide
所谓的注册,听起来很高大上,将类注册入IoC容器内,可是其实很简单,其实就是将其put到一个CurrentHashMap中罢了,是的,IoC容器其实就是使用这个Map来存放这些BeanDefinition数据的。函数
【舒适提示】:若是不知道BeanDefinition是什么的同窗就该面壁了哟,说明Spring了解确实浅,同时也没有看我以前的文章,能够看看这篇Spring之Bean的动态注册工具
其实看到这里也差很少能够了,毕竟大体描述下过程的话确实就是上面三个了,有兴趣看源码解析的能够继续。后面我会将调试的流程跟着源码描述出来,最好是跟着代码调试,否则会晕圈的。post
依旧按照以前套路,直接先定位到资源定位的地方,再经过调试页面,看看程序作了什么,咱们能够直接跳到 ComponentScanAnnotationParser类中的parse函数,首先先解释一波什么是ComponentScanAnnotationParser ,所谓的ComponentScanAnnotationParser其实只是Spring的一个内部工具,它会基于某个类上的 @ComponentScan 注解属性分析指定包(package)以获取其中的BeanDefiniiton。学习
接下来咱们看看其中的parse函数,跟着调试的同窗能够直接给这个函数的最后一段代码加个、断点,启动项目,就能够看到项目的运行流程了,以下所示
// 经过调试能够看到componentScan携带者basePackages这个数据,而declaringClass是
// "com.nuofankj.demo.DemoApplication",也就是包路径+主类
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
BeanUtils.instantiateClass(generatorClass));
ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
scanner.setScopedProxyMode(scopedProxyMode);
}
else {
Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
}
scanner.setResourcePattern(componentScan.getString("resourcePattern"));
for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addIncludeFilter(typeFilter);
}
}
for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addExcludeFilter(typeFilter);
}
}
boolean lazyInit = componentScan.getBoolean("lazyInit");
if (lazyInit) {
scanner.getBeanDefinitionDefaults().setLazyInit(true);
}
// 【重点】真正存放资源位置的地方
Set<String> basePackages = new LinkedHashSet<>();
String[] basePackagesArray = componentScan.getStringArray("basePackages");
for (String pkg : basePackagesArray) {
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
Collections.addAll(basePackages, tokenized);
}
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
// 【重点】该次运行,因为没有是默认启动,所以最终basePackages存放的是declaringClass的包路径,这点能够直接看
// ClassUtils.getPackageName(declaringClass)ll
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(declaringClass));
}
scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
@Override
protected boolean matchClassName(String className) {
return declaringClass.equals(className);
}
});
// 【重点】StringUtils.toStringArray(basePackages) 断点能够看到打印出来的是主类的包名【com.nuofankj.demo】
return scanner.doScan(StringUtils.toStringArray(basePackages));
}
复制代码
接下来让咱们看看 scanner.doScan 中作了什么
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
// 【重点】从指定的包中扫描须要装载的Bean
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
// 【重点】将该 Bean 注册进 IoC容器(beanDefinitionMap)
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
复制代码
上面中有标注了两个比较重要的方法 Set candidates = findCandidateComponents(basePackage); 做用是从basePackage中扫描类并解析成BeanDefinition,邓拿到全部符合条件的类后在 **registerBeanDefinition(definitionHolder, this.registry);**中将该类注册进IoC容器。也就是说在这个方法中完成了IoC容器初始化过程的第二三步,BeanDefinition的载入,和BeanDefinition的注册。
到这里,资源的定位到这里就结束了。
咱们接着上面所说的函数scanCandidateComponents
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
// 【重要】拼接扫描路径,最终生成的是 classpath*:com/nuofankj/demo/**/*.class
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
// 从上面拼接出来的packageSearchPath 路径中扫描全部的类
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
// 【重要】这里判断该类是否是 @Component 注解标注的类
if (isCandidateComponent(metadataReader)) {
// 将该类封装成ScannedGenericBeanDefinition,这是BeanDefinition接口的实现类
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
} else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + resource);
}
}
} else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: " + resource);
}
}
} catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
} else {
if (traceEnabled) {
logger.trace("Ignored because not readable: " + resource);
}
}
}
} catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
复制代码
看源码和注释咱们能够看到,packageSearchPath 是经过拼接出来的,而会在getResources(packageSearchPath)方法中扫描到了该路径下的全部的类,以后遍历这些Resources,判断是不是@Component 注解标注的类,而且不是须要排除掉的类。以后便将扫描到的类,解析成ScannedGenericBeanDefinition,该类是BeanDefinition接口的实现类。 到这里 IoC容器的BeanDefinition载入就结束了。
载入结束了,接下来让咱们看看如何进行BeanDefinition的注册,从新回到 registerBeanDefinition(definitionHolder, this.registry) ,根据调试能够看到,最终会进入如下函数
public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {
String beanName = definitionHolder.getBeanName();
// 【重点】
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
String[] var4 = aliases;
int var5 = aliases.length;
for(int var6 = 0; var6 < var5; ++var6) {
String alias = var4[var6];
registry.registerAlias(beanName, alias);
}
}
}
复制代码
咱们先看看什么是BeanDefinitionRegistry,该类的做用主要是向注册表中注册 BeanDefinition 实例,完成 注册的过程。继续看下重点函数 registerBeanDefinition
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
} catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}
// 【重点】能够看到这里,采用ConcurrentHashMap存放bean,看到这里即可以知道了,其实IoC容器的最底层就是一个ConcurrentHashMap,只是它被放到了某个对象中,经过看源码能够知道这个对象是DefaultListableBeanFactory
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
if (existingDefinition != null) {
// 【重点】若是该类不容许 Overriding 直接抛出异常
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
"': There is already [" + existingDefinition + "] bound.");
} else if (existingDefinition.getRole() < beanDefinition.getRole()) {
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
if (logger.isWarnEnabled()) {
logger.warn("Overriding user-defined bean definition for bean '" + beanName +
"' with a framework-generated bean definition: replacing [" +
existingDefinition + "] with [" + beanDefinition + "]");
}
} else if (!beanDefinition.equals(existingDefinition)) {
if (logger.isInfoEnabled()) {
logger.info("Overriding bean definition for bean '" + beanName +
"' with a different definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("Overriding bean definition for bean '" + beanName +
"' with an equivalent definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
}
// 【重点】注册进beanDefinitionMap
this.beanDefinitionMap.put(beanName, beanDefinition);
} else {
if (hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
synchronized (this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
if (this.manualSingletonNames.contains(beanName)) {
Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
updatedSingletons.remove(beanName);
this.manualSingletonNames = updatedSingletons;
}
}
} else {
// 【重点】若是仍处于启动注册阶段,注册进beanDefinitionMap
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
this.manualSingletonNames.remove(beanName);
}
this.frozenBeanDefinitionNames = null;
}
if (existingDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
}
复制代码
经过一步步调试,咱们能够看到最终走到了DefaultListableBeanFactory,首先介绍下什么是DefaultListableBeanFactory,该类能够说就是IoC容器本器了,调试源码看到最后,其实IoC容器就是一个ConcurrentHashMap。 那么DefaultListableBeanFactory是何时构建的呢?咱们能够看到
文章总结的我都调到前面了,因此总结就不说了,说说看最近更文速度变慢的缘由,主要仍是状态很差,忙是一直都忙的,毕竟身处游戏行业,天天9点或者10点上班,23点左右才下班,不过状态很差的缘由主要仍是情感问题,因此无意学习致使无意写文章。其次是发现最近容易脖子酸,职业病呀,关注个人应该大部分是程序员,若是有好的建议,能够提下哈,我说的是职业病预防,固然了,若是你要给我介绍女友我也是不介意的 ○( ^皿^)っHiahiahia…