Spring思维导图
SpringBean加载流程
SpringIOC加载过程-invokeBeanFactoryPostProcessors

SpringIOC 容器加载过程

第一步:实例化化容器:AnnotationConfigApplicationContext

@Configuration
@ComponentScan("cn.zhe")
public class MainStartTest {public static void main(String[] args) {// SpringIOC 出发点 加载Spring上下文AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainStartTest.class);HelloSpring bean = applicationContext.getBean(HelloSpring.class);bean.sayHello();}
}


构造函数

// 根据参数可知,可以传入多个Class,但这种情况及其少见
public AnnotationConfigApplicationContext(Class<?>... componentClasses) {/*** 调用无参构造函数* 主要分三步:*    a.调用父类构造函数*    b.本类构造函数 初始化注解模式下的 bean定义扫描器*    c.本类构造函数 初始化 classPath类型的 bean定义扫描器*/this();/*** 注册配置类* 把传入的类进行注册,分为两种情况*     a. @Configuration的配置类*     b. 传入普通 Bean (基本不会这么做)*    Spring把配置类分为两种*     a. 带@Configuration注解的配置类称之为FULL配置类*     b. 不带@Configuration注解,是带有@Component,@Import,@ImportResouce,*        @Service, @ComponentScan等注解的配置类称之为Lite配置类*/register(componentClasses);// 刷新IOC容器refresh();
}

this()方法分析开始

第二步:实例化工厂:DefaultListableBeanFactory

DefaultListableBeanFactory 就是我们所说的容器,里面放着beanDefinitionMap, beanDefinitionNames等

// 调用无参构造,会先调用父类GenericApplicationContext的构造函数
// 第一步调用父类构造函数,创建一个Bean工厂
public GenericApplicationContext() {/*** 调用父类的构造函数,为 ApplicationContext spring 上下文对象初始 beanFactory* 因为 DefaultListableBeanFactory 是最底层的实现,功能是最全的*/this.beanFactory = new DefaultListableBeanFactory();
}// 第二三步public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {// 注解bean定义读取器,主要作用是用来读取被注解的Beanprivate final AnnotatedBeanDefinitionReader reader;// 外部调用scan手动扫描的scanner对象,用处不大private final ClassPathBeanDefinitionScanner scanner;public AnnotationConfigApplicationContext() {/*** 初始化注解模式下的bean定义扫描器* 调用AnnotatedBeanDefinitionReader构造方法,传入的* 是this(AnnotationConfigApplicationContext)对象*/this.reader = new AnnotatedBeanDefinitionReader(this);/*** 初始化我们的classPath类型的bean定义扫描器* 使用场景极少,仅外部手动调用扫描使用,常规方式是不会用到scanner对象的* 此处扫描器仅用于自定义的扫描 applicationContext.scan();*/this.scanner = new ClassPathBeanDefinitionScanner(this);}}

第三步:实例化 BeanDefinition 读取器 (AnnotatedBeanDefinitionReader)

主要就做了两件事情:

  • 注册内置 BeanPostProcessor
  • 注册内置相关核心的 BeanDefinition
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment {Assert.notNull(registry, "BeanDefinitionRegistry must not be null");Assert.notNull(environment, "Environment must not be null");// 把ApplicationContext对象赋值给AnnotatedBeanDefinitionReaderthis.registry = registry;// 用户处理条件表达式计算 @Conditionthis.conditionEvaluator = new ConditionEvaluator(registry, environment, null);// 注册一些配置的后置处理器,并注册Spring内置的多个BeanAnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}

关于registerAnnotationConfigProcessors(this.registry);方法内容较多,但大多相同的判断,注册 Spring 内置的多个 Bean,以ConfigurationClassPostProcesso为例:

/*** 为容器中注册解析配置类的后置处理器 ConfigurationClassPostProcessor* org.springframework.context.annotation.internalConfigurationAnnotationProcessor*/
if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);def.setSource(source);beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
}/*** 这方法为BeanDefinition设置了一个Role,ROLE_INFRASTRUCTURE代表这是spring内部的,并非用户定义的* registry.registerBeanDefinition(beanName, definition);是一个接口方法,* 实现类是 DefaultListableBeanFactory*    核心工作就是*    a.this.beanDefinitionMap.put(beanName, beanDefinition);*      把beanName作为key,beanDefinition作为value,放到map里面*    b.beanDefinitionNames就是一个List<String>,这里就是把beanName放到List中去* DefaultListableBeanFactory就是我们所说的容器,里面放着beanDefinitionMap, beanDefinitionNames,*      beanDefinitionMap是一个hashMap,beanName作为Key,beanDefinition作为Value,*      beanDefinitionNames是一个集合,里面存放了beanName*/
private static BeanDefinitionHolder registerPostProcessor(BeanDefinitionRegistry registry, RootBeanDefinition definition, String beanName) {definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);// 核心已在上方注释registry.registerBeanDefinition(beanName, definition);return new BeanDefinitionHolder(definition, beanName);
}

逻辑就是:

  1. 判断容器中是否已经存在了ConfigurationClassPostProcessorBean
  2. 如果不存在,就通过 RootBeanDefinition 的构造方法获得ConfigurationClassPostProcessor的BeanDefinition
  3. 执行registerPostProcessor()方法,其内部就是注册Bean(与其他 Bean 注册流程一致)

