文章目录

  • 说明
  • 测试代码

说明

本文从源码的角度分析Spring中Bean的加载过程,本文使用的Spring版本为4.3.25.RELEASE

测试代码

测试代码如下,根据这段简单的测试代码,一步步跟踪Spring中Bean的生命周期。

    @Testpublic void testSingleConfigLocation() {ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/org/springframework/context/support/simpleContext.xml");Assert.assertTrue(ctx.containsBean("someMessageSource"));ctx.close();}

一行行的分析上面这段代码
1、首先看ClassPathXmlApplicationContext容器加载。

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/org/springframework/context/support/simpleContext.xml");

2、参考ClassPathXmlApplicationContext的UML图。 附上ClassPathXmlApplicationContext的UML图,以便于理解接口之间的关系。

3、跟踪代码到下面这个构造方法的实现

 /*** Create a new ClassPathXmlApplicationContext, loading the definitions* from the given XML file and automatically refreshing the context.* @param configLocation resource location* @throws BeansException if context creation failed*/public ClassPathXmlApplicationContext(String configLocation) throws BeansException {this(new String[] {configLocation}, true, null);}/*** Create a new ClassPathXmlApplicationContext with the given parent,* loading the definitions from the given XML files.* @param configLocations array of resource locations* @param refresh whether to automatically refresh the context,* loading all bean definitions and creating all singletons.* Alternatively, call refresh manually after further configuring the context.* @param parent the parent context* @throws BeansException if context creation failed* @see #refresh()*/public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)throws BeansException {super(parent);setConfigLocations(configLocations);if (refresh) {refresh();}}

这个构造方法就是整个Bean加载过程的核心内容,根据上面的介绍,我们可以了解到

  1. ClassPathXmlApplicationContext 通过父类的的构造方法创建了上下文
  2. 从配置文件的定义中加载了所有的bean,并且默认创建都是singletons
  3. 在配置完上下文之后进一步调用refresh方法

4、最主要的就是这个refresh方法了,下面分析一下这个refresh方法

 @Overridepublic void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// 1、Prepare this context for refreshing.prepareRefresh();// 2、Tell the subclass to refresh the internal bean factory.ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// 3、Prepare the bean factory for use in this context.prepareBeanFactory(beanFactory);try {// 4、Allows post-processing of the bean factory in context subclasses.postProcessBeanFactory(beanFactory);// 5、Invoke factory processors registered as beans in the context.invokeBeanFactoryPostProcessors(beanFactory);// 6、Register bean processors that intercept bean creation.registerBeanPostProcessors(beanFactory);// 7、Initialize message source for this context.initMessageSource();// 8、Initialize event multicaster for this context.initApplicationEventMulticaster();// 9、Initialize other special beans in specific context subclasses.onRefresh();// 10、Check for listener beans and register them.registerListeners();// 11、Instantiate all remaining (non-lazy-init) singletons.finishBeanFactoryInitialization(beanFactory);// 12、Last step: publish corresponding event.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);// Propagate exception to caller.throw ex;}finally {//13、 Reset common introspection caches in Spring's core, since we// might not ever need metadata for singleton beans anymore...resetCommonCaches();}}}

这个fresh方法中包含了13个步,那就把这13一步一步的分析。
4.1、prepareRefresh()
这个方法意思是为刷新准备一个上下文,那进入这个方法,看它提供了什么?

 /*** Prepare this context for refreshing, setting its startup date and* active flag as well as performing any initialization of property sources.*/protected void prepareRefresh() {// 记一个开始时间this.startupDate = System.currentTimeMillis();this.closed.set(false);this.active.set(true);if (logger.isInfoEnabled()) {logger.info("Refreshing " + this);}// 初始化占位符属性,此处是空方法,以供子类继承扩展initPropertySources();// 验证标记为所需的所有属性都是可解析的// see ConfigurablePropertyResolver#setRequiredPropertiesgetEnvironment().validateRequiredProperties();// 这里是收集前期的应用事件// to be published once the multicaster is available...this.earlyApplicationEvents = new LinkedHashSet<ApplicationEvent>();}

4.2、ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory()
这个方法主要是告诉子类刷新bean工厂,这个类的代码如下:

 /*** 子类刷新内部工厂,如果子类覆盖refreshBeanFactory方法* @return the fresh BeanFactory instance* @see #refreshBeanFactory()* @see #getBeanFactory()*/protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {refreshBeanFactory();ConfigurableListableBeanFactory beanFactory = getBeanFactory();if (logger.isDebugEnabled()) {logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);}return beanFactory;}

这里的refreshBeanFactory是在AbstractRefreshableApplicationContext里实现的,这里附一下DefaultListableBeanFactory的UML图

这里就不贴代码了,这个obtainFreshBeanFactory的目的如下:
1.如果之前存在beanfactory则销毁
2.创建一个DefaultListableBeanFactory类型的beanfactory
3.加载DefaultListableBeanFactory

