Spring进阶-Bean生命周期(上)

Bean生命周期

在使用Spring时我们将Bean交给Spring容器来管理,换句话来说就是Bean的整个生命周期都交由Spring容器来控制,所以在学习Spring时很有必要对Bean的生命周期有所了解。
Bean的生命周期有很多个过程,但是这里我不准备按照常规的流程来细说,而是先介绍四个主要过程,然后再在主要过程中查询生命周期的其他过程。这样有利于我们学习和记忆,分析起来也会更加清晰。总体来说Bean的生命周期可以分为四个主要过程:实例化、属性赋值、初始化、销毁。

对于Bean生命周期的分析,我们可以从BeanFactory的getBean方法入手,该方法内部实现方法为「doGetBean」。跟随源码分析,方法调用链如下:「AbstractBeanFactory#doGetBean」->「AbstractAutowireCapableBeanFactory#createBean」->「AbstractAutowireCapableBeanFactory#doCreateBean」,Bean的生命周期的前三个主要阶段都在该方法中有体现,我们后续的源码主要分析大致就在这一块。

AbstractAutowireCapableBeanFactory#doCreateBean

该方法的注释上有说,这个方法就是实际创建指定Bean的方法。这个方法内部很复杂,但是我们主要研究这个方法中涉及的Bean生命周期中的前三个阶段,在该方法中分别对应的方法名为:「createBeanInstance」「populateBean」「initializeBean」,这个三个方法分别对应的就是实例化、属性赋值、初始化。

实例化

实例化就是根据提供的信息创建出一个实例对象,你可以简单的理解成我们平时写代码时「new」一个对象。

 protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
  // 确保真正的解析了Bean的类型
  Class<?> beanClass = resolveBeanClass(mbd, beanName);

  if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {
   throw new BeanCreationException(mbd.getResourceDescription(), beanName,
     "Bean class isn't public, and non-public access not allowed: " + beanClass.getName());
  }
    //判断是否可以通过Supplier获取Bean实例
  Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
  if (instanceSupplier != null) {
   return obtainFromSupplier(instanceSupplier, beanName);
  }
    //静态工厂或者实例工厂
  if (mbd.getFactoryMethodName() != null) {
   return instantiateUsingFactoryMethod(beanName, mbd, args);
  }

  //如果之前创建过该类型的Bean,在此做一个优化
  boolean resolved = false;
  boolean autowireNecessary = false;
  if (args == null) {
   synchronized (mbd.constructorArgumentLock) {
    if (mbd.resolvedConstructorOrFactoryMethod != null) {
     resolved = true;
     autowireNecessary = mbd.constructorArgumentsResolved;
    }
   }
  }
  if (resolved) {
   if (autowireNecessary) {
    return autowireConstructor(beanName, mbd, nullnull);
   }
   else {
    return instantiateBean(beanName, mbd);
   }
  }

  // 自动装配构造函数
  Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
  if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
    mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
   return autowireConstructor(beanName, mbd, ctors, args);
  }

  //上一步没有找到则使用首选的构造函数
  ctors = mbd.getPreferredConstructors();
  if (ctors != null) {
   return autowireConstructor(beanName, mbd, ctors, null);
  }

  //使用无参构造函数
  return instantiateBean(beanName, mbd);
 }

通过上面的源码我们可以确定实例化的主要过程:

  1. 首先是确保Bean的类型解析出来,在AbstractBeanDefinition中有一个属性beanClass就是用来存储类型的。该类型并不是你认为的Class类型,而是一个Object类型,你一定很好奇为什么是Object类型。BeanDefinition中存储的beanClass它的只是Class的全称类型就是String,后来经过解析才将字符串类型的类型名称转化成Class类型,这一步就是调用的resolveBeanClass
  2. 接着就是通过判断是否可以通过Supplier或者工厂方法获取实例。对于工厂方法创建Bean实例这个我们不陌生,但对于Supplier可能会有点不太明白。不管是静态工厂还是实例工厂方法,最后都需要反射来调用目标方法,反射或多或少的对性能都会有影响。在Java中提供了函数式接口编程,对于Supplier它只有一个方法,通过该方法回调来获取实例。
        //创建IOC容器
        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
        //创建BeanDefinition
        AbstractBeanDefinition personBd = BeanDefinitionBuilder.genericBeanDefinition(Person.class)
                .addPropertyValue("name", "jack")
                .addPropertyValue("age", 19)
                .getBeanDefinition()