internalConfigurationAnnotationProcessor

ConfigurationClassPostProcessor 是 Spring 中极其重要的一个类,它实现 BeanDefinitionRegistryPostProcessor 接口,BeanDefinitionRegistryPostProcessor 接口又扩展了 BeanFactoryPostProcessor 接口,BeanFactoryPostProcessor 是 Spring 的扩展点之一

至此加载完以下扩展点(beanDefinition -> beanDefinitionMap)


第四步:创建 BeanDefinition 扫描器 (ClassPathBeanDefinitionScanner)

初始化 classPath 类型的 BeanDefinition 扫描器,使用场景极少,仅外部手动调用扫描使用,常规方式是不会用到 scanner 对象的此处扫描器仅用于自定义的扫描applicationContext.scan()

至此this()方法结束


register(annotatedClasses);分析开始

第五步:注册配置类为BeanDefinition (register(annotatedClasses); )

/*** 注册配置类* 把传入的类进行注册,分为两种情况*     a. @Configuration的配置类*     b. 传入普通 Bean (基本不会这么做)* Spring把配置类分为两种*     a. 带@Configuration注解的配置类称之为FULL配置类*     b. 不带@Configuration注解,是带有@Component,@Import,@ImportResouce,*        @Service, @ComponentScan等注解的配置类称之为Lite配置类*/
register(componentClasses);public void register(Class<?>... componentClasses) {this.reader.register(componentClasses);
}
private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,@Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,@Nullable BeanDefinitionCustomizer[] customizers) {// AnnotatedGenericBeanDefinition可以理解为一种数据结构,是用来描述Bean的,这里的作用就是把传入// 的标记了注解的类转为AnnotatedGenericBeanDefinition数据结构,里面有一个getMetadata方法,可以拿到类上的注解AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);// 判断是否需要跳过注解,spring中有一个@Condition注解,当不满足条件,这个bean就不会被解析if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {return;}abd.setInstanceSupplier(supplier);// 解析bean的作用域,如果没有设置的话,默认为单例ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);abd.setScope(scopeMetadata.getScopeName());// 获得beanNameString beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));// 解析通用注解,填充到AnnotatedGenericBeanDefinition,// 解析的注解 Lazy,Primary,DependsOn,Role,DescriptionAnnotationConfigUtils.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));}}}if (customizers != null) {for (BeanDefinitionCustomizer customizer : customizers) {customizer.customize(abd);}}// 这个方法用处不大,就是把AnnotatedGenericBeanDefinition数据结构和beanName封装到一个对象中BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);// 注册,最终会调用DefaultListableBeanFactory中的registerBeanDefinition方法去注册// DefaultListableBeanFactory维护着一系列信息,比如beanDefinitionNames,beanDefinitionMap// beanDefinitionMap是一个Map,用来保存beanName和beanDefinitionBeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}
  1. 通过AnnotatedGenericBeanDefinition的构造方法,获得配置类的BeanDefinition
  2. 判断需不需要跳过注册,Spring中有一个@Condition注解,如果不满足条件,就会跳过这个类的注册
  3. 然后是解析作用域,如果没有设置的话,默认为单例
  4. 获得BeanName
  5. 解析通用注解,填充到 AnnotatedGenericBeanDefinition,解析的注解为Lazy,Primary,DependsOn,Role,Description
  6. 限定符处理,不是特指@Qualifier注解,也有可能是Primary,或者是Lazy,或者是其他(理论上是任何注解,这里没有判断注解的有效性)
  7. 把AnnotatedGenericBeanDefinition数据结构和beanName封装到一个对象中(不重要,方便传参)
  8. 注册,最终会调用 DefaultListableBeanFactory 中的 registerBeanDefinition() 方法

至此,注册配置类加载结束,配置类(MainStartTest,标记了@Configuration的类) 被放入 BeanDefinitionMap 中未实例化。(实例化都在reflesh()方法中进行)

至此register()方法结束,将我们传入的配置类加载完毕即:mainStartTest


refresh() 方法分析开始

第六步:refresh();

到这一步 Spring 还没有进行扫描,只是实例化了一个工厂,注册了一些内置的 Bean 和 配置类,这一行是至关重要的一个方法,也是内容最多的,里面做了大量的处理。

@Override
public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// 1:准备刷新上下文环境// 刷新预处理,和主流程关系不大,就是保存了容器的启动时间,启动标志等prepareRefresh();//2:告诉子类初始化Bean工厂(MVC),获取Bean工厂// 和主流程关系也不大,最终获得了DefaultListableBeanFactoryConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// 3:对Bean工厂进行填充属性/*** ①添加了两个后置处理器:*    a.ApplicationContextAwareProcessor*    b.ApplicationListenerDetector* ②设置忽略自动装配和允许自动装配的接口,如果不存在某个bean的时候,*  spring就自动注册singleton bean* ③ 设置了bean表达式解析器*/prepareBeanFactory(beanFactory);try {// 4:空方法 留给子类去实现该接口 允许在上下文子类中对Bean工厂进行后置处理。postProcessBeanFactory(beanFactory);// 5:调用Bean工厂的后置处理器.// 执行自定义的BeanFactoryPostProcessor和内置的BeanFactoryPostProcessorinvokeBeanFactoryPostProcessors(beanFactory);// 6:注册BeanPostProcessorsregisterBeanPostProcessors(beanFactory);// 7:初始化国际化资源处理器.initMessageSource();// 8:创建事件多播器initApplicationEventMulticaster();// 9:这个方法同样也是留个子类实现的springboot也是从这个方法进行启动tomcat的.// 模板方法,在容器刷新的时候可以自定义逻辑,不同的Spring容器做不同的事情onRefresh();// 10:将事件监听器注册到多播器上registerListeners();// 11:实例化懒加载单例Bean的,也就是Bean绝大部分都是在这里被创建出来的finishBeanFactoryInitialization(beanFactory);// 12:最后容器刷新 发布刷新事件(Spring cloud也是从这里启动的)finishRefresh();}catch (BeansException ex) {if (logger.isWarnEnabled()) {logger.warn("Exception encountered during context initialization - " +"cancelling refresh attempt: " + ex);}// Destroy already created singletons to avoid dangling resources.destroyBeans();// Reset 'active' flag.cancelRefresh(ex);throw ex;}finally {// 清除元数据缓冲,实例化后就不需要了resetCommonCaches();}}
}

