美文网首页IT@程序员猿媛
【Spring 笔记】InitializingBean 和 in

【Spring 笔记】InitializingBean 和 in

作者: 58bc06151329 | 来源:发表于2019-10-11 11:03 被阅读0次

    文前说明

    作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。

    本文仅供学习交流使用,侵权必删。
    不用于商业目的,转载请注明出处。

    1. 概述

    • Spring 在 bean 初始化时会进行三个检测扩展,可以对 bean 进行三个不同的定制化处理。包含有 Aware 接口BeanPostProcessor 接口InitializingBean 接口init-method

    2. 原理

    2.1 InitializingBean

    • Spring 的 org.springframework.beans.factory.InitializingBean 接口,为 bean 提供了定义初始化方法的方式。

    afterPropertiesSet

    • Spring 在完成实例化后,设置完所有属性,进行 Aware 接口BeanPostProcessor 前置处理 之后,会接着检测当前 bean 对象是否实现了 InitializingBean 接口。
      • 如果实现,则调用其 afterPropertiesSet() 方法,进一步调整 bean 实例对象的状态。
    public interface InitializingBean {
    
        /**
         * 该方法在 BeanFactory 设置完了所有属性之后被调用
         * 该方法允许 bean 实例设置了所有 bean 属性时执行初始化工作,如果该过程出现了错误则需要抛出异常
         *
         * Invoked by the containing {@code BeanFactory} after it has set all bean properties
         * and satisfied {@link BeanFactoryAware}, {@code ApplicationContextAware} etc.
         * <p>This method allows the bean instance to perform validation of its overall
         * configuration and final initialization when all bean properties have been set.
         * @throws Exception in the event of misconfiguration (such as failure to set an
         * essential property) or if initialization fails for any other reason
         */
        void afterPropertiesSet() throws Exception;
    }
    

    2.1.1 用例

    • 配置
    <?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="test" class="Test"></bean>
    </beans>
    
    • 用例
    public class Test implements InitializingBean {
    
        private String name;
    
        public String getName() {
            return name;
        }
    
        public static void main(String[] args) {
            ClassPathResource resource = new ClassPathResource("spring.xml");
            DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
            XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
            reader.loadBeanDefinitions(resource);
            Test test = (Test) factory.getBean("test");
            System.out.println("name :" + test.getName());
        }
    
        @Override
        public void afterPropertiesSet() throws Exception {
            System.out.println("Test initializing...");
            this.name = "test";
        }
    }
    
    /**
    print
    Test initializing...
    name :test
    **/
    
    • 用例中通过 afterPropertiesSet() 方法改变了 bean 的属性,Spring 容器又提供了一种可以改变 bean 实例对象的方法。

    2.1.2 invokeInitMethods

    • bean 初始化阶段,Spring 容器会主动检查当前 bean 是否已经实现了 InitializingBean 接口,这个主动检查、调用的动作是由 invokeInitMethods() 方法完成。
    // AbstractAutowireCapableBeanFactory.java
    protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)
            throws Throwable {
        // 1. 首先会检查是否是 InitializingBean ,如果是的话需要调用 afterPropertiesSet()
        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();
            }
        }
    
        if (mbd != null && bean.getClass() != NullBean.class) {
            // 2. 判断是否指定了 init-method(),
            // 如果指定了 init-method(),则再调用制定的init-method
            String initMethodName = mbd.getInitMethodName();
            if (StringUtils.hasLength(initMethodName) &&
                    !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
                    !mbd.isExternallyManagedInitMethod(initMethodName)) {
                // 激活用户自定义的初始化方法
                // 利用反射机制执行
                invokeCustomInitMethod(beanName, bean, mbd);
            }
        }
    }
    
    • 方法的调用流程。
      • 步骤 1,检测当前 bean 是否实现了 InitializingBean 接口,如果实现了则调用其 afterPropertiesSet() 方法。
      • 步骤 2,检查是否也指定了 init-method,如果指定了则通过反射机制调用指定的 init-method 方法。

    2.2 init-method

    • Spring 的一个核心理念是无侵入性,如果业务类实现 InitializingBean 接口就具有侵入性了。所以 Spring 提供了另外一种实现的方式:init-method 方法。

    2.2.1 用例

    • 配置
    <?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="test" class="Test" init-method="run" />
    </beans>
    
    • 用例
    public class Test {
    
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public static void main(String[] args) {
            ClassPathResource resource = new ClassPathResource("spring.xml");
            DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
            XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
            reader.loadBeanDefinitions(resource);
            Test test = (Test) factory.getBean("test");
            System.out.println("name :" + test.getName());
        }
    
        public void run() {
            System.out.println("Test initializing...");
            this.name = "test1";
        }
    }
    
    /**
    print
    Test initializing...
    name :test1
    **/
    
    • 通过 init-method 可以使用业务对象中定义的任何方法来实现 bean 实例对象的初始化定制化,而不再受制于 InitializingBeanafterPropertiesSet() 方法。
      • 同时使用 <beans> 标签的 default-init-method 属性可以统一指定初始化方法,这样不需要在每个 <bean> 标签中都设置 init-method 属性。
    <?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" default-init-method="run">
        <bean id="test" class="Test" />
    </beans>
    

    2.3 总结

    • init-method 指定的方法会在 afterPropertiesSet() 方法之后执行。
      • 如果 afterPropertiesSet() 方法的执行过程中出现异常,则 init-method 不会执行。
      • 由于 init-method 采用反射执行的方式,所以 afterPropertiesSet() 方法的执行效率一般会高些。
      • 但是依然优先使用 init-method,主要是因为它消除了 bean 对 Spring 的依赖,Spring 没有侵入业务代码,这样更加符合 Spring 的理念。
    • DisposableBeandestroy-method,它们实现与 init 类似。

    相关文章

      网友评论

        本文标题:【Spring 笔记】InitializingBean 和 in

        本文链接:https://www.haomeiwen.com/subject/bgcapctx.html