Google Guice 4:Bindings(2)

news2024/9/24 11:24:40

4 Scopes (实例的作用域)

4.1 默认规则:unreuse instance

  • 到目前为止,通过bind().to()@Provides定义的binding,每次需要注入实例对象时,Guice都会创建一个新的实例

    // 修改DatabaseTransactionLog,使其打印自身的hash code
    @Override
    public void log(String msg) {
        System.out.printf("%s@%s: %s\n", this.getClass().getSimpleName(), 
        		Integer.toHexString(hashCode()), msg);
    }
    // 创建多个MyDatabase实例,需要多次注入TransactionLog
    MyDatabase database1 = injector.getInstance(MyDatabase.class);
    database1.createTable("olap.users");
    
    MyDatabase database2 = injector.getInstance(MyDatabase.class);
    database2.createTable("presto.queries");
    
  • 执行上述代码,发现Guice为MyDatabase注入了不同的DatabaseTransactionLog实例

  • 这是Guice的默认规则,Guice returns a new instance each time it supplies a value.

4.2 build-in scopes

  • 对象的生命周期可以是应用程序级别的、session级别的、request级别,通过**作用域(scope)**可以配置对象的生命周期,从而实现对象的复用

单例@Singleton

  • 在实际应用场景中,需要将类定义为单例模式

  • 例如,在PrestoDB中,与属性配置有关的类(SystemSessionProperties)、监控各种事件的类(GcMonitor)、任务调度有关的类(NodeScheduler)等都被设置为单例

  • Guice支持两种@Singletonjavax.inject.Singletoncom.google.inject.Singleton,官方建议使用javax.inject.Singleton,因为注入框架也支持它

  • 使用@Singleton修饰DatabaseTransactionLog,将其定义为单例

    @Singleton
    public class DatabaseTransactionLog implements TransactionLog {
    	 /* everything here should be threadsafe! */
    }
    
  • 重新执行上述代码,发现Guice为MyDatabase注入的是一个实例

@RequestScoped

  • 除了@Singleton这个内置的scope,Guice官方文档还提到了@RequestScoped,servlet extension所包含的、用于web应用程序的scope

  • Guice并未说明@RequestScoped的详细信息,推测是com.google.inject.servlet.RequestScoped,可以通过如下maven依赖进行引入

    <dependency>
        <groupId>com.google.inject.extensions</groupId>
        <artifactId>guice-servlet</artifactId>
        <version>5.0.1</version>
    </dependency>
    

4.3 如何使用scope

  • 方法一: 在实现类上直接使用@Singleton

    @Singleton
    public class DatabaseTransactionLog implements TransactionLog {
    	 /* everything here should be threadsafe! */
    }
    
  • 方法二: 在bind()语句中定义单例

    // 1. 使用注解定义单例
    bind(TransactionLog.class).annotatedWith(Names.named("database")).to(DatabaseTransactionLog.class).in(Singleton.class);
    // 对应的in()方法
    void in(Class<? extends Annotation> scopeAnnotation);
            
    // 2. 实用Scopes.SINGLETON定义单例 
    bind(TransactionLog.class).annotatedWith(Names.named("database")).to(DatabaseTransactionLog.class).in(Scopes.SINGLETON);
    // 对应的in()方法
    void in(Scope scope);
    
  • 方法三:@Provides方法上定义单例

    @Provides
    @Named("database")
    @Singleton
    public TransactionLog providerDatabaseTransactionLog(DatabaseTransactionLog log) {
        return log;
    }
    
    @Provides
    @Named("database")
    @Singleton
    public TransactionLog providerDatabaseTransactionLog() {
        return new DatabaseTransactionLog();
    }
    
  • 上述三种定义单例的方式,最终使得Guice只会创建一个DatabaseTransactionLog对象

4.4 scopes apply to the binding source

  • 在linked binding中,scope是应用在binding source上,而非binding target

    In linked bindings, scopes apply to the binding source, not the binding target.

  • 与其说作用在binding source上,更易理解的是:作用在整个binding上

