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

6次阅读

共计 10178 个字符,预计需要花费 26 分钟才能阅读完成。

如果你的系统启动耗时 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 性能成果

作者|严熠彬(言益)

原文链接

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

正文完
 0