在上一章笔者介绍了ConfigurationClassParser.doProcessConfigurationClass(...)方法,在这个方法里调用了processImports(...)方法处理配置类的@Import注解,getImports(sourceClass)能从一个配置类上获取@Import注解配置的所有类形成一个集合,如果集合不为空则会在下面代码的<1>处开始遍历处理。

如果一个类是ImportSelector接口的实现类,会进入<2>处的分支,并在<3>处创建实现类的实例,在<4>处调用实例的selectImports(...)方法获取配置类列表,之后在<5>处以递归的方式调用doProcessConfigurationClass(...)将获取到的配置类传入。

如果一个类是ImportBeanDefinitionRegistrar接口的实现类,则会进入<6>处的分支,这里依旧生成一个实现类的实例,只是这里不立马回调该接口的方法,而是暂存在配置类中,待从ConfigurationClassParser.processImports(...)逐层返回到ConfigurationClassPostProcessor.processConfigBeanDefinitions(...)的do-while循环后会执行ImportBeanDefinitionRegistrar实例的回调方法。

如果一个类仅仅是平平无奇的配置类,即不是ImportSelector的实现类,也不是ImportBeanDefinitionRegistrar的实现类,则会进入<7>处的分支,这里会将配置类传给processConfigurationClass(...)方法,这里我们也可以认为是递归调用,这个方法笔者之前讲过,会将配置类上的@ComponentScans、@ComponentScan注解指定的类路径解析出来,根据类路径扫描BeanDefinition,再判断扫描出来的类是否有资格成为配置类,如果可以的话会再进一步解析。

class ConfigurationClassParser {
……
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {
……
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
……
}
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
boolean checkForCircularImports) { if (importCandidates.isEmpty()) {
return;
} if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {//<1>
if (candidate.isAssignable(ImportSelector.class)) {//<2>
// Candidate class is an ImportSelector -> delegate to it to determine imports
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
this.environment, this.resourceLoader, this.registry);//<3>
Predicate<String> selectorFilter = selector.getExclusionFilter();
if (selectorFilter != null) {
exclusionFilter = exclusionFilter.or(selectorFilter);
}
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());//<4>
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);//<5>
}
}
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {//<6>
// Candidate class is an ImportBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {//<7>
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
}
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]", ex);
}
finally {
this.importStack.pop();
}
}
}
……
}

  

那么我们来总结一下ConfigurationClassParser.processImports(...)完成的工作,这个方法主要用于处理配置类上的@Import注解,@Import注解允许我们引入多个配置类,在这个方法会遍历处理这些配置类,如果配置类是ImportSelector的实现类,执行实现类的回调方法能立马拿到待引入的配置类列表,我们可以递归调用processImports(...)方法将拿到的配置类列表传入,在这个方法会处理来自上层传入的配置类。如果配置类是ImportBeanDefinitionRegistrar接口的实现类,这里仅仅是实例化这个类的实例并暂存到配置类,并不立即调用实现类的回调方法,因为spring不能肯定开发者会往传入的registrar对象注册多少个配置类,甚至有的开发者一个都不会注册。如果一个类不是ImportSelector或ImportBeanDefinitionRegistrar接口的实现类,会调用processConfigurationClass(...)方法将配置类传入,我们可以认为这里是递归调用,因为processConfigurationClass(...)方法最终会执行到processImports(...)方法。processConfigurationClass(...)方法会解析配置类指定的类路径,并将可以成为BeanDefinition的类注册进spring容器,在尝试将扫描出来的类作为配置类进行解析。

在了解ConfigurationClassParser.parse(...)方法完成的工作后,我们来整体过一下ConfigurationClassPostProcessor.processConfigBeanDefinitions(...)方法,这个方法在最开始的时候会获取spring容器所有的beanName,遍历这些beanName拿到开发者传给容器的配置类,因为开发者可能传入多个配置类,所以这里会行程一个配置类列表,如果列表为空则退出该方法,如果列表不为空,则对列表中的配置类进行排序。

