调试方法以及代码分析
之前调试源码的时候,总是在自认为是关键点的地方打上断点,然后就一步一步的调试到断点处。但是这种方式是错误而且低效的,因为所有的端点只是你想当然的认为它会走的,很多细节如果不注意的话就容易错过,而像Spring的这种超大工程,错过这些细节就可能错过了一个重要的知识点,而这个知识点就是你理解原理的重要一步。今天参考一个教学视频,发现他的调试方式与自己的方法不一样,而且人家是高效。他是查看Debugger的调用栈,以前也看到过这玩意,但是不知道这是干嘛。通过Debugger调用栈,能快速定位系统跑到你打的断点的地方调用的每一个方法。下面与Idea为例。
后置处理器
执行到 ConfigurationClassPostProcessor.processConfigBeanDefinitions() 中,需要执行ConfigurationClassParser.parse(class),其中这个class就是你的主类 ,
//声明parse对象,
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
//这里获取到的对象就是SpringBoot的启动类
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<BeanDefinitionHolder>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<ConfigurationClass>(configCandidates.size());
do {
parser.parse(candidates);
parser.validate();
再来看看pase方法干了啥。
public void parse(Set<BeanDefinitionHolder> configCandidates) {
this.deferredImportSelectors = new LinkedList<DeferredImportSelectorHolder>();
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
}
}
//parse方法
//获取到主启动类的注解@SpringBootApplication
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(metadata, beanName));
}
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
//省去 ------
// Recursively process the configuration class and its superclass hierarchy.
// sourceClass --> SpringBoot的主启动类
SourceClass sourceClass = asSourceClass(configClass);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
}
doProcessConfigurationClass
//处理启动类的注解,给他们各自的注解映射处理器
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
throws IOException {
// Recursively process any member (nested) classes first
//返回空
processMemberClasses(configClass, sourceClass);
// Process any @PropertySource annotations
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}
// Process any @ComponentScan annotations
//处理ComponentScan注解,将对应的包名下的类加载到Spring容器里面
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
//将对应的包名下的类加载到Spring容器里面
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if needed
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
if (ConfigurationClassUtils.checkConfigurationClassCandidate(
holder.getBeanDefinition(), this.metadataReaderFactory)) {
parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
}
}
}
}
// Process any @Import annotations
//处理@Import,getImports(sourceClass)返回@Import下的类的集合
//核心处理方法,将Import的类注入到容器里面,后期通过发射调用该类的方法
//getImports(sourceClass)
//ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
// ParserStrategyUtils.invokeAwareMethods(selector, this.environment, this.resourceLoader, this.registry);
processImports(configClass, sourceClass, getImports(sourceClass), true);
// Process any @ImportResource annotations
if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) {
AnnotationAttributes importResource =
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
String[] resources = importResource.getStringArray("locations");
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
for (String resource : resources) {
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
configClass.addImportedResource(resolvedResource, readerClass);
}
}
// Process individual @Bean methods
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// Process default methods on interfaces
processInterfaces(configClass, sourceClass);
// Process superclass, if any
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if (!superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// Superclass found, return its annotation metadata and recurse
return sourceClass.getSuperClass();
}
}
// No superclass -> processing is complete
return null;
}
@Import(EnableAutoConfigurationImportSelector.class)
以**@Import(EnableAutoConfigurationImportSelector.class)**为例,实际在上面吧该有的主键加载到Spring容器后,会调用 ConfigurationClassParser.
private void processDeferredImportSelectors() {
List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
this.deferredImportSelectors = null;
Collections.sort(deferredImports, DEFERRED_IMPORT_COMPARATOR);
//循环获取Selector类
for (DeferredImportSelectorHolder deferredImport : deferredImports) {
//返回的是主启动类
ConfigurationClass configClass = deferredImport.getConfigurationClass();
try {
String[] imports =
//deferredImport.getImportSelector() 获取到AutoConfigurationImportSelector对象
deferredImport.getImportSelector().selectImports(configClass.getMetadata());
//将上一步的获取需要加载的类全部加载到容器中
processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false);
}
在 List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors; 这个方法里边,返回的是主启动类所有的加了@Import的方法 。经过调试,他的返回结果是
这样就完成了调用 AutoConfigurationImportSelector.selectImports方法. 该方法返回的结果是系统启动需要自动装配的类。结果如下:
String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());
0 = "org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration"
1 = "org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration"
2 = "org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration"
3 = "org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration"
4 = "org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration"
5 = "org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration"
6 = "org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration"
7 = "org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration"
8 = "org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration"
9 = "com.alibaba.boot.dubbo.actuate.autoconfigure.DubboEndpointAutoConfiguration"
10 = "com.alibaba.boot.dubbo.autoconfigure.DubboAutoConfiguration"
11 = "com.alibaba.boot.dubbo.actuate.autoconfigure.DubboHealthIndicatorAutoConfiguration"
12 = "org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration"
13 = "org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration"
14 = "org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration"
15 = "org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration"
16 = "org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration"
17 = "org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration"
18 = "org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration"
19 = "org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration"
20 = "org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration"
21 = "org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration"
22 = "org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration"
23 = "org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration"
最后,通过 processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false); 将需要的类加载到容器里面。
总结
这找到了之前一直纠结的问题根源在哪里:原理都是根据反射区获取注解,然后再利用反射获取调用注解上面的属性来完成对象的赋值等操作。但是之前都以为是显式的调用。xxx.getAnnotation().xxx()。但是一直没到到对应的代码,所以最近一直在纠结这一块的问题,后来在看别人的视屏看到人家的调试方法,瞬时恍然大悟,以前调试都是按照自己的主观意识去猜测程序的下一步会跳到哪个类里,这样做的方式低效。而通过Debugger的调用栈,能够很清晰的看到程序要执行到断点处,会经过哪些方法,再根据实际情况在这些调用栈上面加上对应的断点来调试,这样不仅效率能提高也能提升对源码学习的兴趣。