4.3、prepareBeanFactory(beanFactory);
在上下文中准备bean工厂

 /*** Configure the factory's standard context characteristics,* such as the context's ClassLoader and post-processors.* @param beanFactory the BeanFactory to configure*/protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {// 设置类加载器:存在则直接设置/不存在则新建一个默认类加载器beanFactory.setBeanClassLoader(getClassLoader());// 设置EL表达式解析器(Bean初始化完成后填充属性时会用到)beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));//设置属性注册解析器PropertyEditorbeanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));//将当前的ApplicationContext对象交给ApplicationContextAwareProcessor类来处理,从而在Aware接口实现类中的注入applicationContextbeanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));//设置忽略自动装配的接口beanFactory.ignoreDependencyInterface(EnvironmentAware.class);beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);beanFactory.ignoreDependencyInterface(MessageSourceAware.class);beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);// 注册可以解析的自动装配// MessageSource registered (and found for autowiring) as a bean.beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);beanFactory.registerResolvableDependency(ResourceLoader.class, this);beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);beanFactory.registerResolvableDependency(ApplicationContext.class, this);// Register early post-processor for detecting inner beans as ApplicationListeners.beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));// 如果当前BeanFactory包含loadTimeWeaver Bean,//说明存在类加载期织入AspectJ,则把当前BeanFactory交给类加载期BeanPostProcessor//实现类LoadTimeWeaverAwareProcessor来处理,从而实现类加载期织入AspectJ的目的。if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));// Set a temporary ClassLoader for type matching.beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));}//注册当前容器环境environment组件Bean.if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());}//注册系统配置的systemProperties组件Beanif (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());}//注册系统环境systemEnvironment组件Beanif (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());}}

4.4、 postProcessBeanFactory(beanFactory)
调用上下文中注册为bean的工厂处理器。

 /*** 在标准出书画之后修改应用程序上下文的bean factory* 所有bean定义都将被加载,但是还没有bean被实例化。* 这允许在特定的ApplicationContext实现中注册特殊的beanpstprocessors等。*/protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {}

4.5、invokeBeanFactoryPostProcessors(beanFactory)

 /*** 如果有顺序就按照顺序实例化所有注册的BeanFactoryPostProcessor* */protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());// 如果当前BeanFactory包含loadTimeWeaver Bean,//说明存在类加载期织入AspectJ,则把当前BeanFactory交给类加载期BeanPostProcessor//实现类LoadTimeWeaverAwareProcessor来处理,从而实现类加载期织入AspectJ的目的。if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));}}

4.6、 registerBeanPostProcessors(beanFactory)
注册拦截bean创建的bean处理器。

 /*** 实例化并注册所有beanPostProcessorbean,如果给定显式顺序,则遵循显式顺序。*/protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);}

PostProcessorRegistrationDelegate.registerBeanPostProcessors的实现如下

 public static void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);// 注册BeanPostProcessorChecker,在BeanPostProcessor实例化期间创建bean时记录信息消息int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));//区分BeanPostProcessors是实现了PriorityOrdered、Ordered、其他List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<BeanPostProcessor>();List<BeanPostProcessor> internalPostProcessors = new ArrayList<BeanPostProcessor>();List<String> orderedPostProcessorNames = new ArrayList<String>();List<String> nonOrderedPostProcessorNames = new ArrayList<String>();for (String ppName : postProcessorNames) {if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);priorityOrderedPostProcessors.add(pp);if (pp instanceof MergedBeanDefinitionPostProcessor) {internalPostProcessors.add(pp);}}else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {orderedPostProcessorNames.add(ppName);}else {nonOrderedPostProcessorNames.add(ppName);}}// First, register the BeanPostProcessors that implement PriorityOrdered.sortPostProcessors(priorityOrderedPostProcessors, beanFactory);registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);// Next, register the BeanPostProcessors that implement Ordered.List<BeanPostProcessor> orderedPostProcessors = new ArrayList<BeanPostProcessor>();for (String ppName : orderedPostProcessorNames) {BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);orderedPostProcessors.add(pp);if (pp instanceof MergedBeanDefinitionPostProcessor) {internalPostProcessors.add(pp);}}sortPostProcessors(orderedPostProcessors, beanFactory);registerBeanPostProcessors(beanFactory, orderedPostProcessors);// Now, register all regular BeanPostProcessors.List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<BeanPostProcessor>();for (String ppName : nonOrderedPostProcessorNames) {BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);nonOrderedPostProcessors.add(pp);if (pp instanceof MergedBeanDefinitionPostProcessor) {internalPostProcessors.add(pp);}}registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors);// Finally, re-register all internal BeanPostProcessors.sortPostProcessors(internalPostProcessors, beanFactory);registerBeanPostProcessors(beanFactory, internalPostProcessors);//重新注册后处理器,以便将内部bean检测为应用程序侦听器,并将其移动到处理器链的末端(用于获取代理等)beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));}

