在上一章笔者介绍了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. .net core番外第2篇:Autofac的3种依赖注入方式(构造函数注入、属性注入和方法注入),以及在过滤器里面实现依赖注入

    本篇文章接前一篇,建议可以先看前篇文章,再看本文,会有更好的效果. 前一篇跳转链接:https://www.cnblogs.com/weskynet/p/15046999.html 正文: Autof ...

  2. 手把手教你用java实现二分查找树及其相关操作

    二分查找树(Binary Search Tree)的基本操作有搜索.求最大值.求最小值.求前继.求后继.插入及删除. 对二分查找树的进行基本操作所花费的时间与树的高度成比例.例如有n个节点的完全二叉树 ...

  3. Java 加载、操作和保存WPS文字文档

    本文通过Java程序代码来展示如何来加载.操作及保存WPS格式的文字文档. 一.基本步骤:加载时,通过流加载WPS文字文档,完成相关文字操作后,再将结果文档保存到流,将流写入WPS文档,闭关闭流. 二 ...

  4. CSS设置height为100%无效的情况

    CSS设置height为100%无效的情况 笔者是小白,不是特别懂前端.今天写一个静态的HTML页面,然后想要一个div占据页面的100%,但是尝试了很多办法都没有实现,不知道什么原因. 后来取百度搜 ...

  5. 开源协同办公平台部署教程:O2OA PAAS平台部署

    一.镜像制作1.将安装介质o2server-5.0.3-linux.zip上传至镜像制作服务器上.(上传目录为/paas/xxhpaas/moka/o2oa)2.使用unzip命令解压安装包,参考命令 ...

  6. Redis双写一致性与缓存更新策略

    一.双写一致性 双写一致性,也就是说 Redis 和 mysql 数据同步 双写一致性数据同步的方案有: 1.先更新数据库,再更新缓存 这个方案一般不用: 因为当有两个请求AB先后更新数据库后,A应该 ...

  7. C++第四十八篇 -- 字符串分离方法

    举例:Test_Bluetooth.exe -param_split Test_Bluetooth.cpp #include "pch.h" #include <iostre ...

  8. python 处理protobuf 接口常见错误

    python 处理protobuf 接口常见错误 1.问题 : Assignment not allowed to repeated field '> http://www.coin163.co ...

  9. 【动画消消乐 】HTML+CSS 吃豆豆动画 073

    前言 Hello!小伙伴! 非常感谢您阅读海轰的文章,倘若文中有错误的地方,欢迎您指出-   自我介绍 ଘ(੭ˊᵕˋ)੭ 昵称:海轰 标签:程序猿|C++选手|学生 简介:因C语言结识编程,随后转入计 ...

  10. root密码找回

    1,启动系统时,按上下键,选择第一项,按e. 2,编辑kernel中,将rhgb quiet 替换作init=/bin/sh.回车确认修改 3,根据提示按b键继续启动. 4,进入bash窗口并有管理员 ...