Spring中的InitializingBean接口源码解析

 更新时间:2024年02月14日 09:09:13   作者:securitit  
这篇文章主要介绍了Spring中的InitializingBean接口源码解析,InitializingBean接口为Bean初始化提供了一种方式,实现InitializingBean接口的Bean,在BeanFactory设置其所有属性后会调用其afterPropertiesSet()方法,需要的朋友可以参考下

简介

InitializingBean

InitializingBean接口为Bean初始化提供了一种方式。

实现InitializingBean接口的Bean,在BeanFactory设置其所有属性后会调用其afterPropertiesSet()方法。可以在afterPropertiesSet()方法中执行自定义初始化、属性检查或强制校验等,若不满足要求可以抛出异常以中断Spring的加载流程。

InitializingBean应用时有几点需要注意:

① Bean必须实现InitializingBean接口。

② Bean的afterPropertiesSet不能使用@PostConstruct注释。

init-method

init-method定义初始化方法为Bean初始化提供了另一种方式。

Bean声明时配置init-method属性,用于指定初始化方法。与InitializingBean方式类似,在BeanFactory设置其所有属性后会调用其init-method指定的方法。可以在init-method方法中执行自定义初始化、属性检查或强制校验等,若不满足要求可以抛出异常以中断Spring的加载流程。

init-method应用时有几个限制需要注意:

① init-method指定属性不能为空。

② Bean不可以实现InitializingBean接口或Bean的init-method方法名不可以为afterPropertiesSet。

③ Bean的init-method方法不能使用@PostConstruct注释。

演示示例

InitializingBean和init-method可以作用于同一个Bean,但是需要满足上面所罗列的注意事项,下面来使用一个简单示例看一下。

1) 建Bean,实现InitializingBean接口,并额外填加一个方法用于init-method配置。

package com.arhorchin.securitit.initbean;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;

/**
 * @author Securitit.
 * @note Bean初始化测试.
 */
public class InitTestBean implements InitializingBean {

    /**
     * logger.
     */
    private Logger logger = LoggerFactory.getLogger(InitTestBean.class);
    
    @Override
    public void afterPropertiesSet() throws Exception {
        logger.info("调用InitializingBean的afterPropertiesSet方法.");
    }
    
    public void initMethod() throws Exception {
        logger.info("调用init-method的initMethod方法.");
    }

}

2) 在Spring的配置文件中增加Bean声明,并指定init-method属性。

<bean class="com.arhorchin.securitit.initbean.InitTestBean" init-method="initMethod"></bean>

3) 运行程序查看效果,可以看到如下的输出。

2020-12-09 10:58:29 INFO [c.a.s.i.InitTestBean] 调用InitializingBean的afterPropertiesSet方法.
2020-12-09 10:58:29 INFO [c.a.s.i.InitTestBean] 调用init-method的initMethod方法.

从结果可以看到,InitializingBean的afterPropertiesSet先于Bean的init-method指定的方法调用。

源码解析

InitializingBean和init-method源码基本集中在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory类中。

1)initializeBean(...)方法

AbstractAutowireCapableBeanFactory的initializeBean(...)方法

initializeBean(...)方法中针对Bean进行了几个操作:

① 若Bean实现了Aware接口,则触发方法调用。包括:BeanNameAware、BeanClassLoaderAware和BeanFactoryAware。

② 调用注册的BeanPostProcessor的postProcessBeforeInitialization(...)方法。

③ 调用初始化方法,包括InitializingBean的afterPropertiesSet()方法和Bean的init-method指定的方法。

④ 调用注册的BeanPostProcessor的postProcessAfterInitialization(...)方法。

/**
 * 初始化给定的Bean实例,应用工厂回调、init方法和Bean后处理程序.
 * 对于传统定义的Bean,从createBean调用,对于现有的Bean实例从initializeBean调用.
 * @param beanName 工厂中的Bean名称(用于调试).
 * @param bean 需要初始化新的Bean实例.
 * @param mbd 创建Bean时使用的Bean定义(如果给定现有的Bean实例,也可以是null).
 * @return 初始化的Bean实例(可能被包装).
 */
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
    // 若Bean实现了Aware接口,则触发方法调用.
    if (System.getSecurityManager() != null) {
        AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
            invokeAwareMethods(beanName, bean);
            return null;
        }, getAccessControlContext());
    }
    else {
        invokeAwareMethods(beanName, bean);
    }

    Object wrappedBean = bean;
    // 在Bean初始化前处理BeanPostProcessor.
    if (mbd == null || !mbd.isSynthetic()) {
        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    }
    // 进行Bean初始化,包括如下两种方式:
    // 1.调用InitializingBean.afterPropertiesSet()方法.
    // 2.调用Bean配置的init-method方法.
    try {
        invokeInitMethods(beanName, wrappedBean, mbd);
    }
    catch (Throwable ex) {
        throw new BeanCreationException(
            (mbd != null ? mbd.getResourceDescription() : null),
            beanName, "Invocation of init method failed", ex);
    }
    // 在Bean初始化后处理BeanPostProcessor.
    if (mbd == null || !mbd.isSynthetic()) {
        wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
    }

    return wrappedBean;
}