下面来逐一解析里面比较重要的几个方法,有些不重要的就仅在上面注释了

6.1 prepareBeanFactory(beanFactory)

顾名思义,BeanFactory的一些准备工作

  1. 设置了一个类加载器
  2. 设置了bean表达式解析器
  3. 添加了属性编辑器的支持
  4. 添加了一个后置处理器:ApplicationContextAwareProcessor,此后置处理器实现了BeanPostProcessor接口
  5. 设置了一些忽略自动装配的接口
  6. 设置了一些允许自动装配的接口,并且进行了赋值操作
  7. 在容器中还没有XX的 bean 的时候,帮我们注册 beanName 为 XX 的 singleton bean

6.2 invokeBeanFactoryPostProcessors(beanFactory)

首先看一下我们到这一步时 BeanDefinitionMap 里面 bean 定义的情况:

/*** 调用Bean工厂的后置处理器.* 执行自定义的 BeanFactoryPostProcessor 和内置的 BeanFactoryPostProcessor*/
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {// getBeanFactoryPostProcessors(),获得外部可以手动添加一个后置处理器,如果不添加获得的集合永远为空PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());// Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime// (例如通过ConfigurationClassPostProcessor注册的@Bean方法)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<BeanFactoryPostProcessor> beanFactoryPostProcessors) {// 第一步:首先调用BeanDefinitionRegistryPostProcessor的后置处理器// 装beanName 后续会根据这个集合来判断处理器是否已经被执行过了Set<String> processedBeans = new HashSet<>();if (beanFactory instanceof BeanDefinitionRegistry) {// 强行把bean工厂转为BeanDefinitionRegistryBeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;// 保存BeanFactoryPostProcessor类型的后置List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();// 循环传递进来的 beanFactoryPostProcessors,正常情况为数据,只有手动添加了后置处理器才会有数据for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {// 判断后置处理器是不是 BeanDefinitionRegistryPostProcessor// 因为BeanDefinitionRegistryPostProcessor扩展了BeanFactoryPostProcessorif (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {// 进行强制转换BeanDefinitionRegistryPostProcessor registryProcessor =(BeanDefinitionRegistryPostProcessor) postProcessor;// 调用作为BeanDefinitionRegistryPostProcessor的处理器的后置方法registryProcessor.postProcessBeanDefinitionRegistry(registry);// 添加到用于保存的BeanDefinitionRegistryPostProcessor的集合中registryProcessors.add(registryProcessor);}// 若没有实现BeanDefinitionRegistryPostProcessor 接口,那么它就是BeanFactoryPostProcessor// 把当前的后置处理器加入到regularPostProcessors中else {regularPostProcessors.add(postProcessor);}}// 定义一个集合用户保存当前准备创建的BeanDefinitionRegistryPostProcessorList<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();// 第一步:去容器中获取BeanDefinitionRegistryPostProcessor的bean的处理器名称// internalConfigurationAnnotationProcessor即ConfigurationAnnotationProcessorString[] postProcessorNames =beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);// 循环上一步获取的BeanDefinitionRegistryPostProcessor的类型名称for (String ppName : postProcessorNames) {// 判断是否实现了PriorityOrdered接口的if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {// 显示的调用getBean()的方式获取出该对象然后加入到currentRegistryProcessors集合中去currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));// 同时也加入到processedBeans集合中去// 后续会根据这个集合来判断处理器是否已经被执行过了processedBeans.add(ppName);}}// 对currentRegistryProcessors集合中BeanDefinitionRegistryPostProcessor进行排序sortPostProcessors(currentRegistryProcessors, beanFactory);// 把他加入到用于保存到registryProcessors中// 为什么要合并,因为registryProcessors是装载BeanDefinitionRegistryPostProcessor的// 一开始的时候,spring只会执行BeanDefinitionRegistryPostProcessor独有的方法// 而不会执行BeanDefinitionRegistryPostProcessor父类的方法,即BeanFactoryProcessor的方法// 所以这里需要把处理器放入一个集合中,后续统一执行父类的方法registryProcessors.addAll(currentRegistryProcessors);/*** 在这里典型的BeanDefinitionRegistryPostProcessor就是* ConfigurationClassPostProcessor* 用于进行bean定义的加载 比如我们的包扫描,@import 等等*/// Spring热插播的体现,像ConfigurationClassPostProcessor就相当于一个组件// Spring很多事情就是交给组件去管理,如果不想用这个组件,直接去掉注册组件就行invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);// 调用完之后,马上clear掉,临时变量需要清除// list.clear()只清除对象的引用使其变为垃圾,与list = null 集合也会置空currentRegistryProcessors.clear();// 接下来,去容器中获取BeanDefinitionRegistryPostProcessor的bean的处理器名称postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);// 循环上一步获取的BeanDefinitionRegistryPostProcessor的类型名称for (String ppName : postProcessorNames) {// 没有被处理过,且实现了Ordered接口的if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {// 显示的调用getBean()的方式获取出该对象然后加入到currentRegistryProcessors集合中去currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));// 同时也加入到processedBeans集合中去processedBeans.add(ppName);}}// 对currentRegistryProcessors集合中BeanDefinitionRegistryPostProcessor进行排序sortPostProcessors(currentRegistryProcessors, beanFactory);// 把他加入到用于保存到registryProcessors中registryProcessors.addAll(currentRegistryProcessors);// 调用他的后置处理方法invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);// 调用完之后,马上clear掉currentRegistryProcessors.clear();// 调用没有实现任何优先级接口的BeanDefinitionRegistryPostProcessor// 定义一个重复处理的开关变量 默认值为true// Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear.boolean reiterate = true;// 第一次就可以进来while (reiterate) {// 进入循环马上把开关变量给改为faslereiterate = false;// 去容器中获取BeanDefinitionRegistryPostProcessor的bean的处理器名称// 根据类型查 beanName 一般情况下只会获取到一个
org.springframework.context.annotation.internalConfigurationAnnotationProcessor,也就是 ConfigurationAnnotationProcessorpostProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);// 循环上一步获取的BeanDefinitionRegistryPostProcessor的类型名称for (String ppName : postProcessorNames) {// 没有被处理过的if (!processedBeans.contains(ppName)) {// 显示的调用getBean()的方式获取出该对象然后加入到currentRegistryProcessors集合中去currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));// 同时也加入到processedBeans集合中去processedBeans.add(ppName);// 再次设置为truereiterate = true;}}// 对currentRegistryProcessors集合中BeanDefinitionRegistryPostProcessor进行排序sortPostProcessors(currentRegistryProcessors, beanFactory);// 把他加入到用于保存到registryProcessors中registryProcessors.addAll(currentRegistryProcessors);// 调用他的后置处理方法invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);// 调用完之后,马上clear掉currentRegistryProcessors.clear();}// 调用实现了BeanDefinitionRegistryPostProcessor的接口 他是他也同时实现了BeanFactoryPostProcessor的方法invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);// 调用BeanFactoryPostProcessor成品的不是通过getBean的invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);}else {// 若当前的beanFactory没有实现了BeanDefinitionRegistry 直接调用// 直接调用 beanFactoryPostProcessor 接口的方法进行后置处理invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);}// 获取容器中所有的 BeanFactoryPostProcessorString[] postProcessorNames =beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);// 保存BeanFactoryPostProcessor类型实现了priorityOrderedList<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();// 保存BeanFactoryPostProcessor类型实现了Ordered接口的List<String> orderedPostProcessorNames = new ArrayList<>();// 保存BeanFactoryPostProcessor没有实现任何优先级接口的List<String> nonOrderedPostProcessorNames = new ArrayList<>();for (String ppName : postProcessorNames) {// processedBeans包含的话,表示在上面处理BeanDefinitionRegistryPostProcessor的时候处理过了if (processedBeans.contains(ppName)) {// skip - already processed in first phase above}// 判断是否实现了PriorityOrderedelse if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));}// 判断是否实现了Orderedelse if (beanFactory.isTypeMatch(ppName, Ordered.class)) {orderedPostProcessorNames.add(ppName);}// 没有实现任何的优先级接口的else {nonOrderedPostProcessorNames.add(ppName);}}// 首先,先调用BeanFactoryPostProcessor实现了 PriorityOrdered接口的sortPostProcessors(priorityOrderedPostProcessors, beanFactory);invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);// 再调用BeanFactoryPostProcessor实现了 Ordered.List<BeanFactoryPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());for (String postProcessorName : orderedPostProcessorNames) {orderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));}sortPostProcessors(orderedPostProcessors, beanFactory);invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory);// 最后调用没有实现任何方法接口的List<BeanFactoryPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());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();
}