;
        personBd.setInstanceSupplier((Supplier<Person>) () -> {
            System.out.println("调用Supplier创建实例");
            return new Person();
        });
        //注册BeanDefinition
        factory.registerBeanDefinition("jack",personBd);
        //获取实例
        Person jack = factory.getBean("jack", Person.class);
        System.out.println(jack);

最后的运行结果如下:

调用Supplier创建实例
Person(name=jack, age=19)
  1. 接下来这部分代码只是一个优化代码。如果这个类型之前创建过,有部分信息就不需要重新再解析一遍,而是使用之前解析过的数据就行。
  2. 下面就到了自动装配候选构造函数了。这里面有一个方法determineConstructorsFromBeanPostProcessors,通过方法名称我们也能知道这个是用来确定构造函数的,它是通过什么方式来确定呢?从名字也能知道它是通过BeanPostProcessor来确定的(BeanPostProcessor很重要,本文只会简单的讲一下后续会出相关文章详解)。如果此时BeanPostProcessor有返回构造函数、Bean的自动装配为AUTOWIRE_CONSTRUCTOR、含有构造参数等情形中的一种,此时就会使用自动装配构造函数的方式来实例化。
  3. 如果上面的方式还是不行,就下来就会尝试通过首选构造函数创建。
  4. 最后上面的方式都不行,最后就会使用instantiateBean来实例化,其实就是使用默认的无参构造函数来实例化Bean。这个方法内部是怎样实例化Bean的呢?在该方法内部有依据关键性代码:
beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, this);

这里面的getInstantiationStrategy会返回一个InstantiationStrategy,从名字可以看出这就是一个实例化策略。这是Spring定义的一个实例化策略接口,在Spring中有两个实现分别为SimpleInstantiationStrategyCglibSubclassingInstantiationStrategy,默认情况下使用的是CglibSubclassingInstantiationStrategy也就是CGLIB的方式。关于具体详情这里就不展开细讲,有兴趣的可以自己去了解。
instantiateBean最后它会将创建完的Bean实例包装成一个BeanWrapper返回,BeanWrapper是个什么东西呢?简单的说就是它是Spring提供的一个用来操作JavaBean的工具,通过它可以更方便就行属性值的转换、嵌套属性设置等等。

属性赋值

继续分析doCreateBean方法,接下来可以看见populateBean(beanName, mbd, instanceWrapper)方法,该方法就是属性赋值。对于属性赋值,简单的说就是将BeanDefinition中设置定属性值赋值到实例中去的一个过程。

 protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
    //没有实例化对象
  if (bw == null) {
      //有属性执行抛出异常
   if (mbd.hasPropertyValues()) {
    throw new BeanCreationException(
      mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");
   }
   else {
    // 跳过属性赋值过程
    return;
   }
  }

  //在设置属性之前给InstantiationAwareBeanPostProcessors最后一次改变bean的机会
  if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
   for (BeanPostProcessor bp : getBeanPostProcessors()) {
    if (bp instanceof InstantiationAwareBeanPostProcessor) {
          // 如果为InstantiationAwareBeanPostProcessors
     InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
          //postProcessAfterInstantiation:如果应该在bean上面设置属性,则返回true,否则返回false
     if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
      return;
     }
    }
   }
  }

  PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
    //自动装配
  int resolvedAutowireMode = mbd.getResolvedAutowireMode();
  if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
   MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
   // 根据属性名称自动装配
   if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {
    autowireByName(beanName, mbd, bw, newPvs);
   }
   // 根据类型自动装配
   if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
    autowireByType(beanName, mbd, bw, newPvs);
   }
   pvs = newPvs;
  }
    //判断是否注册了InstantiationAwareBeanPostProcessors
  boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
    //判断是否要依赖检查
  boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);

  PropertyDescriptor[] filteredPds = null;
    //处理InstantiationAwareBeanPostProcessor
  if (hasInstAwareBpps) {
   if (pvs == null) {
    pvs = mbd.getPropertyValues();
   }
   for (BeanPostProcessor bp : getBeanPostProcessors()) {
    if (bp instanceof InstantiationAwareBeanPostProcessor) {
     InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
     PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
     if (pvsToUse == null) {
      if (filteredPds == null) {
       filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
      }
      pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
      if (pvsToUse == null) {
       return;
      }
     }
     pvs = pvsToUse;
    }
   }
  }
    //依赖检查
  if (needsDepCheck) {
   if (filteredPds == null) {
    filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
   }
   checkDependencies(beanName, mbd, filteredPds, pvs);
  }
    //将属性应用到Bean中
  if (pvs != null) {
   applyPropertyValues(beanName, mbd, bw, pvs);
  }
 }

