Spring的Bean生命周期+bean注入+项目启动时正确姿势初始化数据的五种方式

Spring的Bean生命周期

在Java中万物皆对象,既然是一个实例对象,那么就会有生命历程:被创建–>被使用–>被销毁…但是这说的太过于简洁了,以至于我们根本不能从本质上认清它具体的执行流程、生命历程,今天就来简单了解下Spring中的Bean生命周期具体流程。

我们知道,spring的ioc是创建和管理对象的关键容器,那么项目启动时,一定是spring容器先被创建出来,然后紧接着就是要放入spring容器中管理的各个组件bean,根据机制(是否为懒加载模式)来确定某些bean是否需要在spring容器被创建后立即创建出来进行管理。

总览图

Spring的Bean生命周期+bean注入+项目启动时正确姿势初始化数据的五种方式_第1张图片

那么Bean被创建的时候

  1. IOC通过反射机制调用Bean的构造函数(创建完Bean对象)

  2. 进行属性填充,set方法注入等属性注入

  3. 处理各种实现自xxxAware的实现类

  4. 如果bean中设置了@PostConstruct方法,那么开始执行

  5. BeanPostProcessor的postProcessBeforeInitialization()方法执行,前置处理器

  6. 如果bean实现了InitializingBean,重写了afterPropertiesSet(),那么开始执行

  7. 自定义的init方法,如创建Bean时指定的init-method

  8. BeanPostProcessor的postProcessAfterInitialization()方法执行,后置处理器

  9. bean对象被使用

  10. Spring容器销毁

  11. 实现自DisposableBean的bean执行重写的destroy方法

  12. 自定义的销毁方法,destroy-method执行

详解

前两条估计不用说了,先是调用对象的构造函数,然后属性注入执行set方法。

xxxAware

那么第3条,来自xxxAware的实现类目的其实就是让bean获取spring容器中的组件,当我们需要从spring中获取一些组件时可以使用

  • BeanNameAware:获取容器中bean名称
  • BeanFactorAware:获取BeanFactory容器
  • ApplicationContextAware:获取应用上下文

比如有个BeanUtil工具类,通过这个工具类的getBean方法能获取spring中的组件

@Component
public class BeanUtil implements ApplicationContextAware {
    private ApplicationContext context;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }
    // 对外方法
    public <T> T getBean (Class<T> clazz) {
        return context.getBean(clazz);
    }
}

BeanPostProcessor处理器

BeanPostProcessor的前置和后置处理器:

  • postProcessBeforeInitialization(Object bean, String beanName):当前要被初始化的对象会被传递进来,我们可以对它进行些操作,前置处理
  • postProcessAfterInitialization(Object bean, String beanName):当前已经初始化的对象被传递进来,我们可以对它进行些操作,后置处理

spring中的所有bean在初始化前后都会执行前置和后置的处理器,有点类似是全局方式,比如我创建一个类实现了BeanPostProcessor接口,重写了前置和后置的处理,那么所有的bean初始化前后都会执行它,且方法的返回值可以是bean本身,亦可以是bean的包装类对象(代理对象等),一般使用较少,还是要看项目的实际情况使用

其实像实现一些xxxAwareBeanPostProcessor在bean初始化过程中横插一脚,多数情况下是没有必要的,除非是项目情况需求万不得已,而且实现这些接口将会使我们应用层的代码耦合到spring框架中,会增加耦合度。

项目启动初始化数据+注入bean

上面大概了解下了Bean的整个生命周期流程,再来详细介绍下项目启动时要进行一些初始化数据或者注意一些bean的正确用法。

如何注入bean

刚开始学习spring时,注入一些属性,基本类型多用了@Value,引用类型基本就是在用@Autowired,而在spring中现在已经不建议去使用@Autowired方式了,先来复习下属性注入的几种方式:

  • 属性注入:@Autowired/@Resource
  • 构造函数注入(推荐使用)
  • set方法注入