总结:

首先,之前已经了解过 BeanDefinition 的两个扩展点 postProcessBeanFactorypostProcessBeanDefinitionRegistry。前者是可以修改 BeanDefinition,重写方法,后者可以多添加 BeanDefinition

1、定义了一个 Set,processedBeans 装载BeanName,后面会根据此 Set 来判断后置处理器是否被执行过

2、判断当前的 beanFactory 有没有实现 BeanDefinitionRegistry,当然是肯定的,
定义了两个 List 一个是 regularPostProcessors,用来装载 BeanFactoryPostProcessor。它只有一个实现
方法postProcessBeanFactory()。一个是 registryProcessors, 用来装载 BeanDefinitionRegistryPostProcessor。因为
它继承了 BeanFactoryPostProcessor 它不仅有postProcessBeanFactory()还有postProcessBeanDefinitionRegistry()

3、循环传进来的beanFactoryPostProcessors,一般情况下都是空的,除非自己 add 了 beanFactory 的后置处理器。假设有数据,先判断是否是BeanDefinitionRegistryPostProcessor如果是调用postProcessBeanDefinitionRegistry()方法,并添加到集合 registryProcessors 中,否的话直接加入到集合 regularPostProcessors。(postProcessBeanFactory()会在后面执行,先存起来)