2)实现Aware接口

若Bean实现了Aware接口,则触发方法调用。包括:BeanNameAware、BeanClassLoaderAware和BeanFactoryAware

/**
 * 若Bean实现了Aware接口,则触发方法调用.
 */
private void invokeAwareMethods(final String beanName, final Object bean) {
    if (bean instanceof Aware) {
        // 调用BeanNameAware.setBeanName(...)方法.
        if (bean instanceof BeanNameAware) {
            ((BeanNameAware) bean).setBeanName(beanName);
        }
        // 调用BeanClassLoaderAware.setBeanClassLoader(...)方法.
        if (bean instanceof BeanClassLoaderAware) {
            ClassLoader bcl = getBeanClassLoader();
            if (bcl != null) {
                ((BeanClassLoaderAware) bean).setBeanClassLoader(bcl);
            }
        }
        // 调用BeanFactoryAware.setBeanFactory(...).
        if (bean instanceof BeanFactoryAware) {
            ((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
        }
    }
}

3)InitializingBean的afterPropertiesSet方法调用

invokeInitMethods主要用于InitializingBean的afterPropertiesSet方法调用,从方法源码中也可以看到,是先调用InitializingBean的afterPropertiesSet方法,然后再调用Bean的init-method指定的方法,查看代码的注释,可以看到相关的内容,不做过多解析。

/**
 * 所有属性设置完成后可选的Bean初始化.
 * Bean实现了InitializingBean接口或定义了init-method方法,则会进行回调处理.
 * @param beanName 工厂中的Bean名称(用于调试).
 * @param bean 需要初始化新的Bean实例.
 * @param mbd 创建Bean时使用的合并Bean定义(如果给定现有的Bean实例,也可以是null).
 */
protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)
    throws Throwable {
    // Bean是否实现了InitializingBean接口.
    boolean isInitializingBean = (bean instanceof InitializingBean);
    // 调用InitializingBean.afterPropertiesSet()方法.需要满足条件:
    // 1.Bean实现了InitializingBean接口.
    // 2.Bean为空或afterPropertiesSet方法未被@PostConstruct注释.
    if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
        if (logger.isDebugEnabled()) {
            logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
        }
        // 调用InitializingBean.afterPropertiesSet()方法.
        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();
        }
    }
    // 调用配置init-method方法处理.
    if (mbd != null && bean.getClass() != NullBean.class) {
        String initMethodName = mbd.getInitMethodName();
        // 1.init-method配置不能为空.
        // 2.Bean不能实现InitializingBean或init-method方法不是afterPropertiesSet.
        // 3.init-method方法未被@PostConstruct注释.
        if (StringUtils.hasLength(initMethodName) &&
            !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
            !mbd.isExternallyManagedInitMethod(initMethodName)) {
            invokeCustomInitMethod(beanName, bean, mbd);
        }
    }
}

4)invokeCustomInitMethod

invokeCustomInitMethod主要用于调用init-method指定的方法,调用方式仅是通过反射来调用,查看代码的注释,可以看到相关的内容,不做过多解析。

/**
 * 在给定的Bean上调用指定的自定义init方法.由invokeInitMethods调用.
 * 可以在子类中重写,以便使用参数自定义解析init方法.
 */