以上面@Provides定义的linked binding为例:

  • binding source:TransactionLog,binding target:DatabaseTransactionLog
  • 感觉@Singleton不会起作用,因为TransactionLog —> DatabaseTransactionLog,最终是需要得到DatabaseTransactionLog对象
  • 而无论是通过方法入参传入,还是通过new DatabaseTransactionLog()创建,而@Singleton标记不是的DatabaseTransactionLog,Guice应该会创建多个DatabaseTransactionLog对象
  • 实际上,Guice只会创建一个DatabaseTransactionLog对象,这是由于Singleton作用在TransactionLog上,使得TransactionLog只对应一个DatabaseTransactionLog对象
  • 这个DatabaseTransactionLog对象一旦创建好,Guice就像是直接从缓存中获取一样,而无需再创建新的DatabaseTransactionLog对象

示例2:一个类实现了两个接口,最终创建几个对象?

  • 小学生Pupil类,同时实现了Person和Student接口

    public interface Person {
        void eat();
    }
    
    public interface Student {
        void study();
    }
    
    public class Pupil implements Person, Student {
        @Override
        public void eat() {
            System.out.println("A pupil is eating potato chips");
        }
    
        @Override
        public void study() {
            System.out.println("A pupil is studying math");
        }
    
        @Override
        public String toString() {
            return this.getClass().getSimpleName() + "@" + Integer.toHexString(this.hashCode());
        }
    }	
    
  • 在Module中定义binding关系

    bind(Person.class).to(Pupil.class).in(Scopes.SINGLETON);
    bind(Student.class).to(Pupil.class).in(Singleton.class);
    
  • 从Guice获取实例:

    Student student = injector.getInstance(Student.class);
    Person person = injector.getInstance(Person.class);
    System.out.printf("student: %s, person: %s", student, person);
    
  • 执行代码,发现Student对应的Pupil对象和Person对应的Pupil对象,并非同一个

  • 如果Singleton作用在binding target上,则最终将从Guice获得同一个Pupil对象;但是,Singleton作用在binding source上,使得Student和Person将对应不同的Pupil对象

如何解决上述问题?

  • 若我们希望Pupil是单例,可以在定义Pupil类时使用@Singleton

    @Singleton
    public class Pupil implements Person, Student {}
    
  • 或者添加新的binding,将Pupil定义为单例

    bind(Pupil.class).in(Singleton.class);
    