上面整个代码中的主要要干的事就是将BeanDefinition中的属性值赋值给BeanWrapper对象,同时还有一个重要的点就是在属性赋值的过程中会有处理BeanPostProcessor的逻辑。

初始化

继续分析doCreateBean方法,接下来可以看见initializeBean(beanName, exposedObject, mbd)方法,该方法就是初始化。那么初始化会做哪些事情呢?

 protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
    //执行invokeAwareMethods
  if (System.getSecurityManager() != null) {
   AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
    invokeAwareMethods(beanName, bean);
    return null;
   }, getAccessControlContext());
  }
  else {
   invokeAwareMethods(beanName, bean);
  }

  Object wrappedBean = bean;
    //应用BeanPostProcessor的postProcessBeforeInitialization方法
  if (mbd == null || !mbd.isSynthetic()) {
   wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
  }

  try {
      //执行invokeInitMethods
   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()) {
      //应用BeanPostProcessor的postProcessAfterInitialization方法
   wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
  }
  return wrappedBean;
 }

上面就是整个初始化的过程,上面的代码还是比较清晰的,里面有几个方法我们需要深入了解一下。首先就是这里面的invokeAwareMethods,这个方法的内部实现如下:

 private void invokeAwareMethods(String beanName, Object bean) {
  if (bean instanceof Aware) {
   if (bean instanceof BeanNameAware) {
    ((BeanNameAware) bean).setBeanName(beanName);
   }
   if (bean instanceof BeanClassLoaderAware) {
    ClassLoader bcl = getBeanClassLoader();
    if (bcl != null) {
     ((BeanClassLoaderAware) bean).setBeanClassLoader(bcl);
    }
   }
   if (bean instanceof BeanFactoryAware) {
    ((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
   }
  }
 }

这个方法有什么用呢?在Spring中有一大堆的Aware接口,这个接口的作用就是Spring帮你注入你想要的对象。我们直接上代码:

public class LifecycleDemo1 {
    public static void main(String[] args) {
        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
        reader.loadBeanDefinitions("spring-life-cycle-1.xml");
        Object mac = factory.getBean("mac");

        AbstractBeanDefinition personBd = BeanDefinitionBuilder.genericBeanDefinition(Person.class)
                .addPropertyValue("name", "jack")
                .addPropertyValue("age", 19)
                .getBeanDefinition()
;
        personBd.setInstanceSupplier((Supplier<Person>) () -> {
            System.out.println("调用Supplier创建实例");
            return new Person();
        });
        factory.registerBeanDefinition("jack",personBd);

        Person jack = factory.getBean("jack", Person.class);
        System.out.println(jack);

        System.out.println("jack.getBeanFactory() == factory : " + (jack.getBeanFactory() == factory));

    }
}

@Data
class Person implements BeanFactoryAware {
    private String name;
    private Integer age;
    private BeanFactory beanFactory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }
}

上面示例代码中Person实现接口BeanFactoryAware,然后通过setBeanFactory方法将BeanFactory设置到Person实例中,这样我们就可以在Person实例中获取到BeanFactory,这个BeanFactory就是我们前面手动创建的BeanFactory,从最后的运行结果也可以看出。这个功能的实现就是通过上面的invokeAwareMethods实现的,如果我们的Bean可以是XXXAware类型,那么它就会回调setXXX接口,然后将XXX设置到我们的Bean中。当然这个只是部分Aware接口,在ApplicationContext中可以实现更丰富的注入。
接下来就是两个applyBeanPostProcessorsXXX方法,这两个方法主要是一些扩展,这里不展开说持续关注我后续文章会专门拿一期来讲BeanPostProcessor内容。最后就是invokeInitMethods方法了,这个方法作用是什么呢?直接看源码:

 protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBeanDefinition mbd)
   throws Throwable 
{
    //判断是不是InitializingBean,如果是则应用
  boolean isInitializingBean = (bean instanceof InitializingBean);
  if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
   if (logger.isTraceEnabled()) {
    logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
   }
   if (System.getSecurityManager() != null) {
    try {
     AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
      ((InitializingBean) bean).afterPropertiesSet();
      return null;
     }, getAccessControlContext());
    }
    catch (PrivilegedActionException pae) {
     throw pae.getException();
    }
   }
   else {
    ((InitializingBean) bean).afterPropertiesSet();
   }
  }
    //应用initMethod
  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);
   }
  }
 }

