转载: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 外面都不支持。