zoukankan      html  css  js  c++  java
  • 详解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 外面都不支持。

  • 相关阅读:
    android 自定义动画4 RotateAnimation源码分析
    Android 绘图 阴影制作(Shadow)
    view, surfaceView, invalidate, postInvalidate, 刷新屏幕
    android database 常用字段描述
    Android标题栏进度指示器使用
    ThumbnailUtils Android2.2新增类
    Android 重力感应 测试代码
    Android中内嵌字体实现个性化
    Android中悬浮窗口
    Android布局Java代码构造法
  • 原文地址:https://www.cnblogs.com/cq-yangzhou/p/11947196.html
Copyright © 2011-2022 走看看