4.7、initMessageSource();
初始化此上下文的消息源。

 /*** Initialize the MessageSource.* Use parent's if none defined in this context.*/protected void initMessageSource() {ConfigurableListableBeanFactory beanFactory = getBeanFactory();if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);// Make MessageSource aware of parent MessageSource.if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource) {HierarchicalMessageSource hms = (HierarchicalMessageSource) this.messageSource;if (hms.getParentMessageSource() == null) {// Only set parent context as parent MessageSource if no parent MessageSource// registered already.hms.setParentMessageSource(getInternalParentMessageSource());}}if (logger.isDebugEnabled()) {logger.debug("Using MessageSource [" + this.messageSource + "]");}}else {// Use empty MessageSource to be able to accept getMessage calls.DelegatingMessageSource dms = new DelegatingMessageSource();dms.setParentMessageSource(getInternalParentMessageSource());this.messageSource = dms;beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);if (logger.isDebugEnabled()) {logger.debug("Unable to locate MessageSource with name '" + MESSAGE_SOURCE_BEAN_NAME +"': using default [" + this.messageSource + "]");}}}

4.8、 initApplicationEventMulticaster();
为上下文初始化事件广播

 /*** 初始化应用容器事件,如果没有定义的话就用默认的SimpleApplicationEventMulticaster **/protected void initApplicationEventMulticaster() {ConfigurableListableBeanFactory beanFactory = getBeanFactory();if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {this.applicationEventMulticaster =beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);if (logger.isDebugEnabled()) {logger.debug("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");}}else {this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);if (logger.isDebugEnabled()) {logger.debug("Unable to locate ApplicationEventMulticaster with name '" +APPLICATION_EVENT_MULTICASTER_BEAN_NAME +"': using default [" + this.applicationEventMulticaster + "]");}}}

附一下SimpleApplicationEventMulticaster的UML图