4.5 单例的懒汉模式 vs 恶汉模式

  • 恶汉模式的单例(Eager singleton)会在类加载后完成初始化,以保证用户获得一致且快速的体验

  • 懒汉模式的单例(Lazy singleton,第一次使用时才进行初始化,可以加快程序的编译、运行周期

  • Guice中定义单例的方式很多,具体为哪种模式,可以参考表格

    定义方式PRODUCTIONDEVELOPMENT
    .asEagerSingleton()eagereager
    .in(Singleton.class)eagerlazy
    .in(Scopes.SINGLETON)eagerlazy
    @Singletoneagerlazy

Guice comes with a built-in @Singleton scope that reuses the same instance during the lifetime of an application within a single injector. Both javax.inject.Singleton and com.google.inject.Singleton are supported by Guice, but prefer the standard javax.inject.Singleton since it is also supported by other injection frameworks like Dagger.
在这里插入图片描述

5. Instance Bindings

  • 到目前为止,我们都是将type绑定到具体的implemention(实现)。

  • 在某些场景下,我们需要将type绑定到该type的具体实例

  • 例如,在整个应用中jdbc串是固定的,我们可以通过Guice为String类型的jdbcUrl传入固定值

  • MyDatabase的构造函数中,使用@Named("mysql jdbc url")标记MySQL JDBC串,然后通过instance binding为其赋值"jdbc:mysql://localhost/user"

    @Inject
    public MyDatabase(@Named("database")TransactionLog log, @Named("mysql jdbc url")String jdbcUrl) {
        this.log = log;
        System.out.println("Initialize MyDatabase with jdbcUrl: " + jdbcUrl);
    }
    
    // 通过instance binding配置jdbc url
    bind(String.class).annotatedWith(Names.named("mysql jdbc url")).toInstance("jdbc:mysql://localhost/users");
    
  • 针对基本数据类型、常见类型(如String、enum、Class),还可以使用bindConstant()直接绑定具体实例

    // 创建binding Annotation
    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Qualifier
    public @interface ForDatabase {
    }
    
    // 使用binding Annotation
    @Inject
    public MyDatabase(@Named("database") TransactionLog log, @ForDatabase String jdbcUrl) {
        this.log = log;
        System.out.println("Initialize MyDatabase with jdbcUrl: " + jdbcUrl);
    }
    
    // instance binding,注入具体的String对象
    bindConstant().annotatedWith(ForDatabase.class).to("jdbc:mysql://localhost/users");
    
  • 最终,创建MyDatabase时将打印传入的jdbcUrl

6. Provide Bindings

6.1 @Provides Methods

  • 最简单的方法,使用@Provides method实现对象的创建
  • @Provides method也不陌生了,之前在很多地方都有使用
    • 可以直接定义@Provides method
      @Provides
      public TransactionLog provideTransactionLog() {
          DatabaseTransactionLog transactionLog = new DatabaseTransactionLog();
          transactionLog.setJdbcUrl("jdbc:mysql://localhost/pizza");
          transactionLog.setThreadPoolSize(30);
          return transactionLog;
      }
      
    • 还可以定义带注解的@Provides method
      // instance binding,定义jdbcUrl的值
      bind(String.class).annotatedWith(Names.named("mysql jdbc url")).toInstance("jdbc:mysql://localhost/users");
      
      // Guice自动传入jdbcUrl
      @Provides
      @Database
      public TransactionLog provideTransactionLog(@Named("mysql jdbc url") String jdbcUrl) {
          DatabaseTransactionLog transactionLog = new DatabaseTransactionLog();
          transactionLog.setJdbcUrl(jdbcUrl);
          transactionLog.setThreadPoolSize(30);
          return transactionLog;
      }
      
      // 匹配到使用@Database标识的@Provides method
      @Inject
      public MyDatabase(@Database TransactionLog log) { 
          this.log = log;
      }
      
      @Override
      public void log(String msg) {
          System.out.printf("jdbcUrl: %s, threadPoolSize: %d\n", jdbcUrl, threadPoolSize);
      }
      
    • 最终,为MyDatabase注入的TransactionLog信息为:jdbcUrl: jdbc:mysql://localhost/users, threadPoolSize: 30

6.2 Provider Bindings

  • @Provides method中,创建对象的代码可能变得越来越复杂,也可能由于Module中存在多个@Provides method,导致Module变得越来越臃肿

  • 这时,可以考虑将@Provides method中代码转移到一个独立的类中,这个类实现了Guice的Provider接口

    public interface Provider<T> extends javax.inject.Provider<T> {
    	  T get();
    }
    
  • 下面的代码,在Provider的实现类中定义DatabaseTransactionLog的创建逻辑。其中,Connection需要Guice自动注入

    public class DatabaseTransactionLogProvider implements Provider<TransactionLog> {
        private final Connection connection;
    
        @Inject
        public DatabaseTransactionLogProvider(Connection connection) {
            this.connection = connection;
        }
    
        public TransactionLog get() {
            DatabaseTransactionLog transactionLog = new DatabaseTransactionLog();
            transactionLog.setConnection(connection);
            return transactionLog;
        }
    }
    
  • 在Module中使用toProvider()定义binding

    bind(TransactionLog.class).toProvider(DatabaseTransactionLogProvider.class);
    

7. Constructor Bindings

  • 之前,我们通过@Inject标识某个构造函数,告诉Guice这既是依赖注入的入口,又是创建实例对象的入口
  • 但是以下情况,@Inject将变得不再适用:
    • 使用的是第三方类
    • 类的多个构造函数参与了依赖注入

如何理解这个类的多个构造函数参与了依赖注入

  • 不同的dependent class,需要注入使用不同的构造函数创建的依赖
  • 例如,有的dependent class只需要注入一个通过默认构造函数创建的依赖,有的dependent class需要注入一个通过有参构造函数创建的依赖

  • 面对上述情况,可以通过Provide binding自己决定使用哪个构造函数创建对象

  • 但是,在AOP这种不能手动构造对象的情况,Provide binding也变得不再适用

  • 这时,可以考虑使用toConstructor()定义constructor binding

  • 定义有多个构造函数的MyTransactionLog

    public class MyTransactionLog implements TransactionLog {
        private String jdbcUrl;
        private int threadPoolSize;
    
        public MyTransactionLog() {
        }
    
        public MyTransactionLog(@Named("mysql") String jdbcUrl) {
            this.jdbcUrl = jdbcUrl;
        }
    
        @Override
        public void log(String msg) {
            // 打印自身信息
            System.out.printf("jdbcUrl: %s, threadPoolSize: %d\n", jdbcUrl, threadPoolSize);
        }
    }
    
  • 向MyDatabase和MyWarehouse注入TransactionLog

    @Inject
    public MyDatabase(@Database TransactionLog log) { 
        this.log = log;
    }
    
    @Inject
    public MyWarehouse(@Named("warehouse") TransactionLog log) {
        this.log = log;
    }
    
  • 使用toConstructor()定义constructor binding,使得MyDatabase和MyWarehouse注入的

    try {
        // MyDatabase使用的构造函数
        bind(TransactionLog.class).annotatedWith(Database.class)
                .toConstructor(MyTransactionLog.class.getConstructor(String.class));
        // MyWarehouse使用的构造函数
        bind(TransactionLog.class).annotatedWith(Names.named("warehouse"))
                .toConstructor(MyTransactionLog.class.getConstructor());
    } catch (NoSuchMethodException e) {
        throw new RuntimeException(e);
    }
    
  • 最终,MyDatabase和MyWarehouse将被注入具有不同属性的TransactionLog
    在这里插入图片描述

8. Just-in-time Bindings

  • 以上binding,最终都落在了继承AbstractModule自定义Module中,被叫做显式绑定(explicit binding
  • 如果一个type被需要,却又没有显示绑定,这是Guice会去寻找隐式binding( implicit binding
  • 隐式绑定,又叫Just-In-Time binding,简称 JIT binding

8.1 @Inject的隐式绑定

  • Guice创建一个类的对象时,需要找到该类可以依赖注入的构造函数(injectable constructor

以下两种情况,Guice认为构造函数是可注入的:

  1. 使用@Inject标识的构造函数(推荐的方式)
  2. 无参构造函数:
    • 且定义在非private的类中的、非private构造函数、(实际上,Guice支持private类中private构造函数,但是不推荐,因为反射会导致程序运行变慢)
    • 且未要求必须使用显式绑定
      binder().requireAtInjectRequired();
      
      在这里插入图片描述

以下情况,Guice认为构造函数不是可注入的

  • 未使用@Inject标识的、有一个或多个参数的构造函数
  • 类中不止存在一个使用@Inject标识的构造函数
  • 非静态的内部类中的构造函数

8.2 其他隐式绑定

8.2.1 @ImplementedBy

  • 使用@ImplementedBy定义linked binding

    @ImplementedBy(DatabaseTransactionLog.class)
    public interface TransactionLog {
        void log(String msg);
    }
    
  • 等价于下面的bind()语句

    bind(TransactionLog.class).to(DatabaseTransactionLog.class);
    
  • 如果既使用了bind(),又使用了@ImplementedBy,则bind()的优先级更高,会覆盖@ImplementedBy的定义

8.2.2 @ProvidedBy

  • 定义好的Provider类,可以通过@ProvidedBy告知Guice

    @ProvidedBy(DatabaseTransactionLogProvider.class)
    public interface TransactionLog {
        void log(String msg);
    }
    
  • 等价于下面的语句

    bind(TransactionLog.class).toProvider(DatabaseTransactionLogProvider.class);
    

8.3 关闭隐式绑定

  • 从Guice 3.0开始,在configure()方法中使用如下语句,可以关闭隐式绑定

    binder().requireExplicitBindings();
    

9. 后记

  • Guice还提供了很多其他的binding,例如支持binding多个实现的multi binding

    Multibinder<UriSummarizer> uriBinder = Multibinder.newSetBinder(binder(), UriSummarizer.class);
    // 可以Set的形式注入
    Set<UriSummarizer> summarizers
    
    MapBinder<String, EncoderFactory> encoderFactories = MapBinder.newMapBinder(binder, String.class, EncoderFactory.class);
    // 可以Map的形式注入
    Map<String, EncoderFactory>
    
  • 需要的小伙伴可以继续深入学习,这里就不再记录总结了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/372537.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【python学习笔记】:SQL常用脚本(二)

11、四舍五入ROUND函数 ROUND ( numeric_expression , length [ ,function ] ) function 必须为 tinyint、smallint 或 int。 如果省略 function 或其值为 0&#xff08;默认值&#xff09;&#xff0c;则将舍入 numeric_expression。 如果指定了0以外的值&#xff0c;则将截…

TypeScript笔记-进行中

学习来源&#xff1a; 本笔记由尚硅谷教学视频整理而来 文章目录学习来源&#xff1a;一.TS简介TypeScript是什么TypeScript增加了什么二环境搭建安装nvm环境搭建二.TypeScript中的基本类型类型声明类型类型示例代码三.编译配置自动编译文件自动编译整个项目四.使用webpack打包…

一文掌握如何轻松稿定项目风险管理【静说】

风险管理对于每个项目经理和PMO都非常重要&#xff0c;如果管理不当会出现很多问题&#xff0c;咱们以前分享过很多风险管理的内容&#xff1a; 风险无处不在&#xff0c;一旦发生&#xff0c;会对一个或多个项目目标产生积极或消极影响的确定事件或条件。那么接下来介绍下五大…

在成都想转行IT,选择什么专业比较好?

很多创新型的互联网服务公司的核心其实都是软件&#xff0c;创新的基础、运行的支撑都是软件。例如&#xff0c;软件应用到了出租车行业&#xff0c;就形成了巅覆行业的滴滴;软件应用到了金融领域&#xff0c;就形成互联网金融;软件运用到餐饮行业&#xff0c;就形成美团;软件运…

学渣适用版——Transformer理论和代码以及注意力机制attention的学习

参考一篇玩具级别不错的代码和案例 自注意力机制 注意力机制是为了transform打基础。 参考这个自注意力机制的讲解流程很详细&#xff0c; 但是学渣一般不知道 key&#xff0c;query&#xff0c;value是啥。 结合B站和GPT理解 注意力机制是一种常见的神经网络结构&#xff0…

[计算机网络(第八版)]第二章 物理层(复习笔记)

2.1 物理层的概念 物理层是屏蔽掉传输媒体和通信手段的差异&#xff0c;为数据链路层提供一个统一的数据传输服务&#xff0c;将比特流按照传输媒体的需要进行编码&#xff0c;然后将信号通过传输媒体传输到下一个节点的物理层&#xff0c;并不是指具体的传输媒体。用于物理层…

一文带你看懂:如何进行一次高质量CR?

程序员对代码评审&#xff08;Code Review&#xff09;不可谓不熟悉&#xff0c;而代码评审也已经是许多组织的标准化实践。结合笔者的五年多的开发经验&#xff0c;既有经历过零CR的小组织&#xff0c;也有接触过完善CR规范的大厂团队。对于“如何进行一次--高质量的组内代码C…

力扣-销售员

大家好&#xff0c;我是空空star&#xff0c;本篇带大家了解一道简单的力扣sql练习题。 文章目录前言一、题目&#xff1a;607. 销售员二、解题1.正确示范①提交SQL运行结果2.正确示范②提交SQL运行结果3.正确示范③提交SQL运行结果4.正确示范④提交SQL运行结果5.其他总结前言 …

【测试】自动化测试03(JUnit)

努力经营当下&#xff0c;直至未来明朗&#xff01; 文章目录JUnit一&#xff09; 注解1. Test2. BeforeEach3. BeforeAll4. AfterEach5. AfterAll二&#xff09; 断言&#xff08;Assertions类&#xff09;三&#xff09;用例的执行顺序四&#xff09;参数化五&#xff09;测试…

Qt windeployqt.exe 打包qml

Qt系列文章目录 文章目录Qt系列文章目录前言一、遇到的坑二、参考前言 我们在QtCreator下面开发程序&#xff0c;一般都会遇到工程发布给客户使用的情况。我们通常会使用Qt自带的打包工具&#xff1a;windeployqt.exe。 windeployqt.exe是Qt自带的工具&#xff0c;用于创建应用…

使用windwow windbg 吃透64位分页内存管理

前言 分页基础概念是操作系统基础知识&#xff0c;网上已经有太多太多了。所以本文记录使用windwow内核调试工具验证理论知识。 具体可以参阅intel volume3的 4.1.1 Four Paging Modes章节。 简而言之&#xff1a;CR0.PG 0表示不开启分页.并且根据CR4各种标志开启不同类别的…

力扣-变更性别

大家好&#xff0c;我是空空star&#xff0c;本篇带大家了解一道简单的力扣sql练习题。 文章目录前言一、题目&#xff1a;627. 变更性别二、解题1.正确示范①提交SQL运行结果2.正确示范②提交SQL运行结果3.正确示范③提交SQL运行结果4.正确示范④提交SQL运行结果5.其他总结前言…

算法设计与分析期末考试复习(三)

动态规划 动态规划算法与分治法类似&#xff0c;其基本思想也是将待求解问题分成若干个子问题。但是经分解得到的子问题往往不是互相独立的。在用分治法求解时&#xff0c;有些子问题被重复计算机了许多次。 如果能够保存已解决的子问题的答案&#xff0c;而在需要时再找出已求…

Scala-抽象类、匿名子类、伴生对象、单例模式

抽象类 匿名子类 伴生对象&#xff08;单例对象&#xff09; 单例设计模式 抽象类 定义抽象类&#xff1a;abstract class Person{} //通过 abstract 关键字标记抽象类定义抽象属性&#xff1a;val|var name:String //一个属性没有初始化&#xff0c;就是抽象属性定义抽象…

应用场景六:同时支持CP343和CP341功能(Modbus连接仪表,以太网连接WINCC)

应用描述&#xff1a; 桥接器同时支持西门子PLC的CP343以太网通讯模块和CP341串口通讯模块的功能。可以同时通过ModbusRTU连接仪表、变频器等串口设备&#xff0c;同时可以通过网口连接组态监控软件WINCC。PLC内部不需要编程&#xff0c;也不需要进行硬件组态配置&#xff0c;…

FME+YOLOV7写DNF自动刷图脚本

目录 前言 一、难点分析 二、实现流程 1.DNF窗口位置获取 2.获取训练数据 3.数据标注 4.数据格式转换 5.数据训练 5.刷图逻辑编写 前言 这是一篇不务正业的研究&#xff0c;首先说明&#xff0c;这不是外挂&#xff01;这不是外挂&#xff01;这不是外挂&#xff01;这只是用a…

【网络原理10】构造HTTP请求、HTTPS加密

目录 一、构造HTTP请求 ①使用form表单构造HTTP请求&#xff1a; form表单是如何提交的 form提交的缺点 ②基于ajax构造http请求 如何使用Jquery框架 二、HTTPS 运营商劫持 HTTP的加密版本&#xff1a;HTTPS ①对称加密&#xff1a;客户端和服务端使用同一把密钥&…

【AcWing】差分及其应用

&#x1f386;音乐分享 光辉岁月 (粤语版)_BEYOND 所谓差分&#xff0c;就是前缀和的逆运算 &#xff08;不懂前缀和的同学可以去C站看一下&#x1f602;&#xff09; 797. 差分 - AcWing题库 代码 #include<iostream> using namespace std; const int N 1e5 10…

怎样选择运动耳机、5款最佳运动蓝牙耳机推荐

你是否在跑步时大幅度抖动让耳机松落&#xff0c;不得不一遍又一遍的塞紧耳机&#xff1f;你是否在游泳时因为耳机进水而懊恼自己大意&#xff1f;没错&#xff0c;在运动过程中这些情况我都有遇到过&#xff01;运动耳机因其使用都是在跑步、游泳、骑行、徒步等场景&#xff0…

Apache Hadoop生态部署-Maxwell(实时数据同步)采集节点安装

目录 Apache Hadoop生态-目录汇总-持续更新 1&#xff1a;安装包准备 2&#xff1a;MySQL 环境准备 2.1&#xff1a;开启MySQL Binlog 2.2&#xff1a;验证mysql配置是否正确 2.3&#xff1a;初始化Maxwell元数据库,分配账号 3&#xff1a;安装 4&#xff1a;测试 Apach…