上面方法主要就是用来实现InitializingBean接口和initMethod方法的。通常在Spring中我们可以通过实现InitializingBean接口或者在BeanDefinition中指定initMethod方法来实现Bean的初始化,而invokeInitMethods方法就是Spring实现初始化的实现。下面直接看示例:

  • spring-life-cycle-2.xml配置文件
<bean id="mac" class="com.buydeem.share.lifecycle.User" init-method="initMethod"/>
  • Java代码
public class LifecycleDemo2 {
    public static void main(String[] args) {
        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
        reader.loadBeanDefinitions("spring-life-cycle-2.xml");

        User mac = factory.getBean("mac", User.class);

    }
}

class User implements InitializingBean {

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("调用InitializingBean#afterPropertiesSet()方法");
    }

    public void initMethod(){
        System.out.println("自定义initMethod()方法");
    }
}

最后的运行结果如下:

调用InitializingBean#afterPropertiesSet()方法
自定义initMethod()方法

从源码和打印结果也可以看出,InitializingBean是会在init-method方法之前生效的。在Spring中我们还可以通过@PostConstruct来指定初始化方法,对于@PostConstruct它并不是Spring的而是在java的JSR-250中的一个注解,只不过Spring对JSR-250中的注解有部分支持而已。那么这个是如何实现的呢?这个功能其实Spring是通过BeanPostProcessor来实现的,在前面有一个方法叫applyBeanPostProcessorsBeforeInitialization,而@PostConstruct就是通过该方法实现的,具体实现逻辑在InitDestroyAnnotationBeanPostProcessor可以找到,这个在将在BeanPostProcessor中细讲。修改前面示例的Java代码如下:

public class LifecycleDemo2 {
    public static void main(String[] args) {
        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
        reader.loadBeanDefinitions("spring-life-cycle-2.xml");

        //可以使用 new CommonAnnotationBeanPostProcessor()替换
        InitDestroyAnnotationBeanPostProcessor postProcessor = new InitDestroyAnnotationBeanPostProcessor();
        postProcessor.setInitAnnotationType(PostConstruct.class);

        factory.addBeanPostProcessor(postProcessor);

        User mac = factory.getBean("mac", User.class);

    }
}

class User implements InitializingBean {

    @PostConstruct
    public void postConstruct(){
        System.out.println("@PostConstruct");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("调用InitializingBean#afterPropertiesSet()方法");
    }

    public void initMethod(){
        System.out.println("自定义initMethod()方法");
    }
}

结合源码可以知道初始化的顺序是@PostConstruct->InitializingBean接口->BeanDefinition中的initMethod方法

销毁