4.9、onRefresh()
初始化特定上下文子类中的其他特殊bean。

 /*** 这个是一个模板方法,为特殊的bean初始化用,在单例实例化之前* */protected void onRefresh() throws BeansException {// For subclasses: do nothing by default.}

4.10. registerListeners();
注册监听器

 /*** 添加实现了ApplicationListener的监听器* 不会影响哪些没有注册成bean的侦听器*/protected void registerListeners() {// Register statically specified listeners first.for (ApplicationListener<?> listener : getApplicationListeners()) {getApplicationEventMulticaster().addApplicationListener(listener);}// 不要在这里初始化FactoryBeans:我们需要保留所有常规bean// 未初始化以允许后处理器应用于它们!String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);for (String listenerBeanName : listenerBeanNames) {getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);}// 发布早期的应用程序event,这里使用就是观察者设计模式Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;this.earlyApplicationEvents = null;if (earlyEventsToProcess != null) {for (ApplicationEvent earlyEvent : earlyEventsToProcess) {getApplicationEventMulticaster().multicastEvent(earlyEvent);}}}

4.11、 finishBeanFactoryInitialization(beanFactory)
实例化所有剩余的(非延迟初始化)单例。

 /*** 完成上下文的初始化,初始化所有剩余的bean*/protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {// Initialize conversion service for this context.if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {beanFactory.setConversionService(beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));}//如果没有bean后处理器则注册一个默认的解析器EmbeddedValueResolverif (!beanFactory.hasEmbeddedValueResolver()) {beanFactory.addEmbeddedValueResolver(new StringValueResolver() {@Overridepublic String resolveStringValue(String strVal) {return getEnvironment().resolvePlaceholders(strVal);}});}// 尽早初始化LoadTimeWeaverAware bean,以便尽早注册其转换器。String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);for (String weaverAwareName : weaverAwareNames) {getBean(weaverAwareName);}// 停止使用临时类加载器进行类型匹配。beanFactory.setTempClassLoader(null);// 允许缓存所有bean定义元数据,不需要进一步更改。beanFactory.freezeConfiguration();// 实例化所有剩余的(非延迟初始化)单例beanFactory.preInstantiateSingletons();}

4.12、 finishRefresh()
最后一步:发布对应的事件。这一步bean加载就结束了,通知相应的监听器。

 /*** 完成容器上下文的刷新,执行onRefresh()方法并发布事件* org.springframework.context.event.ContextRefreshedEvent*/protected void finishRefresh() {// 初始化生命周期处理器initLifecycleProcessor();// 将增殖刷新到生命周期getLifecycleProcessor().onRefresh();// 发布最终的事件.publishEvent(new ContextRefreshedEvent(this));//参与MBean如果存在.LiveBeansView.registerApplicationContext(this);}

附bean生命周期DefaultLifecycleProcessor的UML

4.13、 resetCommonCaches();
在Spring的核心中重置常见的内省缓存,因为我们可能不再需要singleton bean的元数据了

 /*** Reset Spring's common core caches, in particular the {@link ReflectionUtils},* {@link ResolvableType} and {@link CachedIntrospectionResults} caches.* @since 4.2* @see ReflectionUtils#clearCache()* @see ResolvableType#clearCache()* @see CachedIntrospectionResults#clearClassLoader(ClassLoader)*/protected void resetCommonCaches() {ReflectionUtils.clearCache();ResolvableType.clearCache();CachedIntrospectionResults.clearClassLoader(getClassLoader());}

Spring源码分析——Bean的生命周期相关推荐

  1. Spring源码分析-深入理解生命周期之BeanFactoryProcessor

    生命周期之BeanFactoryPostProcessor 先来看看bean的生命周期.对于熟悉spring 的朋友来说,bean的生命周期并不陌生.它可以在bean加载,bean初始化的过程中加入我 ...

  2. Spring源码深度解析,Spring源码以及Bean的生命周期(五)(附代码示例:)

    五)Bean 的生命周期,创建---初始化---销毁的过程 目录 五)Bean 的生命周期,创建---初始化---销毁的过程 一 ,  指定初始化方法 init-method 方法​ 二 ,指定销毁 ...

  3. Spring源码学习--Bean的生命周期

    Spring作为当前Java最流行.最强大的轻量级框架,受到了程序员的热烈欢迎.准确的了解Spring Bean的生命周期是非常必要的.我们通常使用ApplicationContext作为Spring ...

  4. zuul源码分析之Request生命周期管理

    为什么80%的码农都做不了架构师?>>>    zuul核心框架 zuul是可以认为是一种API-Gateway.zuul的核心是一系列的filters, 其作用可以类比Servle ...

  5. PHP源码分析-PHP的生命周期

    PHP的最多的两种运行模式是WEB模式.CLI模式. 无论哪种模式,PHP工作原理都是一样的,作为一种SAPI运行. 1.当我们在终端敲入php这个命令的时候,它使用的是CLI. 它就像一个web服务 ...

  6. Spring容器启动流程+Bean的生命周期【附源码】

    如果对SpringIoc与Aop的源码感兴趣,可以访问参考:https://javadoop.com/,十分详细. 文章目录 Spring容器的启动全流程 Spring容器关闭流程 Bean 的生命周 ...

  7. 【Spring源码分析】Bean加载流程概览

    代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...

  8. beaninfo详解源码解析 java_【Spring源码分析】Bean加载流程概览

    代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...

  9. Spring源码分析之Bean的创建过程详解

    前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostProcessor调用过程详解 本文内容: 在 ...

最新文章

  1. html图片滚动红点_html2canvas生成的图片偏移问题
  2. jquery 入门与知识
  3. UGUI滚动列表ScrollView使用注意点
  4. thinkPHP5.0数据查询表达式生成技巧
  5. leetcode最大矩形_柱状图中的最大矩形
  6. 关于依赖倒置,控制反转和依赖注入的趣谈
  7. python图像边缘检测报告_python计算机视觉2:图像边缘检测
  8. Chrome谷歌离线安装包下载
  9. Python实现pdf转word
  10. Solidity 教程系列2 - 地址类型介绍
  11. win10快捷键及浏览器快捷键
  12. android sqlite数据库 emoji表情,Android的Emoji表情
  13. 模拟行走机器人-c语言
  14. 谷歌开源图片压缩算法Guetzli实测体验报告
  15. element-ui el-dialog 的form 表单验证关闭时清除错误验证信息
  16. “知识共享”扎根中国,前景无量
  17. C++ int类型转 LPCTSTR类型
  18. 这可能最全的操作系统面试题
  19. arctanx麦克劳林公式推导过程_点到线的距离公式推导过程
  20. 华为数通 软开2021实习生 业务主管面 (已通过)

热门文章

  1. SAP License审计说明及合并
  2. SAP固定资产减值准备的处理方法
  3. abap--REUSE_ALV_GRID_DISPLAY事件子过程和cl_gui_grid类的事件对应关系
  4. mysql5.6的安装步骤_MySQL5.6安装步骤
  5. cordova 蓝牙_Ionic通过Cordova插件使用设备能力
  6. uniapp 自定义进度条_如何解决uniapp小程序下载进度条问题
  7. mysql从库同步delete不动了_MySQL主从同步报错故障处理集锦
  8. [javaweb] servlet-session 会话跟踪技术 与 session保存作用域 (三)
  9. BUUCTF(pwn)hgame2018_flag_server(简单的栈溢出)
  10. Pwntools---fmtstr_payload()介绍