02.IOC容器加载过程及Bean的生命周期和后置处理器
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);
}
逻辑就是:
- 判断容器中是否已经存在了
ConfigurationClassPostProcessor
Bean - 如果不存在,就通过 RootBeanDefinition 的构造方法获得
ConfigurationClassPostProcessor
的BeanDefinition - 执行
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);
}
- 通过AnnotatedGenericBeanDefinition的构造方法,获得配置类的BeanDefinition
- 判断需不需要跳过注册,Spring中有一个@Condition注解,如果不满足条件,就会跳过这个类的注册
- 然后是解析作用域,如果没有设置的话,默认为单例
- 获得BeanName
- 解析通用注解,填充到 AnnotatedGenericBeanDefinition,解析的注解为Lazy,Primary,DependsOn,Role,Description
- 限定符处理,不是特指@Qualifier注解,也有可能是Primary,或者是Lazy,或者是其他(理论上是任何注解,这里没有判断注解的有效性)
- 把AnnotatedGenericBeanDefinition数据结构和beanName封装到一个对象中(不重要,方便传参)
- 注册,最终会调用 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的一些准备工作
- 设置了一个类加载器
- 设置了bean表达式解析器
- 添加了属性编辑器的支持
- 添加了一个后置处理器:ApplicationContextAwareProcessor,此后置处理器实现了BeanPostProcessor接口
- 设置了一些忽略自动装配的接口
- 设置了一些允许自动装配的接口,并且进行了赋值操作
- 在容器中还没有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 的两个扩展点 postProcessBeanFactory 和 postProcessBeanDefinitionRegistry。前者是可以修改 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
用户保存当前准备创建的 BeanDefinitionRegistryPostProcessor5、去容器中获取BeanDefinitionRegistryPostProcessor的bean的处理器名称
internalConfigurationAnnotationProcessor
即ConfigurationAnnotationProcessor
一般情况都只会获取到一个。此时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的生命周期
① 实例化Bean对象,这个时候 Bean 的对象是非常低级的,基本不能够被我们使用,因为连最基本的属性都没有设置,可以理解为
连Autowired注解都是没有解析的② 填充属性,当做完这一步,Bean对象基本是完整的了,可以理解为Autowired注解已经解析完毕,依赖注入完成了
③ 如果Bean实现了BeanNameAware接口,则调用setBeanName方法
④ 如果Bean实现了BeanClassLoaderAware接口,则调用setBeanClassLoader方法
⑤ 如果Bean实现了BeanFactoryAware接口,则调用setBeanFactory方法
⑥ 调用BeanPostProcessor的postProcessBeforeInitialization方法
⑦ 如果Bean实现了InitializingBean接口,调用afterPropertiesSet方法
⑧ 如果Bean定义了init-method方法,则调用Bean的init-method方法
⑨ 调用BeanPostProcessor的postProcessAfterInitialization方法。当进行到这一步,Bean已经被准备就绪了,一直停留在应用的上下文中,直到被销毁
⑩ 如果应用的上下文被销毁了,如果Bean实现了DisposableBean接口,则调用destroy方法,如果Bean定义了destory-method声明了销毁方法也会被调用
思考
配置类@Configuration 加与不加的区别,加上会创建CGLIB动态代理,保证配置类中的Bean是受 IOC容器控制的,是单例的,如果配置类中重复的用某一个类,不加的话就是是重复调用方法,多次创建,不受IOC容器控制。
重复 beanName 覆盖原则,如果是通过 Scanner 扫描到的同一包下两个相同的 beanName 会抛异常,如果一个是 @Compontent 扫描的 bean,一个是通过 @Bean 配置的 bean,那么 @Bean 配置的 bean 会覆盖前面的 bean,因为它是后创建的。
02.IOC容器加载过程及Bean的生命周期和后置处理器相关推荐
- IOC容器加载过程及Bean的生命周期和后置处理器
SpringIOC 容器加载过程 第一步:实例化化容器:AnnotationConfigApplicationContext @Configuration @ComponentScan("c ...
- 框架源码专题:springIOC的加载过程,bean的生命周期,结合spring源码分析
文章目录 1.BeanFactory和ApplicationContext的区别? 2. IOC与 Bean的加载过程 ①:初始化容器DefaultListableBeanFactory ②:创建读取 ...
- Spring学习笔记八--Bean生命周期和后置处理器
为什么80%的码农都做不了架构师?>>> Bean生命周期和后置处理器 IOC容器的bean生命周期 1.构造器或工厂方法建立bean实例 2.bean属性赋值,引用其他bea ...
- initializeBean()方法为容器产生的Bean 实例对象添加BeanPostProcessor 后置处理器
同样在AbstractAutowireCapableBeanFactory 类中,initializeBean()方法实现为容器创建的Bean实例对象添加BeanPostProcessor 后置处理器 ...
- Spring Ioc 之 Bean的加载(1)(生命周期)
在之前的文章中,我们分析了Spring的Ioc的初始化过程,实际上就是把 beanName 和 BeanDefinition 注册到DefaultListableBeanFactory的map中. 在 ...
- 欧尼酱讲JVM(02)——类的加载过程
我们知道,在代码编译后,就会生成JVM(Java虚拟机)能够识别的二进制字节流文件(*.class).而JVM把Class文件中的类描述数据从文件加载到内存,并对数据进行校验.转换解析.初始化,使这些 ...
- Spring中bean的生命周期(易懂版)
bean的生命周期 写在前面的话 bean的生命周期 代码演示 bean的更完整的生命周期 添加后置处理器的代码演示 写在前面的话 关于bean的生命周期有很多的文章,但是大多数都是长篇的理论,说来说 ...
- spring bean加载过程_Spring源码剖析3:Spring IOC容器的加载过程
本文转自五月的仓颉 https://www.cnblogs.com/xrq730 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https ...
- 面试官:讲讲Spring框架Bean的加载过程
spring作为目前我们开发的基础框架,每天的开发工作基本和他形影不离,作为管理bean的最经典.优秀的框架,它的复杂程度往往令人望而却步. 不过作为朝夕相处的框架,我们必须得明白一个问题就是spri ...
最新文章
- 工艺路线和工序有差别吗_智能制造、数字化车间、数字化企业需要结构化工艺吗?...
- Tomcat性能优化总结
- CSDN开发者周刊 TDengine:专为物联网订制的大数据平台 YugaByte DB:高性能的分布式ACID事务数据库
- 打拼10年的数据分析师,终于明白职场鄙视链才是最大的沉没黑洞
- PHP两种redirect
- 【交换机在江湖】第十二章 VLAN基础篇
- 百度经纬度和google经纬度互转
- Win10声卡驱动正常但没声音怎么办?驱动人生解决办法
- 南通大学java期末_【Java爬虫】爬取南通大学教务处成绩
- 分享 100 道基础的前端面试题(附答案)
- access计算机二级大纲,计算机二级Access考试内容大纲
- 学 C 语言,最经典的书有这样几本
- 《迅雷链精品课》第一课:认识区块链
- Station M2极客主机
- 思科CCNA第一本教材 第十一章 配置和测试网络 个人总结
- 软件开发质量管理和控制措施
- 大数据下的用户行为分析
- linux telnet成功显示什么_一文带你彻底理解 Linux 的各种终端类型及概念
- html页面 消除横向滚动条,框架网页中去掉横向(水平)滚动条的方法
- 开发者的瑞士军刀Eolink,目测要火
热门文章
- 启动计算机引导windows10,win10电脑启动界面提示windows boot manager怎么解决
- 木头骑士的Linux编程实验室(一)——时间、错误、限制
- 设计模式系列文章的总结
- 嘀嗒即使成为出行第一股,也恐怕依然是个锤子!
- 基于Echarts实现可视化数据大屏观测站综合监控平台
- Day3 变量和运算符
- 最新if,elseif,else最清楚用法解释
- 探秘格子间,寻找互联网人缓解焦虑的宝物
- GIT 远程仓库更换ip,导致本地拉取代码报错:ssh: connect to host 192.xxx.0.xxx port 22: Connection timed out fatal: Coul
- 【中秋福利】大数据告诉你:今年中秋礼品这样选