SpringBoot/Spring扩展点系列之初始化之@PostConstruct、init-method、InitializingBean - 第434篇

导读

在一些业务场景下,我们想要在Spring Bean初始化后自动做一些事情,比如预加载一部分数据,举例说明:要开发一个短信发送服务,在服务中,有些基本的配置信息是存放在数据库的,那么在Spring Bean初始化就需要从数据库加载到这些配置信息。

一、缘起:为什么需要init()方法?

为什么需要使用额外的方式来加载一个类的init()方法,好奇的宝宝可能会有这么一个疑问:类的初始化不是会执行构造方法吗?宝宝不要急,听我慢慢到来。

有这么一种场景:在Spring Bean中要调用另外一个Spring Bean的方法,那么这个时候会出现什么情况呢?看个小栗子:

package com.kfit.demo.service;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.core.env.Environment;import org.springframework.stereotype.Service;@Servicepublic class DemoService3 {    @Autowired    private  Environment environment;    public DemoService3(){        System.out.println("DemoService3的构造方法打印environment="+environment);    }}

这个小栗子很简单,就是在DemoService3中注入Environment,然后在构造方法打印Environment的值。小伙伴们,猜猜看Environment的值是多少?

如果对于Spring的执行顺序不是很了解的,这个问题,不见得能理解的很到位。

先看下执行的结果:

执行结果是null,惊不惊喜,意不意外。这个说明一个点,Spring中对于类的初始化以及注入属性值,这个是两个步骤,不是一次性完成的。此时当类的构造方法执行时,类的属性是并未被注入的,那么也就无法获取到Environment的值了。

二、破解:使用Spring的init()

对于这种,需要在Spring初始化之后做一些事情的话,那么怎么破呢?

对于初始化数据常用的有3种实现方式:

(1)使用JSR-250规范定义的@Postconstruct注解。

(2)使用Spring提供的@Bean init-method标签。

(3)实现InitializingBean接口,实现afterPropertiesset()方法。

对于这3种方式的使用,我们直接来看个小栗子:

package com.kfit.demo.service;import org.springframework.beans.factory.InitializingBean;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.core.env.Environment;import javax.annotation.PostConstruct;public class DemoService4 implements InitializingBean {    @Autowired    private  Environment environment;    public DemoService4(){        System.out.println("DemoService4的构造方法打印environment="+environment);    }    /**     * 使用JSR-250规范定义的@Postconstruct注解     */    @PostConstruct    public void postConstruct() {        System.out.println("DemoService4使用@postConstruct打印environment="+environment);    }    /**     * 使用@Bean的init-method属性     */    public void initMethod() {        System.out.println("DemoService4使用initMethod打印environment="+environment);    }    /**     * InitializingBean接口中的方法     * @throws Exception     */    @Override    public void afterPropertiesSet() throws Exception {        System.out.println("DemoService4使用InitializingBean打印environment=" + environment);    }}

然后在添加一个配置类:

package com.kfit.config;import com.kfit.demo.service.DemoService4;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configurationpublic class BeanConfig {    @Bean(initMethod = "initMethod")    public DemoService4 demoService4(){        return new DemoService4();    }}

说明:

(1)在这里定义了一个普通的java类DemoService4,然后通过配置类BeanConfig的@Bean进行注入。

(2)在@Bean注入的时候,指定了初始化方法initMethod。

(3)对于@PostConstruct的使用只需要在类的方法上添加注解即可。

(4)对于InitializingBean的使用需要实现接口InitializingBean中的afterPropertiesSet方法。

来看下控制台的信息打印:

调用顺序:构造函数 --> @PostConstruct --> InitializingBean接口 --> @Bean init-method 

基本的使用就这么简单,那么你还会问,纳尼,为啥存在这么种方式呢?它们有什么区别?

三、缘灭:前因后果

3.1 @PostConstruct

注解@PostConstruct是javax里定义的,属于JSR-250规范的一部分。






注: @PostConstruct已经从jdk 11版本开始移除了,所以在使用时需要额外引入javax.annotation-api:

<dependency>    <groupId>javax.annotation</groupId>    <artifactId>javax.annotation-api</artifactId>    <version>1.3.2</version></dependency>

使用方法就是类的方法上添加注解@PostConstruct,那么是否允许注解多个方法呢?答案是可以的,看如下执行效果:

对应的代码块:

其实不执行注解多个,也不影响,我们可以在方法中调用内部的其它方法就可以了。

3.2 @Bean init-method

在早起的Spring的版本是使用的xml实现,如<bean id="demoService" class="com.kfit.DemoService" init-method="initMethod"/>,而在Spring高版本中,可以使用注解的方式配置。

@Bean(initMethod = "initMethod")public DemoService4 demoService4(){    return new DemoService4();}

对于这种实现方式,我们会发现需要额外的@Configuration来声明一个@Bean。

如果对于使用@Service、@Controller等这样的方式注入Bean的,那么这种实现方案,就有点爱莫能助了。

initMethod就是原来spring配置文件里bean标签上的init-method,

3.3实现InitializingBean接口

方式相当于与Spring框架深度绑定了,所以官方文档上也说了并不是十分推荐用这个方式,而是推荐使用第1或是第2种方式。

官方文档:

https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-lifecycle-initializingbean

但是对于我们的项目深度捆绑又如何呢?本身框架就是基于Spring的体系搭建的,难道你能够把框架变成summer,想太多。所以就是哪种合适使用哪一种就好了,当然如果前面两种可以满足,优先使用前面两种方式。

如果你有看过MyBatis的代码,就可以看到SqlSessionFactoryBean就是实现了接口InitializingBean:



  1. 购买完整视频,请前往:http://www.mark-to-win.com/TeacherV2.html?id=287