【bean的生命周期】详解InitializingBean、initMethod和@PostConstruct


源码地址 https://github.com/nieandsun/spring-study

1. InitializingBean、initMethod和@PostConstruct的作用

上篇文章中用到了InitializingBean接口,如上文所诉,实现了InitializingBean接口的类,可以在该类被注入到spring容器时达到 某些属性先装配完成后,再去装配另一些属性 的能力。而initMethod和@PostConstruct也可以达到相同的目的。

注意: 上文是一种用法,但思维不要局限。比如说我们的一个类里有一个属性,但是该属性不支持Spring注入,只能通过Build或者new的方式创建,而我们又想在spring装配Bean的时候一起将该属性注入进来,那使用InitializingBean、initMethod或@PostConstruct再合适不过了。

2. initMethod和InitializingBean

2.1 从initMethod说起

进行过spring配置开发的肯定对下面的配置非常熟悉

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="person" class="com.nrsc.springstudy.c071_InitializingBean_initMethod_PostConstruct.beans.Cat"
          init-method="init">
        <property name="name" value="花花"></property>
    </bean>

</beans>

没错initMethod就是原来spring配置文件里bean标签上的init-method,而InitializingBean也是spring提供的接口,那它俩有什么关系呢?先看如下代码:

2.2 从一个栗子来看initMethod和InitializingBean

  • 下面的类中包含了initMethod和InitializingBean它俩的用法
package com.nrsc.springstudy.c071_InitializingBean_initMethod_PostConstruct.beans;

import org.springframework.beans.factory.InitializingBean;

/**
 * Created By: Sun Chuan
 * Created Date: 2019/7/7 22:19
 */
public class Cat implements InitializingBean {
    private String name;

    //构造方法-----创建对象时调用
    public Cat() {
        System.out.println("Cat......constructor............");
    }

    //设置name属性时会调用
    public void setName(String name) {
        System.out.println("===cat=========setName========");
        this.name = name;
    }

    public String getName() {
        return name;
    }

    //在配置类中利用注解将initMethod指向下面的init方法----对应于initMethod的用法
    public void init() {
        System.out.println("Cat......init............");
    }

    //继承了InitializingBean接口,需要实现afterPropertiesSet方法---对应于InitializingBean的用法
    public void afterPropertiesSet() throws Exception {
        System.out.println("Cat......afterPropertiesSet............");
    }
}
  • 配置类如下
package com.nrsc.springstudy.c071_InitializingBean_initMethod_PostConstruct.config;

import com.nrsc.springstudy.c071_InitializingBean_initMethod_PostConstruct.beans.Cat;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class C071Config {

    @Bean(initMethod = "init")
    public Cat buildCat() {
        Cat cat = new Cat();
        cat.setName("花花");
        return cat;
    }
}
  • 启动类如下
import com.nrsc.springstudy.c071_InitializingBean_initMethod_PostConstruct.config.C071Config;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * Created By: Sun Chuan
 * Created Date: 2019/7/7 22:14
 */
public class Test071_InitializingBean_initMethod_PostConstruct {
    @Test
    public void test01() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(C071Config.class);
        System.out.println("IOC容器创建完成........");
    }
}
  • 运行结果

在这里插入图片描述
为什么会得到上面的运行结果呢?我们阅读spring装配bean的源码来找一下答案,请继续看2.3

