详解InitializingBean、initMethod和@PostConstruct

转载:https://blog.csdn.net/nrsc272420199/article/details/95033223

1. InitializingBean、initMethod和@PostConstruct的作用

实现了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容器创建完成........");
    }
}

 运行结果

 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............");
    }
}

运行结果

1、Spring为bean提供了两种初始化bean的方式,实现InitializingBean接口,实现afterPropertiesSet方法,或者在配置文件中通过init-method指定,两种方式可以同时使用。

2、实现InitializingBean接口是直接调用afterPropertiesSet方法,比通过反射调用init-method指定的方法效率要高一点,但是init-method方式消除了对spring的依赖。

3、如果调用afterPropertiesSet方法时出错,则不调用init-method指定的方法

运用:

有时候springboot自定义类@autowire注入为null的问题

解决方案: 

@Component
public class AtoboPipeline implements Pipeline {
 
    @Autowired
    private UrllistRepository urllistRepository;
 
    private static AtoboPipeline atoboPipeline;
    @PostConstruct //通过@PostConstruct实现初始化bean之前进行的操作
    public void init() {
        atoboPipeline = this;
        atoboPipeline.urllistRepository = this.urllistRepository;
        // 初使化时将已静态化的testService实例化
    }
...
 
//使用的时候这样使用
 atoboPipeline.urllistRepository.save(urlList);
}

 需要注意:注入类的调用方法是
atoboPipeline.urllistRepository.save(urlList);

这种调用方法看着很怪异,不过管用。注入这个功能在 controller 外面都不支持。

原文地址:https://www.cnblogs.com/cq-yangzhou/p/11947196.html