4、 定义一个集合(List 临时变量) currentRegistryProcessors用户保存当前准备创建的 BeanDefinitionRegistryPostProcessor

5、去容器中获取BeanDefinitionRegistryPostProcessor的bean的处理器名称internalConfigurationAnnotationProcessorConfigurationAnnotationProcessor一般情况都只会获取到一个。此时Spring还未扫描完成,扫描是在ConfigurationClassPostProcessor类完成的,就是下面的第一个invokeBeanDefinitionRegistryPostProcessors()方法

6、循环 postProcessorNames 即internalConfigurationAnnotationProcessor判断是否实现了 PriorityOrdered,实现了添加到currentRegistryProcessors和processedBeans表示它们被处理过了(下一步才处理)

7、对 currentRegistryProcessors 集合中 BeanDefinitionRegistryPostProcessor 进行排序

8、将currentRegistryProcessors集合加到registryProcessors集合中,因为registryProcessors是装载BeanDefinitionRegistryPostProcessor,一开始的时候,spring只会执行BeanDefinitionRegistryPostProcessor独有的方法postProcessBeanDefinitionRegistry()。而不会执行 BeanDefinitionRegistryPostProcessor 父类的方法,即 BeanFactoryProcessor 的方法postProcessBeanFactory()。所有在此统一放到一起等待后续执行

9、internalConfigurationAnnotationProcessor(currentRegistryProcessors, registry),执行currentRegistryProcessors中的ConfigurationClassPostProcessor中的postProcessBeanDefinitionRegistry()方法,这里体现了 Spring 中热插拔,插件化开发的思想,如果不想用这个,不添加就行了。从下图可以看到,执行完该方法后 bean定义 被加载到了BeanDefinitionMap中

10、清空currentRegistryProcessors,用完了就需要清空,给后面的其他的重复使用

11、最后会重复上面的逻辑,调用顺序如下:

  • 实现了PriorityOrdered接口的

  • 实现了Ordered接口的

  • 没有实现任何的优先级接口的

如果实现了多个的话,将在最先实现的地方调用,第二次将会判断是否已经处理过