在对配置类列表排序完毕后,会进入一个do-while循环解析配置类。解析配置类是一项非常复杂的工作,解析配置类的时候不但会扫描配置类指定的类路径,还会尝试将扫描出来的BeanDefinition当做一个配置类再次进行解析,如果扫描出来的BeanDefinition可以作为配置类会再次进行二次解析,这里又会重复之前所说的解析工作,在解析完毕后,才会处理@Import注解。

在了解完spring是如何解析配置类后,我们来再来整体过下ConfigurationClassPostProcessor.processConfigBeanDefinitions(...)的逻辑,在<1>处会先用candidateNames这个数组存放spring容器目前所有的beanName。

candidateNames有以下两点作用:

  1. spring会在<2>处先根据candidateNames中存放的beanName过滤出可以成为配置类的的BeanDefinition。
  2. candidateNames可以用于判断解析配置类时是否有新的BeanDefinition被注册进spring容器,可以认为candidateNames是解析配置类前的快照,如果解析完配置类后发现spring容器的BeanDefinition数量大于快照中beanName的数量,表示在解析的时候有引入新的BeanDefinition。

在<2>处完成遍历后会将配置类的BeanDefinition和beanName包装成一个BeanDefinitionHolder对象存进configCandidates列表,之后再<3>处会根据configCandidates列表生成一个配置类集合candidates,<4>处同样会根据配置类的长度生成一个集合alreadyParsed,这个集合会存放已经解析的配置类。

之后会进入一个do-while循环,在循环中会解析、校验配置类。<5>处会获取所有解析出来的配置类,这里笔者所说的不单单是我们平常用@Configuration注解标记的配置类,在解析方法里会把类路径下描到的BeanDefinition尝试作为配置类进行解析,所以<5>处的方法还会将类路径下可以作为配置类的BeanDefinition返回。我们可以在配置类上用@Import注解引入一个ImportBeanDefinitionRegistrar实现类,也可以用@ImportResource注解引入一个XML配置文件,ImportBeanDefinitionRegistrar实现类和XML文件在解析的时候会暂存在配置类中,等到从解析方法回到do-while循环时就会在<7>处配置类中暂存的ImportBeanDefinitionRegistrar实例和XML文件,这里可能会向spring容器注册新的BeanDefinition。

在执行完<7>处的代码后,配置类才算完全被解析,在<8>会将已经被完全解析的配置类存放进alreadyParsed集合,同时我们往回看,每次执行do-while循环时,<5>处总会把目前获取到的配置类返回,configClasses可能存在已经被完全解析的配置类,所以在<6>处会把已经完全解析的配置类移除,<7>处仅处理目前需要处理尚未回调ImportBeanDefinitionRegistrar接口的实例和XML文件。

在<8>处将完全处理完毕的配置类放到alreadyParsed集合后,会清空candidates集合,如果在<9>处判断目前spring容器拥有的BeanDefinition数量大于解析配置类前beanName的数量,会进入分支<9>看看是否有需要再解析的配置类,如果有配置类添加到candidates集合则开启新的一轮的循环。如果<9>处判断解析完配置类后spring容器的BeanDefinition数量等于原先解析前beanName的数量,则candidates集合为空退出do-while循环。