官方不推荐使用属性注入,属性注入其实有一个显而易见的缺点,那就是对于 IOC 容器以外的环境,除了使用反射来提供它需要的依赖之外,无法复用该实现类。因为该类没有提供该属性的 set 方法或者相应的构造方法来完成该属性的初始化。换言之,要是使用属性注入,那么你这个类就只能在 IOC 容器中使用,要是想自己 new 一下这个类的对象,那么相关的依赖无法完成注入。

当然,他不推荐归为不推荐,各位想用他也拦住啊,O(∩_∩)O哈哈~

个人目前构造方法注入用的多一些,配合lombok的注解,用的嘎嘎爽,给个例子

@RestController
@RequestMapping("/snow/score")
@AllArgsConstructor
public class SnowScoreController {

    private final SnowRefereeService refereeService;
}

项目启动的初始化操作

介绍个场景:项目启动时,需要将数据库中的一些配置信息数据读取出来加载到java的配置类中,完成初始化数据的操作。

列举下可以使用的方式:

  • @PostConstruct: bean中可以使用@PostConstruct来完成初始化,注意:此bean必须被spring管理,且方法必须是无返回值的

    // 翻转到spring中
    @Component
    public class AliyunConfig {
        private String endpoint;
        private String accessKeyId;
        private String accessKeySecret;
        private String bucketName;
        // 阿里云域名(bucket域名或者自定义域名)
        private String aliyunDomain;
    
        @Autowired
        private SystemConfigService configService;
    
        @PostConstruct
        void initProperties() {
            configService.list();
            // 以下完成初始化数据操作
        }
    }
    
  • 也阔以将此Bean通过不同的方式反转到ioc中

    @Data
    class BeanTest {
        private String name;
        @Autowired
        private SystemConfigService configService;
    
        private void setConfigName() {
            System.out.println(configService);
            System.out.println("BeanTest初始化数据");
        }
    }
    
    @Configuration
    @Slf4j
    public class AliyunConfig {
        
        @Bean(initMethod = "setConfigName")
        public BeanTest beanTest () {
            return new BeanTest();
        }
        
    }
    
  • 还可以实现InitializingBean,重写方法

    @Data
    @Component
    class BeanTest implements InitializingBean {
        private String name;
    
        @Autowired
        SystemConfigService systemConfigService;
        @Override
        public void afterPropertiesSet() throws Exception {
            System.out.println("BeanTest初始化");
            System.out.println(systemConfigService);
        }
    }
    
  • 还可以实现ApplicationRunner或者CommandLineRunner,这两个是springboot启动流程中最后被执行的runner,有兴趣可以了解下我的一篇文章Springboot启动流程(源码解析)、自动装配流程(源码解析)、总结、SrpringBoot初始化数据扩展

    @Component
    public class MyInitRunner1 implements ApplicationRunner {
    
        @Autowired
        private DispatcherService dispatcherService;
    
        @Override
        public void run(ApplicationArguments args) throws Exception {
            System.out.println("我是runner1");
            System.out.println(dispatcherService);
        }
    }
    
  • 实现CommandLineRunner

    @Component
    public class MyInitRunner2 implements CommandLineRunner {
    
        @Autowired
        private DispatcherService dispatcherService;
    
        @Override
        public void run(String... args) throws Exception {
            System.out.println("我是runner2");
            System.out.println(dispatcherService);
        }
    }
    

最后贴一下几个的优先级:

@PostConstruct > 实现InitializingBean接口 > 实现ApplicationRunnerCommandLineRunner接口

按源码来看,ApplicationRunner的子类执行是优先于CommandLineRunner的子类的,可以通过@Order设置类的加载顺序。

介绍了5种springboot项目启动初始化数据的方式,基本开发中差不多是够用的。

你可能感兴趣的:(Java,SpringBoot,spring,java,spring,boot)