[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";
    }
}

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

  1. 创建 @Configure

    package com.example.demo;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class DBConfig {
    }
    
    
  2. 定义 @Bean 方法去配置 bean

    
    @Configuration
    public class DBConfig {
    
        @Bean
        public DBConnection lambdaConn() {
            return new Lambda();
        }
    }
    
    

    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;
        }
    
    }
    

    效果如下:

    [spring] spring core - 配置注入及其他内容补充_第1张图片

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,如果结构更新成下面这样的:

[spring] spring core - 配置注入及其他内容补充_第2张图片

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

[spring] spring core - 配置注入及其他内容补充_第3张图片

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

@SpringBootApplication(scanBasePackages = {"com.example.util"})
public class DemoApplication {
    // ...
}

除了有 scanBasePackages 之外,还有 scanBasePackageClasses

懒初始化

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

[spring] spring core - 配置注入及其他内容补充_第4张图片

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

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

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

实现的方法有三种:

  • 全局化实现

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

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

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

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

          @Component
          @Lazy
          public class MySQL implements DBConnection {
          }
      
      

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

      [spring] spring core - 配置注入及其他内容补充_第5张图片


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

          @Configuration
          public class DBConfig {
      
              @Bean
              @Lazy
              public DBConnection lambdaConn() {
                  return new Lambda();
              }
      
              @Bean
              @Lazy
              public  DBConnection mongoDBConn() {
                  return new MongoDB();
              }
          }
      
      
      

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

      改完后效果如下:

      [spring] spring core - 配置注入及其他内容补充_第6张图片


    • 在被 @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;
          }
      
      }
      
      

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

      [spring] spring core - 配置注入及其他内容补充_第7张图片

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

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

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

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

bean 生命周期

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

[spring] spring core - 配置注入及其他内容补充_第8张图片

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

init

有三种方式可以实现:

  • XML

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

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

    public class MySQL implements DBConnection, InitializingBean {
        @Override
        public void afterPropertiesSet() throws Exception {
    
        }
    }
    

cleanup

也是三种实现方式

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

    public class MySQL implements DBConnection {
        @PreDestroy
        public void cleanup() {
            // clean up logic
        }
    }
    
  • 实现 interface

    public class MySQL implements DBConnection, DisposableBean {
        @Override
        public void destroy() throws Exception {
    
        }
    }
    

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" />

使用 annotation 的方式为:

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Lambda implements DBConnection{
    // ...
}

这里简单的进行一下 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.";
    }
}

在 prototype 的情况下为 false:

[spring] spring core - 配置注入及其他内容补充_第9张图片

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

[spring] spring core - 配置注入及其他内容补充_第10张图片

特殊的 prototype

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

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

总结

[spring] spring core - 配置注入及其他内容补充_第11张图片

你可能感兴趣的:(Spring,spring,python,java)