那么在分支<9>中又是如何判断是否应该往candidates集合添加新的配置类呢?在分支<9>内会先把spring容器目前所有的beanName存放到newCandidateNames这个数组中,oldCandidateNames用于存放解析前的beanName,alreadyParsedClasses用于存放解析后的所有配置类。之后会在<10>处遍历spring容器目前所有的beanName,如果beanName在oldCandidateNames集合中,表示其BeanDefinition在本次循环或之前的循环已经被解析,这里不会进入<11>处的分支。如果beanName不在oldCandidateNames集合中,那么这个beanName对应的BeanDefinition有可能是在解析时注册进spring容器,也可能是解析配置类完毕后在<7>处注册进spring容器的,如果是在解析时引入,那么alreadyParsedClasses会包含这个配置类,这里不会进入<12>处的分支。如果是在<7>处处理暂存在配置类的ImportBeanDefinitionRegistrar实例和XML文件,那么alreadyParsedClasses不会包含BeanDefinition对应的类,这里只要ConfigurationClassUtils.checkConfigurationClassCandidate(...)判断BeanDefinition对应的类是配置类就会把BeanDefinition和beanName包装成BeanDefinitionHolder对象存进candidates。最后在<13>处将spring目前所有的beanName存进数组candidateNames,作为下一轮循环解析前的快照。如果下一轮循环在<9>处判断spring容器中的BeanDefinition数量等于上一轮的快照数量,就会退出循环。另外进入了<9>处的分支并不意味着一定会有下个循环,do-while退出循环的条件是candidates为空,只要新引入的BeanDefinition都是解析出来的配置类,或者新引入的BeanDefinition都不是配置类,则不会进入分支<12>,则candidates为空,会退出do-while循环。

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
……
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
String[] candidateNames = registry.getBeanDefinitionNames();//<1> for (String beanName : candidateNames) {//<2>
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
}
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
……
// Parse each @Configuration class
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry); Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);//<3>
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());//<4>
do {
parser.parse(candidates);
parser.validate(); Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());//<5>
configClasses.removeAll(alreadyParsed);//<6> // Read the model and create bean definitions based on its content
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
this.reader.loadBeanDefinitions(configClasses);//<7>
alreadyParsed.addAll(configClasses);//<8> candidates.clear();
if (registry.getBeanDefinitionCount() > candidateNames.length) {//<9>
String[] newCandidateNames = registry.getBeanDefinitionNames();
Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
Set<String> alreadyParsedClasses = new HashSet<>();
for (ConfigurationClass configurationClass : alreadyParsed) {
alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
}
for (String candidateName : newCandidateNames) {//<10>
if (!oldCandidateNames.contains(candidateName)) {//<11>
BeanDefinition bd = registry.getBeanDefinition(candidateName);
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
!alreadyParsedClasses.contains(bd.getBeanClassName())) {//<12>
candidates.add(new BeanDefinitionHolder(bd, candidateName));
}
}
}
candidateNames = newCandidateNames;//<13>
}
}
while (!candidates.isEmpty());
……
}
……
}

  

至此,我们了解在执行ConfigurationClassPostProcessor.processConfigBeanDefinitions(...)的时候是如何完成扫描类路径,笔者再介绍在上面的方法是如何完成配置类的校验,如何执行配置类中暂存的ImportBeanDefinitionRegistrar实例引入新的BeanDefinition,就结束本章对ConfigurationClassPostProcessor的介绍。

首先我们来看配置类是如何完成校验的,在执行ConfigurationClassParser.validate()方法的时候会遍历已经获取到的所有配置类ConfigurationClass.validate(...)方法,在配置类校验的时候,会检查如果配置类是full模式(有标记@Configuration注解且proxyBeanMethods为true),这个full模式的配置类是否有使用fina修饰符,如果是full模式的配置类,那么类中是否有同时使用了@Bean注解和fina修饰符的方法?之所以做这样的校验:是因为一个full模式的配置类是要使用cglib技术代理的,如果配置类本身使用了final修饰符是无法被继承的,则不能通过校验。如果full模式的配置类有用@Bean注解标记的方法,方法本身也使用了final修饰符,这个方法是不能被重写的,就不允许通过校验。

但如果大家将@Configuration、@Bean和final修饰符搭配使用,会发现java会出现编译错误,既然连编译这关都过不了,为何还要校验呢?spring这里是防止编译出来的字节码被篡改,有人特意在编译后的full模式配置类字节码加上final修饰符,在在这个方法里再次对配置类进行校验。

