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


    Spring的Bean生命周期

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

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

    总览图

    在这里插入图片描述

    那么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);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    项目启动的初始化操作

    介绍个场景:项目启动时,需要将数据库中的一些配置信息数据读取出来加载到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();
              // 以下完成初始化数据操作
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
    • 也阔以将此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();
          }
          
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
    • 还可以实现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);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
    • 还可以实现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);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
    • 实现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);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12

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

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

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

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

  • 相关阅读:
    spring
    LeetCode+ 76 - 80 暴搜专题
    若依微服务上传图片文件代理配置
    虚拟机与主机(win10之间的通信)
    npm install 使用了不同版本的node,jenkins打包vue项目,cnpm打包超时,node-sass安装失败
    Tomcat深入浅出——Filter与Listener(五)
    C++异步:libunifex中的concepts详解!
    [春秋云境] CVE-2022-32991
    PX4利用matlab推导生成 EKF中雅可比矩阵的方法
    网络规划与设计实验+配置案例报告+pkt
  • 原文地址:https://blog.csdn.net/weixin_45248492/article/details/127444778