再来看一下postProcessBeanFactory()方法,它调用了一个会解析我们BeanDefinition的方法processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {int factoryId = System.identityHashCode(beanFactory);if (this.factoriesPostProcessed.contains(factoryId)) {throw new IllegalStateException("postProcessBeanFactory already called on this post-processor against " + beanFactory);}this.factoriesPostProcessed.add(factoryId);if (!this.registriesPostProcessed.contains(factoryId)) {// BeanDefinitionRegistryPostProcessor hook apparently not supported...// Simply call processConfigurationClasses lazily at this point then.processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);}// 为属性为full的Bean定义做CGLIB增强enhanceConfigurationClasses(beanFactory);beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}

这个方法中会引申出一个知识点

注册配置类
把传入的类进行注册,分为两种情况

  • a. @Configuration的配置类
  • b. 传入普通 Bean (基本不会这么做)

Spring把配置类分为两种

  • a. 带@Configuration注解的配置类称之为FULL配置类
  • b. 不带@Configuration注解,而是带有@Component,@Import,@ImportResouce,
    @Service, @ComponentScan等注解的配置类称之为Lite配置类

如果我们注册了Full 配置类,我们 getBean 这个配置类,会发现它已经不是原本那个配置类了,而是已经被 CGLIB 代理的类

例如:写一个A类,其中有一个构造方法,打印出“HelloSpring”,再写一个配置类,里面有两个 带 @Bean 的方法。假设其中一个方法getA()new A(),并且返回A的对象。第二个方法又调用了getA()。如果配置类是 Lite 配置类,会发现打印了两次“HelloSpring”,即 A 类被 new 了两次。如果配置类是 FULL 配置类,会发现只打印一次,因为这个类被CGLIB代理了。

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {List<BeanDefinitionHolder> configCandidates = new ArrayList<>();// 获取IOC 容器中目前所有bean定义的名称String[] candidateNames = registry.getBeanDefinitionNames();// 循环上一步获取的所有的Bean定义信息for (String beanName : candidateNames) {// 通过Bean的名称来获取我们的bean定义对象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));}}// Return immediately if no @Configuration classes were found// 若没有找到配置类直接返回if (configCandidates.isEmpty()) {return;}// Sort by previously determined @Order value, if applicable// 对配置类进行Order排序configCandidates.sort((bd1, bd2) -> {int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());return Integer.compare(i1, i2);});// Detect any custom bean name generation strategy supplied through the enclosing application context// 创建我们通过@CompentScan导入进来的bean name的生成器// 创建我们通过@Import导入进来的bean的名称SingletonBeanRegistry sbr = null;if (registry instanceof SingletonBeanRegistry) {sbr = (SingletonBeanRegistry) registry;if (!this.localBeanNameGeneratorSet) {BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);if (generator != null) {// 设置@CompentScan导入进来的bean的名称生成器this.componentScanBeanNameGenerator = generator;// 设置@Import导入进来的bean的名称生成器this.importBeanNameGenerator = generator;}}}if (this.environment == null) {this.environment = new StandardEnvironment();}// 创建一个配置类解析器对象// Parse each @Configuration classConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment,this.resourceLoader, this.componentScanBeanNameGenerator, registry);// 创建一个集合用于保存我们的配置类BeanDefinitionHolder集合默认长度是配置类集合的长度Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);// 创建一个集合用于保存我们的已经解析的配置类,长度默认为解析出来默认的配置类的集合长度Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());//do while 会进行第一次解析do {// 解析配置类// 经过这一步,会将@ComponentScans、@ComponentScan、@Bean、@Import等注解要注册的类扫描出来parser.parse(candidates);parser.validate();// 解析出来的配置类Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());configClasses.removeAll(alreadyParsed);// Read the model and create bean definitions based on its contentif (this.reader == null) {this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.resourceLoader, this.environment,this.importBeanNameGenerator, parser.getImportRegistry());}// 把解析出来的配置类注册到容器中// 经过这一步会将@Bean、@import 注册的类变成BeanDefinitionthis.reader.loadBeanDefinitions(configClasses);// 加入到已经解析的集合中alreadyParsed.addAll(configClasses);candidates.clear();//判断我们IOC容器中的是不是>候选原始的bean定义的个数if (registry.getBeanDefinitionCount() > candidateNames.length) {// 获取所有的bean定义String[] newCandidateNames = registry.getBeanDefinitionNames();// 原始的老的候选的bean定义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) {// 表示当前循环的还没有被解析过if (!oldCandidateNames.contains(candidateName)) {BeanDefinition bd = registry.getBeanDefinition(candidateName);// 判断有没有被解析过// checkConfigurationClassCandidate 此时为Bean定义标识为full或lite,在后面根据属性潘森是否需要用CGLIB增强if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&!alreadyParsedClasses.contains(bd.getBeanClassName())) {candidates.add(new BeanDefinitionHolder(bd, candidateName));}}}candidateNames = newCandidateNames;}}// 存在没有解析过的 需要循环解析while (!candidates.isEmpty());// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classesif (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());}if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {// Clear cache in externally provided MetadataReaderFactory; this is a no-op// for a shared cache since it'll be cleared by the ApplicationContext.((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();}}

至此自定义的配置类都加载到了beanDefinitonMap 中,但仍未初始化

此处会实例化以下几个内置 Bean

6.3 registerBeanPostProcessors(beanFactory);

实例化和注册 beanFactory 中扩展了 BeanPostProcessor 的bean。

例如:

AutowiredAnnotationBeanPostProcessor(处理被@Autowired注解修饰的bean并注入)

RequiredAnnotationBeanPostProcessor(处理被@Required注解修饰的方法)

CommonAnnotationBeanPostProcessor(处理@PreDestroy、@PostConstruct、@Resource等多个注解的作用)等。

此处会实例化以下几个内置 Bean

6.4 initApplicationEventMulticaster(); 和 registerListeners();

创建事件多播器

注册监听器,广播early application events

后续监听机制再来看这两个

6.5 finishBeanFactoryInitialization(beanFactory);

实例化非懒加载单例 Bean ,也就是我们的 Bean 都是在这里被创建出来的。包括(实例化、填充属性、初始化)

里面会有一个方法preInstantiateSingletons()是一个接口方法,这个方法只有一个实现类DefaultListableBeanFactory,里面最重要的就是getBean();。这中间还会去判断是否是一个特殊的Bean(即:FactoryBean)一旦一个类实现了 FactoryBean 并从写了getObject() 方法那么,IOC容器拿到的实例就是调用getObject方法得到的特殊的实例,没有实现这个接口时注册到IOC容器中的就是一个普通的Bean,当实现后,IOC容器会调用getObject方法返回的实例(工厂模式)

// 初始化所有的非懒加载单例Bean
beanFactory.preInstantiateSingletons();
// 执行 getBean流程
getBean(beanName);

里面调用的是 AbstractBeanFactory.java 里面的getBean();

@Override
public Object getBean(String name) throws BeansException {return doGetBean(name, null, null, false);
}

doGetBean 方法太多了,这里挑出主要创建 单例bean 的逻辑

// 创建单例bean
if (mbd.isSingleton()) {// 把beanName 和一个 singletonFactory 并且传入一个回调对象用于回调sharedInstance = getSingleton(beanName, () -> {try {// 进入创建bean的逻辑return createBean(beanName, mbd, args);}catch (BeansException ex) {// 创建bean的过程中发生异常,需要销毁关于当前bean的所有信息destroySingleton(beanName);throw ex;}});bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

这里的creatBean()又是一个接口方法,但也仅仅只有一个类对其做了实现AbstractAutowireCapableBeanFactory。该方法前面也会进行一大堆的判断,我们再次挑出关键步骤

// 真正的开始创建Bean实例对象
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
if (logger.isTraceEnabled()) {logger.trace("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;

点进方法doCreateBean(beanName, mbdToUse, args);发现也在该类下面,里面又做了一大堆的事情,我们主要调出机构关键点

创建实例

// 使用合适的实例化策略来创建新的实例:工厂方法、构造函数自动注入、简单初始化
instanceWrapper = createBeanInstance(beanName, mbd, args);

填充属性及初始化

// 给我们的属性进行赋值(调用set方法进行赋值)
populateBean(beanName, mbd, instanceWrapper);
// 进行对象初始化操作(在这里可能生成代理对象)
exposedObject = initializeBean(beanName, exposedObject, mbd);

initializeBean(beanName, exposedObject, mbd);中,又调用了invokeInitMethods(beanName, wrappedBean, mbd);invokeAwareMethods(beanName, bean);

protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {if (System.getSecurityManager() != null) {AccessController.doPrivileged((PrivilegedAction<Object>) () -> {invokeAwareMethods(beanName, bean);return null;}, getAccessControlContext());}else {// 若我们的bean实现了XXXAware接口进行方法的回调invokeAwareMethods(beanName, bean);}Object wrappedBean = bean;if (mbd == null || !mbd.isSynthetic()) {// 调用我们的bean的后置处理器的postProcessorsBeforeInitialization方法  @PostCust注解的方法wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);}try {// 调用初始化方法invokeInitMethods(beanName, wrappedBean, mbd);}catch (Throwable ex) {throw new BeanCreationException((mbd != null ? mbd.getResourceDescription() : null),beanName, "Invocation of init method failed", ex);}if (mbd == null || !mbd.isSynthetic()) {// 调用我们bean的后置处理器的PostProcessorsAfterInitialization方法wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);}return wrappedBean;
}private void invokeAwareMethods(final String beanName, final Object bean) {if (bean instanceof Aware) {// 此bean实现了BeanNameAwareif (bean instanceof BeanNameAware) {((BeanNameAware) bean).setBeanName(beanName);}// 实现了BeanClassLoaderAware接口if (bean instanceof BeanClassLoaderAware) {ClassLoader bcl = getBeanClassLoader();if (bcl != null) {((BeanClassLoaderAware) bean).setBeanClassLoader(bcl);}}// 实现了BeanFactoryAwareif (bean instanceof BeanFactoryAware) {((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);}}
}

至此剩余的Bean也全部初始化完成至 IOC 容器中

验证Spring Bean 的生命周期

定义一个SpringBean

@ComponentScan
public  class SpringBeanimplements InitializingBean, DisposableBean, BeanNameAware, BeanFactoryAware, BeanClassLoaderAware {// 就是一个普通的被@Component标注的类@AutowiredAutoBean autoBean;public SpringBean() {System.out.println("SpringBean Constructor Method:" + autoBean);System.out.println("SpringBean()");}@Overridepublic void setBeanClassLoader(ClassLoader classLoader) {System.out.println("ClassLoader");}@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {System.out.println("setBeanFactory");}@Overridepublic void setBeanName(String name) {System.out.println("setBeanName:" + autoBean);System.out.println("setBeanName");}@Overridepublic void destroy() throws Exception {System.out.println("destroy");}@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("afterPropertiesSet");}public void initMethod() {System.out.println("initMethod");}public void destroyMethod() {System.out.println("destroyMethod");}
}

再定义一个BeanPostProcessor,在重写的两个方法中进行了判断,如果传进来的 beanName 是 springBean 才进行打印

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {if(beanName.equals("springBean")) {System.out.println("postProcessBeforeInitialization");}return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {if(beanName.equals("springBean")) {System.out.println("postProcessAfterInitialization");}return bean;}
}

定义一个配置类,完成自动扫描,但是SpringBean是手动注册的,并且声明了initMethod和destroyMethod:

@Configuration
@ComponentScan
public class MainConfig {@Bean(initMethod = "initMethod",destroyMethod = "destroyMethod")public SpringBean springBean() {return new SpringBean();}
}

然后是启动类:

public class Main {public static void main(String[] args) throws Exception {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);// Spring 的 destroy() 方法已过时会报错,推荐使用 registerShutdownHook() 优雅的关闭 IOC// context.registerShutdownHook() 是一个钩子方法,当jvm关闭退出的时候会调用这个钩子方法// 当然 context.close() 也可以销毁容器context.registerShutdownHook();}
}

运行结果:

由此我们可以推导出Spring的生命周期

  1. 实例化Bean对象,这个时候 Bean 的对象是非常低级的,基本不能够被我们使用,因为连最基本的属性都没有设置,可以理解为
    连Autowired注解都是没有解析的

  2. 填充属性,当做完这一步,Bean对象基本是完整的了,可以理解为Autowired注解已经解析完毕,依赖注入完成了

  3. 如果Bean实现了BeanNameAware接口,则调用setBeanName方法

  4. 如果Bean实现了BeanClassLoaderAware接口,则调用setBeanClassLoader方法

  5. 如果Bean实现了BeanFactoryAware接口,则调用setBeanFactory方法

  6. 调用BeanPostProcessor的postProcessBeforeInitialization方法

  7. 如果Bean实现了InitializingBean接口,调用afterPropertiesSet方法

  8. 如果Bean定义了init-method方法,则调用Bean的init-method方法

  9. 调用BeanPostProcessor的postProcessAfterInitialization方法。当进行到这一步,Bean已经被准备就绪了,一直停留在应用的上下文中,直到被销毁

  10. 如果应用的上下文被销毁了,如果Bean实现了DisposableBean接口,则调用destroy方法,如果Bean定义了destory-method声明了销毁方法也会被调用

思考

配置类@Configuration 加与不加的区别,加上会创建CGLIB动态代理,保证配置类中的Bean是受 IOC容器控制的,是单例的,如果配置类中重复的用某一个类,不加的话就是是重复调用方法,多次创建,不受IOC容器控制。

重复 beanName 覆盖原则,如果是通过 Scanner 扫描到的同一包下两个相同的 beanName 会抛异常,如果一个是 @Compontent 扫描的 bean,一个是通过 @Bean 配置的 bean,那么 @Bean 配置的 bean 会覆盖前面的 bean,因为它是后创建的。

02.IOC容器加载过程及Bean的生命周期和后置处理器相关推荐

  1. IOC容器加载过程及Bean的生命周期和后置处理器

    SpringIOC 容器加载过程 第一步:实例化化容器:AnnotationConfigApplicationContext @Configuration @ComponentScan("c ...

  2. 框架源码专题:springIOC的加载过程,bean的生命周期,结合spring源码分析

    文章目录 1.BeanFactory和ApplicationContext的区别? 2. IOC与 Bean的加载过程 ①:初始化容器DefaultListableBeanFactory ②:创建读取 ...

  3. Spring学习笔记八--Bean生命周期和后置处理器

    为什么80%的码农都做不了架构师?>>>    Bean生命周期和后置处理器 IOC容器的bean生命周期 1.构造器或工厂方法建立bean实例 2.bean属性赋值,引用其他bea ...

  4. initializeBean()方法为容器产生的Bean 实例对象添加BeanPostProcessor 后置处理器

    同样在AbstractAutowireCapableBeanFactory 类中,initializeBean()方法实现为容器创建的Bean实例对象添加BeanPostProcessor 后置处理器 ...

  5. Spring Ioc 之 Bean的加载(1)(生命周期)

    在之前的文章中,我们分析了Spring的Ioc的初始化过程,实际上就是把 beanName 和 BeanDefinition 注册到DefaultListableBeanFactory的map中. 在 ...

  6. 欧尼酱讲JVM(02)——类的加载过程

    我们知道,在代码编译后,就会生成JVM(Java虚拟机)能够识别的二进制字节流文件(*.class).而JVM把Class文件中的类描述数据从文件加载到内存,并对数据进行校验.转换解析.初始化,使这些 ...

  7. Spring中bean的生命周期(易懂版)

    bean的生命周期 写在前面的话 bean的生命周期 代码演示 bean的更完整的生命周期 添加后置处理器的代码演示 写在前面的话 关于bean的生命周期有很多的文章,但是大多数都是长篇的理论,说来说 ...

  8. spring bean加载过程_Spring源码剖析3:Spring IOC容器的加载过程

    本文转自五月的仓颉 https://www.cnblogs.com/xrq730 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https ...

  9. 面试官:讲讲Spring框架Bean的加载过程

    spring作为目前我们开发的基础框架,每天的开发工作基本和他形影不离,作为管理bean的最经典.优秀的框架,它的复杂程度往往令人望而却步. 不过作为朝夕相处的框架,我们必须得明白一个问题就是spri ...

最新文章

  1. 工艺路线和工序有差别吗_智能制造、数字化车间、数字化企业需要结构化工艺吗?...
  2. Tomcat性能优化总结
  3. CSDN开发者周刊 TDengine:专为物联网订制的大数据平台 YugaByte DB:高性能的分布式ACID事务数据库
  4. 打拼10年的数据分析师,终于明白职场鄙视链才是最大的沉没黑洞
  5. PHP两种redirect
  6. 【交换机在江湖】第十二章 VLAN基础篇
  7. 百度经纬度和google经纬度互转
  8. Win10声卡驱动正常但没声音怎么办?驱动人生解决办法
  9. 南通大学java期末_【Java爬虫】爬取南通大学教务处成绩
  10. 分享 100 道基础的前端面试题(附答案)
  11. access计算机二级大纲,计算机二级Access考试内容大纲
  12. 学 C 语言,最经典的书有这样几本
  13. 《迅雷链精品课》第一课:认识区块链
  14. Station M2极客主机
  15. 思科CCNA第一本教材 第十一章 配置和测试网络 个人总结
  16. 软件开发质量管理和控制措施
  17. 大数据下的用户行为分析
  18. linux telnet成功显示什么_一文带你彻底理解 Linux 的各种终端类型及概念
  19. html页面 消除横向滚动条,框架网页中去掉横向(水平)滚动条的方法
  20. 开发者的瑞士军刀Eolink,目测要火

热门文章

  1. 启动计算机引导windows10,win10电脑启动界面提示windows boot manager怎么解决
  2. 木头骑士的Linux编程实验室(一)——时间、错误、限制
  3. 设计模式系列文章的总结
  4. 嘀嗒即使成为出行第一股,也恐怕依然是个锤子!
  5. 基于Echarts实现可视化数据大屏观测站综合监控平台
  6. Day3 变量和运算符
  7. 最新if,elseif,else最清楚用法解释
  8. 探秘格子间,寻找互联网人缓解焦虑的宝物
  9. GIT 远程仓库更换ip,导致本地拉取代码报错:ssh: connect to host 192.xxx.0.xxx port 22: Connection timed out fatal: Coul
  10. 【中秋福利】大数据告诉你:今年中秋礼品这样选