class ConfigurationClassParser {
……
public void validate() {
for (ConfigurationClass configClass : this.configurationClasses.keySet()) {
configClass.validate(this.problemReporter);
}
}
……
} final class ConfigurationClass {
……
public void validate(ProblemReporter problemReporter) {
// A configuration class may not be final (CGLIB limitation) unless it declares proxyBeanMethods=false
Map<String, Object> attributes = this.metadata.getAnnotationAttributes(Configuration.class.getName());
if (attributes != null && (Boolean) attributes.get("proxyBeanMethods")) {
if (this.metadata.isFinal()) {//校验full模式的配置类是否使用final修饰符
problemReporter.error(new FinalConfigurationProblem());
}
for (BeanMethod beanMethod : this.beanMethods) {//校验full模式配置类的方法
beanMethod.validate(problemReporter);
}
}
}
……
} final class BeanMethod extends ConfigurationMethod {
……
public void validate(ProblemReporter problemReporter) {
if (getMetadata().isStatic()) {
// static @Bean methods have no constraints to validate -> return immediately
return;
} if (this.configurationClass.getMetadata().isAnnotated(Configuration.class.getName())) {
if (!getMetadata().isOverridable()) {//校验方法是否允许被重写
// instance @Bean methods within @Configuration classes must be overridable to accommodate CGLIB
problemReporter.error(new NonOverridableMethodError());
}
}
}
……
}

  

最后,我们来了解下ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(...)是如何在解析完配置类后,又从配置类加载新的BeanDefinition到spring容器里。这个方法会调用loadBeanDefinitionsForConfigurationClass(...)方法遍历传入的配置类,在代码<1>处会遍历配置类中加了@Bean注解的方法,这里会调用loadBeanDefinitionsForBeanMethod(...)将@Bean方法包装成一个BeanDefinition注册进spring容器,因为我们知道spring会根据@Bean方法实例化一个bean对象,而BeanDefinition是生产bean对象的原料。

在根据@Bean方法生成BeanDefinition时,会先在<2>处获取方法名,然后在<3>处获取bean对象的别名,并在<4>处建立beanName和别名的映射。之后会在<5>处根据配置类、元数据和方法名创建一个ConfigurationClassBeanDefinition对象,创建bean对象的方法是否是静态决定了控制流是进入<6>处还是<7>处的分支。如果是用静态方法来创建bean对象,在<6>处就设置静态方法对应的类以及方法名。如果是实例方法创建bean对象则进入<7>处的分支,在<7>处的分支会设置可以创建当前bean对象的工厂beanName,即配置类的beanName,再设置创建bean对象的方法名。

在<8>处会设置@Bean方法的BeanDefinition的默认自动装配模型为AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR,但@Bean注解默认的自动装配模型为Autowire.NO,在<9>处会获取@Bean注解配置的自动装配模型并存放到BeanDefinition。在<10>处会获取@Bean注解设置的是否自动参与候选,默认为true。之后在<11>处获取初始化方法,在<12>处获取销毁方法,如果这两个字段有设置的话。最后在<13>处将beanName和BeanDefinition的映射注册进spring容器中。这里我们又看到一个BeanDefinition的实现类型。

在<1>处的方法遍历完所有的beanMethod将其BeanDefinition注册进spring容器后,会分别在<14>和<15>处处理暂存在配置类的XML配置文件和ImportBeanDefinitionRegistrar实例。在<15>处loadBeanDefinitionsFromRegistrars(...)方法的实现我们也看到在这个方法内会遍历配置类中所有的ImportBeanDefinitionRegistrar实例,回调其registerBeanDefinitions(...)方法,在这个方法中开发者可以向spring容器注册新的BeanDefinition。

