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支持两种 @Singleton:javax.inject.Singleton和com.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中定义单例的方式很多,具体为哪种模式,可以参考表格 定义方式 PRODUCTION DEVELOPMENT .asEagerSingleton() eager eager .in(Singleton.class) eager lazy .in(Scopes.SINGLETON) eager lazy @Singleton eager lazy 
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
 
- 可以直接定义@Provides method
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()定义bindingbind(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认为构造函数是可注入的:
- 使用@Inject标识的构造函数(推荐的方式)
- 无参构造函数: 
  - 且定义在非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>
-  需要的小伙伴可以继续深入学习,这里就不再记录总结了 





![[计算机网络(第八版)]第二章 物理层(复习笔记)](https://img-blog.csdnimg.cn/2f393b03af264bb5a88a45b3114a6d0c.png)













