Bean异步初始化,让你的应用启动飞起来 | 您所在的位置:网站首页 › hmc初始化 › Bean异步初始化,让你的应用启动飞起来 |
阿里妹导读 应用启动速度主要的瓶颈在于bean的初始化过程,本文提供了启动速度的一个探索方向。 如果你的系统启动耗时250s以上,文章思路应该可以帮到你。 一、背景 近期,在做应用启动提速相关工作的过程中,我们发现,应用启动速度主要的瓶颈在于bean的初始化过程(init,afterPropertiesSet方法的耗时)。很多中间件bean的初始化逻辑涉及到网络io,且在没有相互依赖的情况下串行执行。将这一部分中间件bean进行异步加载,是提升启动速度的一个探索方向。 二、解决方案 自动扫描可批量异步的中间件bean,而后,在bean的初始化阶段利用线程池并行执行其初始化逻辑。 允许使用方自行配置耗时bean以享受异步加速能力。(需使用方自行确认依赖关系满足异步条件) 三、原理 3.1 异步初始化原理
3.1.1 如何异步init和afterPropertiesSet? 3.1.1.1 这俩初始化方法在哪里执行的? 在AbstractAutowireCapableBeanFactory#invokeInitMethods方法(以下代码省略异常处理以及日志打印) 调用位置图
3.1.1.2 如何自定义该方法逻辑使其支持异步执行? 很简单的想法 有没有可能,我可以替换原有的BeanFactory,换成我自定义的一个BeanFactory,然后我继承他,只是重写invokeInitMethods方法逻辑使其支持异步? 像这样: 那现在已经有了自定义方法了,只要解决替换就行了呗? 怎么替换? 实现ApplicationContextInitializer接口,ApplicationContextInitializer在ApplicationContext做refresh之前可以对ConfigurableApplicationContext的实例做进一步的设置或者处理。在这里可以用反射替换掉原BeanFactory。 像这样: 之后我们只需要在spring.factories文件将其注册即可。 这样一来就实现了我们一开始的目标,让init方法和afterPropertiesSet支持异步执行。 3.1.2 如何异步PostConstruct? 3.1.2.1 @PostConstruct在哪执行的? 在CommonAnnotationBeanPostProcessor#postProcessBeforeInitialization方法 这是哪里? CommonAnnotationBeanPostProcessor实现了BeanPostProcessor接口,postProcessBeforeInitialization方法是BeanPostProcessor的方法。 BeanPostProcessor在初始化阶段被调用。 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization 调用位置图
3.2.1.2 如何自定义该方法逻辑使其支持异步执行? 很简单的想法 有没有可能,我可以去掉原有的CommonAnnotationBeanPostProcessor,换成我自定义的一个BeanPostProcessor,然后我继承他,只是重写postProcessBeforeInitialization方法逻辑使其支持可异步的@PostConstruct 方法? 像这样: 那现在已经有了自定义方法了,只要解决替换就行了呗? 怎么替换? 实现InstantiationAwareBeanPostProcessorAdapter接口,其中有一个方法叫做postProcessBeforeInstantiation。postProcessBeforeInstantiation方法是对象实例化前最先执行的方法,它在目标对象实例化之前调用,该方法的返回值类型是Object,我们可以返回任何类型的值。由于这个时候目标对象还未实例化,所以这个返回值可以用来代替原本该生成的目标对象的实例(比如代理对象)。
像这样: 之后我们只需要把这个BeanPostProcessor添加到BeanFactory,beanFactory.addBeanPostProcessor(new OverrideAwareBeanPostProcessor(beanFactory)); 这样一来就实现了我们一开始的目标,让@PostConstruct方法支持异步执行。 3.2 批量扫描&异步加载中间件Bean原理 中间件bean批量异步实现案例以RPC为例 RPC是后端日常开发中最常见的中间件之一,HSF是阿里内部常见的RPC中间件,3.2节的讲述我们以HSF为案例,实现HSFConsumerBean的批量异步初始化。 3.2.1 如何获取待异步的Bean信息? 3.2.1.1 HSF Consumer是怎么样使用的? 与Dubbo相似,对于使用者而言,只需在成员变量上加上@HSFConsumer注解,服务启动过程中HSF就会将实现了远程调用的代理对象注入成员变量。如下: 3.2.1.2 如何通过Consumer的注解获取Bean信息? 如3.2.1.1节所示,被注入代理对象的成员变量字段上带有@HSFConsumer注解,这样,我们是不是可以利用该注解在启动过程中找到这些Bean,并对其实施异步初始化处理? 答案是肯定的 通过实现BeanFactoryPostProcessor接口,我们可以在beanDefinition被扫描&记录后,在postProcessBeanFactory方法中获取所有bean的定义信息,并找出其中带有@HSFConsumer注解的bean进行记录,以便在后续调用init方法时(见3.1.1.2节)进行异步初始化。 3.3.2 如何安全异步HSFSpringConsumerBean? 3.3.2.1 我们加@HSFConsumer注解的成员变量是如何被注入动态代理类的? HSFSpringConsumerBean实现了FactoryBean接口,其中的getObject方法会在属性注入时被调用,获取其返回值,注入成员变量。而真正接口的实现(也就是动态代理类)就是在这里被注入的。 而该动态代理类是如何生成的呢?答案在HSFApiConsumerBean的init方法中 如下所示:metadata.setTarget(consume(metadata)); 3.3.2.2 会存在什么问题? 动态代理对象的生成在init阶段意味着什么? 意味着bean初始化如果未完成,会为成员变量注入一个null值,导致consumer不可用,这是异步的巨大风险。 3.3.2.3 我们的解决方案 自定义一个NewHsfSpringConsumerBean,继承HSFSpringConsumerBean并重写getObject方法,在父类的getObject方法执行前等待初始化任务完成。 像这样: 现在的问题就是我们如何将原有的HSFSpringConsumerBean替换成NewHsfSpringConsumerBean? 答案还是InstantiationAwareBeanPostProcessorAdapter接口 如下所示: 我们在实例化之前,修改beanDefinition,使容器创建自定义的HsfSpringConsumerBean。然后在实例化后的阶段将beanDefinition改回,这样就非常优雅实现了对原有HSFSpringConsumerBean的替换动作! 四、效果 4.1 性能效果
|
CopyRight 2018-2019 实验室设备网 版权所有 |