class ConfigurationClassBeanDefinitionReader {
……
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
for (ConfigurationClass configClass : configurationModel) {
loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
}
}
private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
……
for (BeanMethod beanMethod : configClass.getBeanMethods()) {//<1>
loadBeanDefinitionsForBeanMethod(beanMethod);
} loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());//<14>
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());//<15>
}
……
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
ConfigurationClass configClass = beanMethod.getConfigurationClass();
MethodMetadata metadata = beanMethod.getMetadata();
String methodName = metadata.getMethodName();//<2>
……
AnnotationAttributes bean = AnnotationConfigUtils.attributesFor(metadata, Bean.class);
Assert.state(bean != null, "No @Bean annotation attributes"); // Consider name and any aliases
List<String> names = new ArrayList<>(Arrays.asList(bean.getStringArray("name")));//<3>
String beanName = (!names.isEmpty() ? names.remove(0) : methodName); // Register aliases even when overridden
for (String alias : names) {//<4>
this.registry.registerAlias(beanName, alias);
} ……
ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata, beanName);//<5>
beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource())); if (metadata.isStatic()) {//<6>
// static @Bean method
if (configClass.getMetadata() instanceof StandardAnnotationMetadata) {
beanDef.setBeanClass(((StandardAnnotationMetadata) configClass.getMetadata()).getIntrospectedClass());
}
else {
beanDef.setBeanClassName(configClass.getMetadata().getClassName());
}
beanDef.setUniqueFactoryMethodName(methodName);
}
else {//<7>
// instance @Bean method
beanDef.setFactoryBeanName(configClass.getBeanName());
beanDef.setUniqueFactoryMethodName(methodName);
} if (metadata instanceof StandardMethodMetadata) {
beanDef.setResolvedFactoryMethod(((StandardMethodMetadata) metadata).getIntrospectedMethod());
} beanDef.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);//<8>
beanDef.setAttribute(org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor.
SKIP_REQUIRED_CHECK_ATTRIBUTE, Boolean.TRUE); AnnotationConfigUtils.processCommonDefinitionAnnotations(beanDef, metadata); Autowire autowire = bean.getEnum("autowire");//<9>
if (autowire.isAutowire()) {
beanDef.setAutowireMode(autowire.value());
} boolean autowireCandidate = bean.getBoolean("autowireCandidate");//<10>
if (!autowireCandidate) {
beanDef.setAutowireCandidate(false);
} String initMethodName = bean.getString("initMethod");//<11>
if (StringUtils.hasText(initMethodName)) {
beanDef.setInitMethodName(initMethodName);
} String destroyMethodName = bean.getString("destroyMethod");//<12>
beanDef.setDestroyMethodName(destroyMethodName);
……
BeanDefinition beanDefToRegister = beanDef;
……
this.registry.registerBeanDefinition(beanName, beanDefToRegister);//<13>
}
……
private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
registrars.forEach((registrar, metadata) ->
registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator));
}
……
}

  

