• [spring] spring core - 配置注入及其他内容补充


    [spring] spring core - 配置注入及其他内容补充

    上篇 [sping] spring core - 依赖注入

    这里主要补一些 core 相关内容补充,同时添加了 java config bean 的方法

    java config bean 是除了 XML、java 注解之外另一给实现 DI 的方法

    java config bean

    这个方法不使用 annotation,而是使用 @Configure 类实现

    首先实现一个不使用注解的类:

    package com.example.demo;
    
    public class Lambda implements DBConnection{
        @Override
        public String connect() {
            return  "Connection via Lambda";
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    将这个类注入到 controller 的过程为:

    1. 创建 @Configure

      package com.example.demo;
      
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      
      @Configuration
      public class DBConfig {
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    2. 定义 @Bean 方法去配置 bean

      
      @Configuration
      public class DBConfig {
      
          @Bean
          public DBConnection lambdaConn() {
              return new Lambda();
          }
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

      bean id 默认为方法名

    3. 将 bean 注入到 controller

      这里依旧使用 @Qualifier("lambdaConn") 去实现:

      @RestController
      public class FirstRestController {
          private DBConnection dbconn;
      
          @Autowired
          public void setDBconn(@Qualifier("lambdaConn") DBConnection dbconn) {
              System.out.println("dbconn from setter");
              this.dbconn = dbconn;
          }
      
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11

      效果如下:

      在这里插入图片描述

    java config bean vs annotation

    二者的优缺点还是比较明显的

    方法优点缺点
    注解
    • 简单
    • boilerplate 代码少
    • 直观
    • 难以溯源,尤其是当代码量大起来的时候,寻找对应的依赖会比较困难
    • 耦合度较高
    java-based-config
    • 集中配置
    • 灵活性,尤其是对接第三方代码或是 legacy 代码
    • 低耦合
    • boilerplate 代码较多
    • 学习曲线更为陡峭

    一般推荐方案有:

    • 中小型项目,代码量较少的情况下,使用注解
    • 中大型项目使用 java 配置
    • 在有需求的时候进行混用,特别是对于无法控制的第三方库,或是一时半会儿没法重构的代码
    • 考虑使用 spring profile @Profile 代替 java config

    component scan

    关于 component scan,spring 会扫面 entry point——即有 @SpringBootApplication 的 java class——的统计和子 package,如果结构更新成下面这样的:

    在这里插入图片描述

    util 中的 class 不会被 spring 自动扫到:

    在这里插入图片描述

    想要让 spring 扫到 util 下的代码,需要使用在 @SpringBootApplication 中添加 scanBasePackages,如:

    @SpringBootApplication(scanBasePackages = {"com.example.util"})
    public class DemoApplication {
        // ...
    }
    
    • 1
    • 2
    • 3
    • 4

    除了有 scanBasePackages 之外,还有 scanBasePackageClasses

    懒初始化

    在 spring 项目启动时,所有的 bean 默认都完成初始化。我在构造函数里面添加了一行 System.out.println("In constructor: " + getClass().getSimpleName()); 后,终端显示如下:

    在这里插入图片描述

    可以看到,在 LiveReload 和 Tomcat 启动之前,所有的 bean 都完成了实例化。

    这也就意味着项目在启动初期一定会更加耗时,想要对其优化,可以使用懒初始化(lazy initialization)。这样 bean 只有在这两个情况下会被初始化:

    • 被其他的 bean 进行调用
    • 直接请求当前 bean

    实现的方法有三种:

    • 全局化实现

      这个在 spring boot 中的配置文件,如 application.properties 中实现:

      spring.main.lazy-initialization=true
      
      • 1
    • 使用 @Lazy 注解

      这个又可以分成多种情况:

      • 直接使用 @Component 的,可以在 @Component 中加:

            @Component
            @Lazy
            public class MySQL implements DBConnection {
            }
        
        
        • 1
        • 2
        • 3
        • 4
        • 5

        这时候 MySQL 就不会显示完成加载:

        在这里插入图片描述


      • Java Config 中可以在对应的 bean 上加,如:

            @Configuration
            public class DBConfig {
        
                @Bean
                @Lazy
                public DBConnection lambdaConn() {
                    return new Lambda();
                }
        
                @Bean
                @Lazy
                public  DBConnection mongoDBConn() {
                    return new MongoDB();
                }
            }
        
        
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17

        我这里添加了一个 MongoDB,因为 lambdaConn 在启动 rest controller 时被请求,所以肯定是要实例化才能进行下一步的。

        改完后效果如下:

        在这里插入图片描述


      • 在被 @Autowired 地方的添加 @Lazy

        @RestController
        public class FirstRestController {
            private DBConnection dbconn;
        
            @Autowired
            @Lazy
            public void setDBconn(@Qualifier("lambdaConn") DBConnection dbconn) {
                System.out.println("dbconn from setter");
                this.dbconn = dbconn;
            }
        
        }
        
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13

        这个情况下,只有当该 rest controller 被访问时,才会进行初始化:

        在这里插入图片描述

        JDBC 是唯一一个没有被添加 @Lazy 的类,因此一开始它就被初始化了

        随后可以看到,尽管这里用的是 setter 注入,但是却没有对象被实例化,一直到服务器启动了,网页被访问之后,Lambda 对象才被实例化

    • XML 中也可以添加 lazy-init 属性实现,不过我这没 XML 的案例,就不贴代码了

    总体来说,懒初始化可以提升项目启动速度,不过这个成本可能会加到运行的时候,因此在做优化时还是要跑一些 metrics 去最终决定是否要实现懒初始化,或是哪些 bean 被懒初始

    bean 生命周期

    上网找了一下,大体的流程是这样的:

    在这里插入图片描述

    不过教程没有细说,之时提了两个比较常用的 lifecycle callback:init 和 cleanup。前者在 bean 准备被使用前调用,这里可以处理一些 side effect,如链接 db,新建 socket 等,后者则是在 bean 被销毁前调用,用来清理一些 side effect

    init

    有三种方式可以实现:

    • XML

      <bean id="someBeanId" class="some.class" init-method="method-name" />
      
      • 1
    • 注解

      public class MySQL implements DBConnection {
          @PostConstruct
          public void init() {
              // init logic
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • 通过实现 InitializingBean 进行重载

      public class MySQL implements DBConnection, InitializingBean {
          @Override
          public void afterPropertiesSet() throws Exception {
      
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

    cleanup

    也是三种实现方式

    • XML
    <bean id="someBeanId" class="some.class" destroy-method="method-name" />
    
    • 1
    • 注解

      public class MySQL implements DBConnection {
          @PreDestroy
          public void cleanup() {
              // clean up logic
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • 实现 interface

      public class MySQL implements DBConnection, DisposableBean {
          @Override
          public void destroy() throws Exception {
      
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

    bean scope

    bean scope 和它的生命周期,以及在 app 中的可见度(即被分享)有关,所有的 bean 默认都是 singleton。

    目前官方提供的列表为:

    scope描述
    singleton默认,每个 IoC 容器只会生成一个
    prototype每次容器新创建一个对象,就就会新创建一个 instance
    request每个 HTTP 请求会生成一个新的 instance
    只有在 web 项目中适用
    session每个 HTTP session 中会生成一个 instance
    只有在 web 项目中适用
    application每个 ServletContext 生命周期会生成一个 instance
    只有在 web 项目中适用
    网页项目中与 singleton 相似
    websocket同理,一个 websocket 生命周期中会生成一个 instance
    只有在 web 项目中适用

    XML 中的配置方法为:

    <bean id="someBeanId" class="some.class" scope="a_valid_scope" />
    
    • 1

    使用 annotation 的方式为:

    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public class Lambda implements DBConnection{
        // ...
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这里简单的进行一下 shallow compare,即对比地址:

    public class FirstRestController {
        private DBConnection dbconn;
        private DBConnection dbconn2;
    
        @Autowired
        public void setDBconn(@Qualifier("lambdaConn") DBConnection dbconn, @Qualifier("lambdaCoÏnn") DBConnection dbconn2) {
            System.out.println("dbconn from setter");
            this.dbconn = dbconn;
            this.dbconn2 = dbconn2;
        }
    
        @GetMapping("/check")
        public String getCheck() {
            System.out.println(dbconn.getClass().getSimpleName());
            return "Comparing beans: dbconn" + (this.dbconn2 == this.dbconn ? "==" : "!=") + " dbconn2.";
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在 prototype 的情况下为 false:

    在这里插入图片描述

    在默认(singleton)的情况下为 true:

    在这里插入图片描述

    特殊的 prototype

    prototype 的清理有一些特殊,因为 spring 不会对 prototype 的生命周期进行完整的干里,即不会调用对应的清理函数,所以如果某个 bean 需要经常被创建/毁灭,那么就要考虑是不是需要使用 prototype,还是应该使用其他的 scope

    以及,prototype bean 默认就是懒初始化,所以没有必要添加 @Lazy

    总结

    在这里插入图片描述

  • 相关阅读:
    EdgeX Foundry MQTT设备服务
    生产数据自动化同步到预生产
    基于Spring Cloud搭建分布式配置中心
    Spring中shutdown hook作用
    Unity 中3D数学基础-向量
    Linux调试器——gdb的使用
    ECharts的高级使用
    算法入门02旋转数组的最小数字
    JavaWeb之Mybatis进阶
    修改51单片机中数组元素的值
  • 原文地址:https://blog.csdn.net/weixin_42938619/article/details/133514027