2.3 探秘initMethod和InitializingBean在spring创建bean过程中的执行流程

  • 追踪spring装配bean的源码到AbstractAutowireCapableBeanFactory类,里面有一个方法doCreateBean为真正创建bean的方法,我把其关键代码摘出来并加上注释后,如下:
	protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
			throws BeanCreationException {

		// Instantiate the bean.----即初始化bean的意思,BeanWrapper为所有bean的包装类
		BeanWrapper instanceWrapper = null;
		if (mbd.isSingleton()) {
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
		if (instanceWrapper == null) {
			//创建对象----利用反射机制(结合动态代理或CGLIB代理)创建对象---》相当于new一个对象
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}

		try {
			//给属性赋值---》可以简单的理解为调用各个属性的set方法为各个属性进行赋值
			populateBean(beanName, mbd, instanceWrapper);
			//配置bean,即在当前对象创建完成,并对某些属性赋完值之后在对该bean进行其他一些处理
			//就比如会调用我们利用initMethod和InitializingBean指定的方法
			//还比如前置增强---后置增强(之后的博客肯定会介绍到)
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
		//将装配好的bean返回,最终将会被装配到spring容器
		return exposedObject;
	}
  • 再跟一下initializeBean方法 — 所在类AbstractAutowireCapableBeanFactory
	protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
		if (System.getSecurityManager() != null) {
			AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
				invokeAwareMethods(beanName, bean);
				return null;
			}, getAccessControlContext());
		}
		else {
			invokeAwareMethods(beanName, bean);
		}

		Object wrappedBean = bean;
		if (mbd == null || !mbd.isSynthetic()) {
			//前置增强处理----先有个概念,之后的博客肯定会介绍到
			wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
		}

		try {
			//真正调用initMethod和InitializingBean指定的方法
			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()) {
			//后置增强处理----先有个概念,之后的博客肯定会介绍到
			wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
		}

		return wrappedBean;
	}
  • 继续跟踪invokeInitMethods方法 — 所在类AbstractAutowireCapableBeanFactory

看到下面的方法就很一目了然了:

	protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)
			throws Throwable {
		//判断该bean是否实现了实现了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>) () -> {
						//不用管if-else是啥逻辑,反正就是如果实现了InitializingBean接口,则调用该bean的afterPropertiesSet方法
						((InitializingBean) bean).afterPropertiesSet();
						return null;
					}, getAccessControlContext());
				}
				catch (PrivilegedActionException pae) {
					throw pae.getException();
				}
			}
			else {
				//不用管if-else是啥逻辑,反正就是如果实现了InitializingBean接口,则调用该bean的afterPropertiesSet方法
				((InitializingBean) bean).afterPropertiesSet();
			}
		}
		//判断是否指定了initMethod方法,如果指定了,则再调用指定的initMethod方法
		if (mbd != null && bean.getClass() != NullBean.class) {
			String initMethodName = mbd.getInitMethodName();
			if (StringUtils.hasLength(initMethodName) &&
					!(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
					!mbd.isExternallyManagedInitMethod(initMethodName)) {
				//具体调用initMethod方法---用到了反射
				invokeCustomInitMethod(beanName, bean, mbd);
			}
		}
	}

2.4 总结

将上面的三个方法的逻辑抽离出来,大概就是下图的样子,initMethod和InitializingBean是spring提供的两种对类的属性进行装配的方式,initMethod和InitializingBean指定的方法运行顺序在普通属性装配之后,而initMethod指定的方法又在InitializingBean指定的方法之后。
在这里插入图片描述

3. 简单介绍@PostConstruct,并比较其与InitializingBean、initMethod的执行顺序

  • @PostConstruct不属于spring,它是JSR250定义的java规范,也就是说它是jdk的注解,但它也能完成和InitializingBean、initMethod一样的功能,更具体的就不再进行研究了,这里仅将其和InitializingBean、initMethod放在一起,进行一下简单测试,修改后的Cat类如下:
package com.nrsc.springstudy.c071_InitializingBean_initMethod_PostConstruct.beans;

import org.springframework.beans.factory.InitializingBean;

import javax.annotation.PostConstruct;

/**
 * Created By: Sun Chuan
 * Created Date: 2019/7/7 22:19
 */
public class Cat implements InitializingBean {
    private String name;

    //构造方法-----创建对象时调用
    public Cat() {
        System.out.println("Cat......constructor............");
    }

    //设置name属性时会调用
    public void setName(String name) {
        System.out.println("===cat=========setName========");
        this.name = name;
    }

    public String getName() {
        return name;
    }

    //在配置类中利用注解将initMethod指向下面的init方法----对应于initMethod的用法
    public void init() {
        System.out.println("Cat......init............");
    }

    //继承了InitializingBean接口,需要实现afterPropertiesSet方法---对应于InitializingBean的用法
    public void afterPropertiesSet() throws Exception {
        System.out.println("Cat......afterPropertiesSet............");
    }
    @PostConstruct
    public void  init2(){
        System.out.println("Cat......@PostConstruct............");
    }
}
  • 测试结果如下

在这里插入图片描述

  • 10
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值