protected void invokeCustomInitMethod(String beanName, final Object bean, RootBeanDefinition mbd)
    throws Throwable {
    // 取得Method实例.
    String initMethodName = mbd.getInitMethodName();
    Assert.state(initMethodName != null, "No init method set");
    final Method initMethod = (mbd.isNonPublicAccessAllowed() ?
                               BeanUtils.findMethod(bean.getClass(), initMethodName) :
                               ClassUtils.getMethodIfAvailable(bean.getClass(), initMethodName));
    if (initMethod == null) {
        if (mbd.isEnforceInitMethod()) {
            throw new BeanDefinitionValidationException("Couldn't find an init method named '" +
                                                        initMethodName + "' on bean with name '" + beanName + "'");
        }
        else {
            if (logger.isDebugEnabled()) {
                logger.debug("No default init method named '" + initMethodName +
                             "' found on bean with name '" + beanName + "'");
            }
            // Ignore non-existent default lifecycle methods.
            return;
        }
    }
    if (logger.isDebugEnabled()) {
        logger.debug("Invoking init method  '" + initMethodName + "' on bean with name '" + beanName + "'");
    }
    // 调用Bean的init-method配置的方法.
    if (System.getSecurityManager() != null) {
        AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
            ReflectionUtils.makeAccessible(initMethod);
            return null;
        });
        try {
            AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () ->
                                          initMethod.invoke(bean), getAccessControlContext());
        }
        catch (PrivilegedActionException pae) {
            InvocationTargetException ex = (InvocationTargetException) pae.getException();
            throw ex.getTargetException();
        }
    }
    else {
        try {
            ReflectionUtils.makeAccessible(initMethod);
            initMethod.invoke(bean);
        }
        catch (InvocationTargetException ex) {
            throw ex.getTargetException();
        }
    }
}

总结

InitializingBean是一个很神奇的接口,Spring框架中对InitializingBean的应用很是频繁,init-method同样如此,一定要了解两者之间的调用顺序,才能在更细粒度控制Bean的初始化过程。

源码解析基于spring-framework-5.0.5.RELEASE版本源码。

若文中存在错误和不足,欢迎指正!

到此这篇关于Spring中的InitializingBean接口源码解析的文章就介绍到这了,更多相关InitializingBean接口 内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Springboot Activemq整合过程代码图解

    Springboot Activemq整合过程代码图解

    这篇文章主要介绍了Springboot Activemq整合过程代码图解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-02-02
  • RabbitMQ中Confirm消息确认机制保障生产端消息的可靠性详解

    RabbitMQ中Confirm消息确认机制保障生产端消息的可靠性详解

    这篇文章主要介绍了RabbitMQ中Confirm消息确认机制保障生产端消息的可靠性详解,生产者将数据发送到 RabbitMQ 的时候,可能数据就在半路给搞丢了,因为网络问题啥的,都有可能,需要的朋友可以参考下
    2023-12-12
  • SpringBoot整合JPA的实例代码

    SpringBoot整合JPA的实例代码

    本篇文章主要介绍了SpringBoot整合JPA的实例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-05-05
  • 解读java.lang.Character.isLetterOrDigit()的使用方式

    解读java.lang.Character.isLetterOrDigit()的使用方式

    这篇文章主要介绍了解读java.lang.Character.isLetterOrDigit()的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-06-06
  • SpringBoot将Spring fox更换为Springdoc的方法详解

    SpringBoot将Spring fox更换为Springdoc的方法详解

    由于项目中使用Spring fox已经不维护更新了,代码扫描,扫出问题,需要将Spring fox更换为Spring Doc,所以本文给大家介绍了SpringBoot将Spring fox更换为Springdoc的方法,文中有相关的代码供大家参考,需要的朋友可以参考下
    2024-01-01
  • 微服务 Spring Boot 整合 Redis BitMap 实现 签到与统计功能

    微服务 Spring Boot 整合 Redis BitMap 实现 签到与统计功能

    这篇文章主要介绍了微服务 Spring Boot 整合 Redis BitMap 实现 签到与统计功能,文章简单介绍了Redis BitMap 基本用法结合实例代码给大家介绍的非常详细,需要的朋友可以参考下
    2023-01-01
  • java中用数组实现环形队列的示例代码

    java中用数组实现环形队列的示例代码

    这篇文章主要介绍了java中用数组实现环形队列的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • SpringBoot拦截器读取流后不能再读取的问题

    SpringBoot拦截器读取流后不能再读取的问题

    这篇文章主要介绍了SpringBoot拦截器读取流后不能再读取的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • Java8 Stream API 详细使用方法与操作技巧指南

    Java8 Stream API 详细使用方法与操作技巧指南

    这篇文章主要介绍了Java8 Stream API 详细使用方法与操作技巧,总结分析了Java8 Stream API 基本功能、使用方法与操作注意事项,需要的朋友可以参考下
    2020-05-05
  • springsecurity第三方授权认证的项目实践

    springsecurity第三方授权认证的项目实践

    Spring security 是一个强大的和高度可定制的身份验证和访问控制框架,本文主要介绍了springsecurity第三方授权认证的项目实践,具有一定的参考价值,感兴趣可以了解一下
    2023-08-08

最新评论