乐趣区

关于bean:Bean异步初始化让你的应用启动飞起来

如果你的系统启动耗时 250s 以上,文章思路应该能够帮到你。

一、背景

近期,在做利用启动提速相干工作的过程中,咱们发现,利用启动速度次要的瓶颈在于 bean 的初始化过程(init,afterPropertiesSet 办法的耗时)。很多中间件 bean 的初始化逻辑波及到网络 io,且在没有相互依赖的状况下串行执行。将这一部分中间件 bean 进行异步加载,是晋升启动速度的一个摸索方向。

二、解决方案

  1. 主动扫描可批量异步的中间件 bean,而后,在 bean 的初始化阶段利用线程池并行执行其初始化逻辑。
  2. 容许应用方自行配置耗时 bean 以享受异步减速能力。(需应用方自行确认依赖关系满足异步条件)

三、原理

3.1 异步初始化原理

3.1.1 如何异步 init 和 afterPropertiesSet?

3.1.1.1 这俩初始化办法在哪里执行的?

在 AbstractAutowireCapableBeanFactory#invokeInitMethods 办法(以下代码省略异样解决以及日志打印)

protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)        throws Throwable {// 先看 bean 是不是实现了 InitializingBean,如果是则执行 afterPropertiesSet 办法。boolean isInitializingBean = (bean instanceof InitializingBean);    if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {if (System.getSecurityManager() != null) {AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {                ((InitializingBean) bean).afterPropertiesSet();                return null;}, getAccessControlContext());        } else {((InitializingBean) bean).afterPropertiesSet();}    }    // xml 定义的 init 办法    if (mbd != null && bean.getClass() != NullBean.class) {String initMethodName = mbd.getInitMethodName();        if (StringUtils.hasLength(initMethodName) &&                !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&                !mbd.isExternallyManagedInitMethod(initMethodName)) {invokeCustomInitMethod(beanName, bean, mbd);        }    }}
  • 调用位置图

3.1.1.2 如何自定义该办法逻辑使其反对异步执行?

  • 很简略的想法

有没有可能,我能够替换原有的 BeanFactory,换成我自定义的一个 BeanFactory,而后我继承他,只是重写 invokeInitMethods 办法逻辑使其反对异步?像这样:

public class AsyncInitBeanFactory extends DefaultListableBeanFactory {private static final Logger logger = LoggerFactory.getLogger(AsyncInitBeanFactory.class);
    // 省略
    @Override    protected void invokeInitMethods(String beanName, Object bean, RootBeanDefinition mbd) throws Throwable {if (AsyncInitBeanNameContainer.MIDDLEWARE_ASYNC_HSF_BEAN_NAME.contains(beanName)) {// hsf 异步 init            this.asyncCallInitMethods(TaskUtil.threadPool4HsfBean, beanName, bean, mbd);        } else if (AsyncInitBeanNameContainer.MIDDLEWARE_ASYNC_INIT_BEAN_NAME.contains(beanName)) {// 其余 bean 异步 init            this.asyncCallInitMethods(TaskUtil.threadPool4NormalMBean, beanName, bean, mbd);        } else {// 同步 init call 父类原来的 invokeInitMethods            try {                super.invokeInitMethods(beanName, bean, mbd);            } catch (Exception e) {logger.error("middleware-bean-accelerator sync-init error: {}", e.getMessage(), e);                throw e;            }        }    }    // 省略}

那当初曾经有了自定义办法了,只有解决替换就行了呗?

  • 怎么替换?

实现 ApplicationContextInitializer 接口,ApplicationContextInitializer 在 ApplicationContext 做 refresh 之前能够对 ConfigurableApplicationContext 的实例做进一步的设置或者解决。在这里能够用反射替换掉原 BeanFactory。像这样:

public class AsyncAccelerateInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {@Override    public void initialize(ConfigurableApplicationContext context) {// 是否开启异步初始化        if (ConfigUtil.isEnableAccelerate(context) && context instanceof GenericApplicationContext) {AsyncInitBeanFactory beanFactory = new AsyncInitBeanFactory(context.getBeanFactory());
            // 通过反射替换 beanFactory            try {Field field = GenericApplicationContext.class.getDeclaredField("beanFactory");                field.setAccessible(true);                field.set(context, beanFactory);            } catch (Throwable e) {throw new RuntimeException(e);            }        }    }}

之后咱们只须要在 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
@Overridepublic Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)        throws BeansException {Object result = existingBean;    // 把 BeanPostProcesss 都抓进去调用一下    for (BeanPostProcessor processor : getBeanPostProcessors()) {Object current = processor.postProcessBeforeInitialization(result, beanName);        if (current == null) {return result;}        result = current;    }    return result;}
  • 调用位置图

3.2.1.2 如何自定义该办法逻辑使其反对异步执行?

  • 很简略的想法

有没有可能,我能够去掉原有的 CommonAnnotationBeanPostProcessor,换成我自定义的一个 BeanPostProcessor,而后我继承他,只是重写 postProcessBeforeInitialization 办法逻辑使其反对可异步的 @PostConstruct 办法?像这样:

public class AsyncCommonAnnotationBeanPostProcessor extends CommonAnnotationBeanPostProcessor {private static final Logger logger = LoggerFactory.getLogger(AsyncCommonAnnotationBeanPostProcessor.class);
    @Override    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {// 如果是我指定的 beanName 那么走异步初始化, 把 super.postProcessBeforeInitialization(bean, beanName) 放进线程池里执行        if (AsyncInitBeanNameContainer.MIDDLEWARE_ASYNC_POST_CONSTRUCT_BEAN_NAME.contains(beanName)) {// 异步初始化            this.asyncExecutePostConstruct(bean, beanName);        } else {// 同步初始化            return super.postProcessBeforeInitialization(bean, beanName);        }        return bean;    }    // 略}

那当初曾经有了自定义办法了,只有解决替换就行了呗?

  • 怎么替换?

实现 InstantiationAwareBeanPostProcessorAdapter 接口,其中有一个办法叫做 postProcessBeforeInstantiation。postProcessBeforeInstantiation 办法是对象实例化前最先执行的办法,它在指标对象实例化之前调用,该办法的返回值类型是 Object,咱们能够返回任何类型的值。因为这个时候指标对象还未实例化,所以这个返回值能够用来代替本来该生成的指标对象的实例(比方代理对象)。

像这样:

public class OverrideAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter {@Override    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {// 替换掉原解决 @PostConstruct 注解的后置处理器        if ("org.springframework.context.annotation.internalCommonAnnotationProcessor".equals(beanName)) {AsyncCommonAnnotationBeanPostProcessor asyncBeanPostProcessor = new AsyncCommonAnnotationBeanPostProcessor();            // 省略根底的设置            return asyncBeanPostProcessor;        }        return super.postProcessBeforeInstantiation(beanClass, beanName);    }}

这样一来就实现了咱们一开始的指标,让 @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 就会将实现了近程调用的代理对象注入成员变量。如下:

@Servicepublic class XXXService {@HSFConsumer(serviceVersion = "1.0.0", serviceGroup = "HSF")    private OrderService orderService;
    // 省略}

3.2.1.2 如何通过 Consumer 的注解获取 Bean 信息?

如 3.2.1.1 节所示,被注入代理对象的成员变量字段上带有 @HSFConsumer 注解,这样,咱们是不是能够利用该注解在启动过程中找到这些 Bean,并对其施行异步初始化解决?答案是必定的通过实现 BeanFactoryPostProcessor 接口,咱们能够在 beanDefinition 被扫描 & 记录后,在 postProcessBeanFactory 办法中获取所有 bean 的定义信息,并找出其中带有 @HSFConsumer 注解的 bean 进行记录,以便在后续调用 init 办法时(见 3.1.1.2 节)进行异步初始化。

public class HsfBeanNameCollector implements BeanFactoryPostProcessor, BeanClassLoaderAware {    private ClassLoader classLoader;
    @Override    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {// 省略        for (String beanName : beanFactory.getBeanDefinitionNames()) {BeanDefinition definition = beanFactory.getBeanDefinition(beanName);            String beanClassName = definition.getBeanClassName();
            Class<?> clazz = ClassUtils.resolveClassName(definition.getBeanClassName(), this.classLoader);            ReflectionUtils.doWithFields(clazz, field -> {                if (AnnotationUtils.getAnnotation(field, HSFConsumer.class) == null) {return;}                // 收集 HsfConsumerBeanName 不便后续异步化                AsyncInitStaticVariables.MIDDLEWARE_ASYNC_HSF_BEAN_NAME.add(field.getName());            });        }    }
    @Override    public void setBeanClassLoader(ClassLoader classLoader) {this.classLoader = classLoader;}}

3.3.2 如何平安异步 HSFSpringConsumerBean?

3.3.2.1 咱们加 @HSFConsumer 注解的成员变量是如何被注入动静代理类的?

HSFSpringConsumerBean 实现了 FactoryBean 接口,其中的 getObject 办法会在属性注入时被调用,获取其返回值,注入成员变量。而真正接口的实现(也就是动静代理类)就是在这里被注入的。

public class HSFSpringConsumerBean implements FactoryBean, InitializingBean, ApplicationListener, ApplicationContextAware {// 省略    @Override    public Object getObject() throws Exception {return consumerBean.getObject();    }    // 省略}

而该动静代理类是如何生成的呢?答案在 HSFApiConsumerBean 的 init 办法中
如下所示:metadata.setTarget(consume(metadata));

public class HSFApiConsumerBean {    // 省略
    /**     * 初始化     *     * @throws Exception     */    public void init() throws Exception {        // 省略        synchronized (metadata) {// 省略                        metadata.init();            try {// 动静代理类的设置就在这里                metadata.setTarget(consume(metadata));              // 省略            } catch (Exception e) {// 省略} catch (Throwable t) {// 省略}
            // 省略        }    }    // 省略}

3.3.2.2 会存在什么问题?

  • 动静代理对象的生成在 init 阶段意味着什么?

意味着 bean 初始化如果未实现,会为成员变量注入一个 null 值,导致 consumer 不可用,这是异步的微小危险。

3.3.2.3 咱们的解决方案

自定义一个 NewHsfSpringConsumerBean,继承 HSFSpringConsumerBean 并重写 getObject 办法,在父类的 getObject 办法执行前期待初始化工作实现。
像这样:

public class NewHsfSpringConsumerBean extends HSFSpringConsumerBean {    // 省略    private Future<?> initTaskFuture;
    /**     * 重写 NewHsfSpringConsumerBean 的次要目标 在此退出卡点 避免 hsfSpringConsumerBean 未初始化实现导致的 npe     *     * @return     * @throws Exception     */    @Override    public Object getObject() throws Exception {        this.waitHsfInit();        return super.getObject();}
    private void waitHsfInit() {        if (this.initTaskFuture == null) {logger.warn("middleware-bean-accelerator, hsf getObject wait future is null.");            return;        }        try {this.initTaskFuture.get();        } catch (InterruptedException | ExecutionException e) {throw new RuntimeException(e);        }    }    // 省略}

当初的问题就是咱们如何将原有的 HSFSpringConsumerBean 替换成 NewHsfSpringConsumerBean?
答案还是 InstantiationAwareBeanPostProcessorAdapter 接口
如下所示:

public class OverrideAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter {    private final AsyncInitBeanFactory beanFactory;
    // 省略
    @Override    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {// 批改 beanDefinition 使容器创立自定义的 HsfSpringConsumerBean        if (beanClass == HSFSpringConsumerBean.class) {this.reviseBeanDefinition(beanName, NewHsfSpringConsumerBean.class);            // 返回 null 能够让实例化的工作交由 spring 容器            return null;        }        return super.postProcessBeforeInstantiation(beanClass, beanName);    }
    @Override    public boolean postProcessAfterInstantiation(Object bean, String beanName) {if (bean.getClass() == NewHsfSpringConsumerBean.class) {this.reviseBeanDefinition(beanName, HSFSpringConsumerBean.class);        }        return super.postProcessAfterInstantiation(bean, beanName);    }
    /**     * 批改 beanDefinition     * 设置 NewHsfSpringConsumerBean 使容器创立自定义的 HsfSpringConsumerBean 实例化后设置回来     *     * @param beanName     * @return     */    private void reviseBeanDefinition(String beanName, Class<?> clazz) {try {            Method methodOfRootBeanDefinition = this.beanFactory.getClass().                    getSuperclass().getSuperclass().getSuperclass().                    getDeclaredMethod("getMergedLocalBeanDefinition", String.class);            methodOfRootBeanDefinition.setAccessible(true);            RootBeanDefinition beanDefinition = (RootBeanDefinition) methodOfRootBeanDefinition.invoke(this.beanFactory, beanName);            // 重点步骤: 批改 beanDefinition 使容器创立自定义的 HsfSpringConsumerBean, 并在实例化后设置回来            beanDefinition.setBeanClass(clazz);        } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {throw new RuntimeException(e);        }    }}

咱们在实例化之前,批改 beanDefinition,使容器创立自定义的 HsfSpringConsumerBean。而后在实例化后的阶段将 beanDefinition 改回,这样就十分优雅实现了对原有 HSFSpringConsumerBean 的替换动作!

四、成果

4.1 性能成果

作者|严熠彬(言益)

原文链接

本文为阿里云原创内容,未经容许不得转载。

退出移动版