Spring源码解析之ConfigurationClassPostProcessor(三)的更多相关文章

  1. Spring源码解析之ConfigurationClassPostProcessor(二)

    上一个章节,笔者向大家介绍了spring是如何来过滤配置类的,下面我们来看看在过滤出配置类后,spring是如何来解析配置类的.首先过滤出来的配置类会存放在configCandidates列表, 在代 ...

  2. Spring源码解析之ConfigurationClassPostProcessor(一)

    ConfigurationClassPostProcessor 在前面一个章节,笔者和大家介绍了在构造一个应用上下文时,spring会执行到PostProcessorRegistrationDeleg ...

  3. Spring源码解析之BeanFactoryPostProcessor(三)

    在上一章中笔者介绍了refresh()的<1>处是如何获取beanFactory对象,下面我们要来学习refresh()方法的<2>处是如何调用invokeBeanFactor ...

  4. Spring源码解析 - AbstractBeanFactory 实现接口与父类分析

    我们先来看类图吧: 除了BeanFactory这一支的接口,AbstractBeanFactory主要实现了AliasRegistry和SingletonBeanRegistry接口. 这边主要提供了 ...

  5. spring 源码解析

    1. [文件] spring源码.txt ~ 15B     下载(167) ? 1 springн┤┬вио╬Ш: 2. [文件] spring源码分析之AOP.txt ~ 15KB     下载( ...

  6. Spring源码解析——循环依赖的解决方案

    一.前言 承接<Spring源码解析--创建bean>.<Spring源码解析--创建bean的实例>,我们今天接着聊聊,循环依赖的解决方案,即创建bean的ObjectFac ...

  7. Spring源码解析系列汇总

    相信我,你会收藏这篇文章的 本篇文章是这段时间撸出来的Spring源码解析系列文章的汇总,总共包含以下专题.喜欢的同学可以收藏起来以备不时之需 SpringIOC源码解析(上) 本篇文章搭建了IOC源 ...

  8. Spring源码解析之八finishBeanFactoryInitialization方法即初始化单例bean

    Spring源码解析之八finishBeanFactoryInitialization方法即初始化单例bean 七千字长文深刻解读,Spirng中是如何初始化单例bean的,和面试中最常问的Sprin ...

  9. bitcoin 源码解析 - 交易 Transaction(三) - Script

    bitcoin 源码解析 - 交易 Transaction(三) - Script 之前的章节已经比较粗略的解释了在Transaction体系当中的整体运作原理.接下来的章节会对这个体系进行分解,比较 ...

随机推荐

  1. YARN学习总结之环境搭建

    Yarn环境搭建(基于hadoop-2.6.0-cdh5.7.0 伪分布) 1)配置文件 etc/hadoop/mapred-site.xml: <configuration> <p ...

  2. xampp的Apache服务无法启动 Apache的443端口被占用解决方法

    今天在使用本地的XAMPP的时候,发现Apache服务不能正常启动,根据以往的经验,可能是80端口或者443端口被占用导致的,所以对端口占用情况进行排查. 1. 执行xampp/apache/bin中 ...

  3. 《手把手教你》系列技巧篇(十)-java+ selenium自动化测试-元素定位大法之By class name(详细教程)

    1.简介 按宏哥计划,本文继续介绍WebDriver关于元素定位大法,这篇介绍By ClassName.看到ID,NAME这些方法的讲解,小伙伴们和童鞋们应该知道,要做好Web自动化测试,最好是需要了 ...

  4. NOI Online 提高组 题解

    来补坑了-- 个人认为三道题难度差不多-- 还有要说一嘴,为啥我在其他网站代码都好好的,复制到 cnblogs 上 Tab 就成 8 空格了?不过也懒得改了. T1 序列 首先,遇到这种加一减一还带附 ...

  5. java 日期字符串互相转换

    一.把日期转换成字符串 //获取当前时间  Date date = new Date();   //打印date数据类型  System.out.println(date.getClass().get ...

  6. 流畅的python-2

    一.python数据结构  ----------------> ()      []          {} List 列表[] 可变的 lst = [1,2,3,4] #改 lst[(元素下标 ...

  7. vant vue 屏幕自适应

    手机端 pc端 屏幕自适应 一.新建 vue.config.js项目目录中没有 vue.config.js 文件,需要手动创建,在根目录中创建 vue.config.js const pxtorem ...

  8. SQL语句(五)子查询

    目录 一.子查询含义 二.子查询分类 按子查询的位置分 按结果集的行列数不同分 三.WHERE后面的子查询 1. 标量子查询 2. 列子查询(多行子查询) 3. 行子查询(结果为一行多列或多行多列) ...

  9. scrapy 错误:Missing scheme in request url: %s' % self._url

    先说报错原因:使用了和start_urls同名的参数 我通过scral crawl projename -a start_urls=http:example.com来传start_urls,然后想在项 ...

  10. Bypass D盾 Webshell

    测试环境 OS: Windows Server 2008 PHP: PHP 7.2.10 D盾: d_safe_2.1.5.4 绕过分析 我是以 eval($_POST['x']); 这个代码以根据, ...