作用域(Scopes)


作用域

默认情况下,Guice每次都会返回一个新的实例。这个特性可以通过作用域scopes)进行配置。Scopes允许你重用实例对象,例如在整个应用生命周期级别的重用(@Singleton)、session级别的重用(@SessionScoped)或request级别的重用(@RequestScoped)。Guice提供了一个servlet扩展用于支持在web应用中的作用域。同时,你还可以自已定义作用域用于其他类型的应用。

应用Scopes

Guice支持使用注解来定义作用域。你可以通过在实现类上添加作用域注解来指定绑定实例的作用域。这些注解同时也提供了文档注释功能。例如,@Singleton注解同时也表明该类应该是线程安全的。

@Singleton
public class InMemoryTransactionLog implements TransactionLog {
  /* everything here should be threadsafe! */
}

作用域也可以在bind()语句中配置:

  bind(TransactionLog.class).to(InMemoryTransactionLog.class).in(Singleton.class);

另外,通过注解@Provides方法也可以达到同样的目的:

@Provides @Singleton
  TransactionLog provideTransactionLog() {
    ...
}

如果一个类型存在的多个作用域发生冲突,Guice会优先使用bind()语句中的。这就意味着,如果一个类型被绑定了你不希望使用的作用域,你可以在bind()语句中使用Scopes.NO_SCOPE覆盖它。

需要注意的是:在关联绑定中作用域是应用在绑定源(binding source)而不是绑定目标上(binding target)的。例如,我们有一个类Applebees同时是实现了Bar接口和Grill接口,下面的绑定方式最终会生成两个Applebees的实例:一个是Bar类型的、一个是Grill类型的。

bind(Bar.class).to(Applebees.class).in(Singleton.class);
bind(Grill.class).to(Applebees.class).in(Singleton.class);

原因就在于,作用域影响的是边界类型(BarGrill)而不是满足绑定的类型(Applebees)。为了保证只有一个实例被创建,需要在类型的声明上添加@Singleton注解或者额外添加一个绑定:

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

有了上面的绑定,就无需前面的两组.in(Singleton.class)了。

in()语句可以接受一个作用域注解,如RequestScoped.class;或者一个Scope的实例,如ServletScopes.REQUEST:

bind(UserPreferences.class)
      .toProvider(UserPreferencesProvider.class)
      .in(ServletScopes.REQUEST);

Guice建议使用基于注解的方式,因为它可以使得module重用于不同类型的应用中。例如,一个@RequestScoped对象可以被用在web应用中的HTTP请求中或用在API服务器的RPC中。

恶汉模式的Singleton

Guice提供了一种定义单例绑定的语法来使注入器更早地创建实例对象:

bind(TransactionLog.class).to(InMemoryTransactionLog.class).asEagerSingleton();

恶汉模式singleton可以更早地发现初始化问题,从而确保终端用户能够有更一致优雅的体验。相反的,懒汉模式的singleton则能够保证开发-编译-运行这个流程更加快捷。通常,Stage枚举指定了不同的加载策略。

PRODUTION DEVELOPMENT
.asEagerSingleton() eager eager
.in(Sigleton.class) eager lazy
.in(Scope.SINGLETON) eager lazy
@Singleton eager* lazy

*Guice只会为它明确知道的类型执行恶汉模式的单例构建,包括在modules中显示定义的类型和这些类型的传递依赖。(也就是隐式绑定不会被)

选择合适的作用域

如果对象是有状态的(stateful),那么作用域的选择是显而易见的:对单个应用中使用的对象采用@Singleton,对单个请求中的使用的对象采用@RequestScoped。如果对象是无状态的(stateless)并且创建开销很小,作用域不是必须的。在绑定配置中保持默认即可,Guice会在需要的时候自动创建实例。

单例模式在Java应用中非常流行,但是很多情况下作用有限,尤其是在依赖注入的场景中。尽管单例模式能够节省对象创建的开销(进一步节省GC),但是单例的初始化过程往往需要同步加锁,获取指向单例的引用只需要读取一个volatile变量即可。单例在下面的场景中往往是比较有用的:

  • 有状态对象,比如配置对象和计数器;
  • 创建或查找开销比较大的对象;
  • 包装了某些资源的对象,例如数据库连接池。

作用域与并发

@Singleton@SessionScoped注解的类必须保证线程安全。被注入到这些类内部的类也必须保证自身的线程安全。你需要遵循最小化易变性的原则来减少类中的状态信息,从而减少同步保护的开销。

当然@RequestScoped是不需要保证线程安全的(因为每次都会从注入器获得一个新的实例)。一般而言,@Singleton@SessioinScoped注解的对象是不能依赖@RequestScoped注解的对象的(因为@RequestScoped每次都会从注入器获得一个新的实例导致线程不安全)。如果你需要一个更小的作用域,可以通过注入该对象的Provider来实现。

results matching ""

    No results matching ""