前面Bean的生命周期中前三个我们可以在doCreateBean方法中找到,对于最后的销毁阶段我们可以通过BeanFactory中destroyBean找到对应的源码。

 public void destroy() {
    //处理BeanPostProcessor扩展并应用postProcessBeforeDestruction方法
  if (!CollectionUtils.isEmpty(this.beanPostProcessors)) {
   for (DestructionAwareBeanPostProcessor processor : this.beanPostProcessors) {
    processor.postProcessBeforeDestruction(this.bean, this.beanName);
   }
  }
    //如果实现DisposableBean接口则调用其destroy方法
  if (this.invokeDisposableBean) {
   if (logger.isTraceEnabled()) {
    logger.trace("Invoking destroy() on bean with name '" + this.beanName + "'");
   }
   try {
    if (System.getSecurityManager() != null) {
     AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
      ((DisposableBean) this.bean).destroy();
      return null;
     }, this.acc);
    }
    else {
     ((DisposableBean) this.bean).destroy();
    }
   }
   catch (Throwable ex) {
    String msg = "Invocation of destroy method failed on bean with name '" + this.beanName + "'";
    if (logger.isDebugEnabled()) {
     logger.warn(msg, ex);
    }
    else {
     logger.warn(msg + ": " + ex);
    }
   }
  }
    //调用BeanDefinition中定义的destroyMethod方法
  if (this.destroyMethod != null) {
   invokeCustomDestroyMethod(this.destroyMethod);
  }
  else if (this.destroyMethodName != null) {
   Method methodToInvoke = determineDestroyMethod(this.destroyMethodName);
   if (methodToInvoke != null) {
    invokeCustomDestroyMethod(ClassUtils.getInterfaceMethodIfPossible(methodToInvoke));
   }
  }
 }

对于Bean的销毁主要流程对比Bean的初始化过程有很多相似之处。在Spring中我们通常可以通过三种方式来自定义Bean的销毁扩展,分别是使用@PreDestroy注解方法、实现DisposableBean接口、在BeanDefinition中定义destroyMethod。废话不多说直接看示例:

  • spring-life-cycle-3.xml配置文件
<bean id="userService" class="com.buydeem.share.lifecycle.UserService" destroy-method="destroyMethod"/>
  • java示例代码
public class LifeCycleDemo3 {
    public static void main(String[] args) {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        reader.loadBeanDefinitions("spring-life-cycle-3.xml");

        //注册BeanPostProcessor
        InitDestroyAnnotationBeanPostProcessor beanPostProcessor = new InitDestroyAnnotationBeanPostProcessor();
        beanPostProcessor.setInitAnnotationType(PostConstruct.class);
        beanPostProcessor.setDestroyAnnotationType(PreDestroy.class);
        beanFactory.addBeanPostProcessor(beanPostProcessor);

        UserService userService = beanFactory.getBean("userService", UserService.class);
        beanFactory.destroyBean("userService",userService);
    }
}

class UserService implements DisposableBean {

    @PreDestroy
    public void preDestroy(){
        System.out.println("执行@PreDestroy方法");
    }

    public void destroy() throws Exception {
        System.out.println("执行DisposableBean#destroy()方法");
    }

    public void destroyMethod(){
        System.out.println("执行自定义destroyMethod()方法");
    }
}
  • 运行结果
执行@PreDestroy方法
执行DisposableBean#destroy()方法
执行自定义destroyMethod()方法

从运行结果可以看出,它们的顺序分别是@PreDestroy ->DisposableBean->destroyMethod。其中@PreDestroy它与@PostConstruct一样都是Spring对JSR-250的支持,它们都是通过BeanPostProcessor实现的。

小结

本文并未将Bean的整个生命周期全部讲完,特别是对于BeanPostProcessor部分并未做详细说明。看完该文我们需要知道以下几点:

  • Bean的主要生命周期就是实例化、属性赋值、初始化、销毁。
  • 对于Bean的每个主要生命周期中的大致流程。
  • 在主要的生命周期中间穿插了许多BeanPostProcessor扩展。
  • 对于初始化时,我们可以通过@PostConstruct注解、InitializingBean接口和BeanDefinition中定义初始化方法来实现自定义初始化,以及这三种方式的执行顺序。
  • 对于销毁时,我们可以通过@PreDestroy注解、DisposableBean接口和BeanPostProcessor中定义销毁方法实现自定义销毁,以及这三种方式的执行顺序。

对于Bean的生命周期这只是开篇的文章,在后面我准备再出一期将BeanPostProcessor相关内容的文章,最后再通过一篇文章来丰富整个Bean的生命周期。欢迎关注微信公众号,及时关注后续更新内容。


原文始发于微信公众号(一只菜鸟程序员):Spring进阶-Bean生命周期(上)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/73000.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!