龙空技术网

Spring Boot「50」扩展:Google Guice,另一个 DI 框架

IT知识分享官 526

前言:

眼前我们对“谷歌框架框架全版本”大概比较注重,姐妹们都需要学习一些“谷歌框架框架全版本”的相关文章。那么小编也在网上汇集了一些有关“谷歌框架框架全版本””的相关内容,希望各位老铁们能喜欢,咱们一起来了解一下吧!

Google Guice(发音同“juice”,果汁)是 Google 公司开源的一个 DI(Dependency Injection,依赖注入)框架。 DI 是一种设计模式或编程范式,它鼓励用户尽量地去声明它的依赖,然后通过某种方式(构造器、函数等)注入,而不是自己去创建依赖对象。 这种设计模式使得类的行为(或功能)与依赖的解析、处理分离,也使得应用职责更单一,封闭性更好。 Guice 官方文档中提供了一个示例,来说明使用 DI 模式与不使用 DI 的差异。

java复制代码// 未使用 DI 模式class Foo {  private Database database;  // We need a Database to do some work  Foo() {    // 需要显式地创建依赖    this.database = new Database("/path/to/my/data");  }}// 使用 DI 模式class Foo {  private Database database;  // We need a Database to do some work  // 不再需要手动创建,而由框架或其他模块传入  /**  * 这里即所谓的依赖注入,将显示创建依赖的逻辑从当前类中分离出去  * 让当前类更关注于自身逻辑的实现,而不必过多关心依赖对象的创建  * 更够达到降低耦合度的目的  */  Foo(Database database) {    this.database = database;  }}

通过前面的介绍,我们可以了解到,DI 模式的核心就是将类的功能或行为与依赖对象的解析、创建等逻辑分离开来。 当提及 DI 时,经常会听到如下的概念,它们所描述的设计思想,与 DI 是相同的:

Inversion of control,控制反转。Hollywood principle,好莱坞原则。Injection,注入。01-理解 Guice 中的核心概念

从某种程度上,可以把 Guice 理解为或认为是一个 Map(映射表),key -> value。 实际上的 Guice 实现要更为复杂。

Guice key,映射表中的键;一般由两部分组成,一个类型(必须的)和一个绑定注解(binding annotation,作用是确定依赖,可选的)。 key 一般通过如下接口获得:

java复制代码Key.get(String.class, English.class);// 或当不需要 binding annotation 就能确定时Key.get(String.class);  // map 中不存在同类型的对象,例如两个 String 类对象
Provider,映射表中的值;它仅有一个方法 Provider#get 方法,能够返回特定类型的对象实例。 一般来说,应用并不需要直接实现 Provider 接口。 通过继承 AbstractModule,Guice 能够根据配置自动创建相应的 Provider。 例如:
java复制代码class DemoModule extends AbstractModule {  @Provides  static Integer provideCount() {    return 3;  }  @Provides  static String provideMessage() {    return "hello world";  }}

Guice 根据上述配置,会自动创建 Provider<Integer> 和 Provider<String>。 其中,前者会在调用 Provider#get 时返回 3,后者返回 "hello world"。

有了前面对 Guice 的基本理解,接下来我们就能讨论如何使用 Guice 进行依赖注入了。 Guice 中的核心接口,或者说应用与 Guice 交互的核心接口是 Injector。 Injector 的创建一般通过如下的方式:

java复制代码 Injector injector = Guice.createInjector(new SomeModule());

应用中各个类之间的依赖关系,形成了一个有向图。 这些依赖关系描述在配置文件中,即继承了 AbstractModule 的类中。 Injector 了解这个依赖关系有向图中的所有信息。 当你向 Injector 请求某个实例,例如:

java复制代码injector.getInstance(String.class);

或者要求 Injector 为某个对象注入它需要的依赖时,例如:

java复制代码injector.injectMembers(someObj);

Injector 会以深度优先的方式遍历有向图,尝试为它注入所有的依赖项。

01.1-Binding

正如前面介绍的,Guice 中的配置是通过 Module 来完成的。 Module 是 Guice 中的配置单元,定义了要加入到 map 中的 key -> value 关系(在 Guice 中称为 binding)。 Guice 支持两种方式来定义、或声明 binding:

@Provides 等注解的方式,在 *Module 中的方法上使用。使用 Guice DSL,在 *Module#configure 方法中,通过 bind(..).to(..) 形式来定义。

Guice DSL

Mental Model

bind(key).toInstance(value)

map.put(key, () -> value)

bind(key).toProvider(provider)

map.put(key, provider)

bind(key).to(anotherKey)

map.put(key, map.get(anotherKey))

@Provides Foo provideFoo() {...}

map.put(Key.get(Foo.class), module::provideFoo)

前面提到的 DemoModule 中就定义了两个 binding:

bind(Key.get(Integer.class)).toInstance(3)bind(Key.get(String.class)).toInstance("hello world")

Guice 中的 binding 分为多种类型:

Linked binding,将某个类型映射到它的实现上,例如 bind(BillingService.class).to(BillingServiceImpl.class)。 或者,使用 @Provides 方式:java复制代码class DemoModule extends AbstractModule { // 等价与上面的 bind(BillingService.class).to(BillingServiceImpl.class) @Provides BillingService billingService(BillingServiceImple impl) { return impl; } }Instance Binding,将某个类型映射到它的某个具体实例上,例如bind(String.class).toInstance("hello world")。Provider Binding,将某个类型映射到能够提供该类型实例的 Provider 上,例如 bind(String.class).toProvider(() -> "hello world")。Constructor Binding,将某个类型映射到某个 Constructor 上,主要用在第三方库的类或者类中有多个 Constructor 时,用来指定使用某个特定的 Constructor。 例如:java复制代码bind(TransactionLog.class).toConstructor( DatabaseTransactionLog.class.getConstructor(DatabaseConnection.class));Built-in Binding,Guice 内置了一些开箱即用的 binding,应用可以直接使用。 如何理解这些内置 binding 呢? 借助之前的 map 类比,这些内置的 binding 就是 Guice 初始化 map 时会添加进来的 kv 对。 这些内置 binding 中常用的一些包括 Logger、Injector、已知类型的 Provider 等;Just-in-time Binding,在 *Module 中定义的的 binding 被称为是 explicit bindings; 如果 Injector 中不存在某个类型的 explicit binding,而应用又请求这个类型的实例时,Guice 会尝试为它创建 jit binding 或 implicit binding。 Guice 创建 implicit binding 时,需要用到目标类型的 injectable constructor。 injectable constructor 指:用 @Inject 标注的构造器或无参构造器,且是非私有类的非私有构造器、私有类的私有构造器(可用,但不推荐)not injectable constructor 指:有一个或多个参数,且没有 @Inject 注解@Inject 标注的构造器有多个非静态嵌套类中定义的构造器。原因是内部类对外部类对象有隐含的依赖,Guice 无法完成注入。injectable constructor 举例:java复制代码public final class Foo { // @Inject 标注的构造器 @Inject Foo(Bar bar) { } } public final class Bar { // 非私有类的非私有构造器 Bar() {} private static class Baz { // 私有类的私有构造器,也可用,但不推荐 private Baz() {} } }not injectable constructor 举例:java复制代码public final class Foo { // 构造器包含一个或多个参数,且没有 @Inject 注解 Foo(Bar bar) { } } public final class Bar { // 非私有类中的私有构造器,not injectable private Bar() {} class Baz { // 非静态内部嵌套类的构造器 Baz() {} } }01.2-Injection

Injection(注入,或依赖注入)指把依赖设置到对象中的过程(例如通过构造器、setter 方法等)。 注入有如下几种类型:

Constructor injection,在构造器上注解 @Inject,Guice 会自动注入所需依赖。Method injection,Guice 也支持在类方法上注解 @Inject。Field injection,最简洁的方式,直接注解在某个 field 上。

Injection point(注入点)指 Guice 支持的、会自动注入依赖的位置,它包括:

injectable constructor@Provides 注解的方法@Inject 注解的类方法@Inject 注解的 filed

Guice 进行依赖注入的实例:

java复制代码class Foo {  private Database database;  /*** database = map.get(Key.get(Database.class)).get() */  @Inject  Foo(Database database) {  // We need a database, from somewhere    this.database = database;  }}// 或 Module 中@ProvidesDatabase provideDatabase(    /*** databasePath = map.get(Key.get(String.class, DatabasePath.class)).get()*/    @DatabasePath String databasePath) {  return new Database(databasePath);}

在前面,我有提到 Guice 会为所有已知类型创建对应的 Provider。 所以,在注入的时候,可以选择注入特定类型的实例,也可以选择注入该类型的 Provider。 例如,上述 @Inject Foo(Database database){..} 也可以换成另一种方式 @Inejct Foo(Provider<Database> pDatabase) { this.database = pDatabase.get(); } 。

02-Guice 对 AOP 的支持

在之前的一篇文章里 Spring Boot「49」扩展:Spring AOP,我介绍了 Spring 中对 AOP 的支持。 Guice 同样支持 AOP。 简单来说,Guice 中实现 AOP 的方式是通过方法拦截。 Guice 中与 AOP 相关的接口包括 com.google.inject.matcher.Matcher 和 org.aopalliance.intercept.MethodInterceptor。 其中,前者 Matcher 一般需要两个,一个用来去定哪些类需要拦截,另一个用来确定这些类中的哪些方法需要拦截。 后者 MethodInterceptor 来自于 aopalliance 定义的 API,它会在拦截的方法比调用时调用,换句话说,它就是 advice(对这个概念比较陌生的同学,可以翻阅我之前的文章进行了解)。

Guice 官网提供了一个示例,在一个披萨订购系统中,使用 AOP 实现周末禁止下单的功能。 接下来,我将与大家一块学习下。 首先,披萨订单系统中,有一个相关的订单服务。

java复制代码/** * Pizza 订购系统 */public class PizzaBillingService {    public void chargeOrder() {        System.out.println("charge order...");    }}

它只有一个方法,就是下订单。 如果有一个需求,周末披萨店休息,不营业,我们就要修改订单系统,在休息时不接受订单。 根据这个需求,先定义一个注解,表示周末不允许的行为:

java复制代码@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface NotOnWeekends {}

之后,在 chargeOrder 上增加注解 @NotOnWeekends。

然后,定义一个拦截器:

java复制代码/** * 周末不支持下单 */public class WeekendBlocker implements MethodInterceptor {    @Override    public Object invoke(MethodInvocation invocation) throws Throwable {        final Calendar today = new GregorianCalendar();        final String displayName = today.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG, Locale.ENGLISH);        System.out.println(displayName);        // 如果是周末        if (displayName.startsWith("S")) {            throw new IllegalStateException(invocation.getMethod().getName() + " not allowed on weekends!");        }        return invocation.proceed();    }}

准备就绪后,通过 NotOnWeekendsModule 将这些组装起来。

java复制代码public class NotOnWeekendsModule extends AbstractModule {    @Override    protected void configure() {        // any() 表示任何类都要进行拦截        // annotatedWith(NotOnWeekends.class) 表示带有 @NotOnWeekends 的方法需要拦截        // 表示要执行 new WeekendBlocker() 中的逻辑        bindInterceptor(Matchers.any(), Matchers.annotatedWith(NotOnWeekends.class), new WeekendBlocker());    }}

最后,编写测试程序测试下:

java复制代码public class PizzaBillingApplication {    public static void main(String[] args) {        final Injector injector = Guice.createInjector(new NotOnWeekendsModule());        final PizzaBillingService service = injector.getInstance(PizzaBillingService.class);        service.chargeOrder();    }}

周末的时候运行上述程序,会提示:

php复制代码Exception in thread "main" java.lang.IllegalStateException: chargeOrder not allowed on weekends!	at self.samson.example.guice.pizza.WeekendBlocker.invoke(WeekendBlocker.java:21)	at self.samson.example.guice.pizza.PizzaBillingApplication.main(PizzaBillingApplication.java

非周末时,则能正常运行。

03-Guice 与 Spring IoC 的对比

Google Guice 和 Spring IoC 都提供了 DI 实现。 baeldung.com 中有一篇文章对比了两者在概念、使用方法的不同。 感兴趣的读者可以自行去了解下,限于篇幅问题,这里不再展开介绍。

另外,关于多个 DI 框架的对比,在 stackoverflow.com 上有一个高质量的帖子,感兴趣的也可以翻阅下。 Why use/develop Guice, when You have Spring and Dagger?

这里,我大致总结下这个回答中的内容。 首先,这篇帖子是关于 Dagger、Guice、Spring 三个 DI 框架的对比。 作者列出了这三个框架出现的时间线:

2004年,Spring 1.0 发布2007年,Guice 发布2009年,JSR-330 发布,定义了 javax.inject.*2013年,Dagger 1 发布(2016年废弃)2015年,Dagger 2 发布

Spring、Guice、Dagger 三个框架,就 DI 实现上,存在如下的差异:

Spring 是一个相对重量级的框架。如果你的应用采用 Spring 开发,那么使用它提供的 DI 功能不需要做额外的工作。Guice 是一个相对轻量的框架。它集成的功能比 Spring 要少。Dagger 是三者中最轻量的。它与前两者最大的不同是发生在编译时,所以运行时性能比较好,更适合于手机端(android)这种移动设备。三者都支持 JSR-330。

另外再补充一个 Guice 官网提供的对 "how does Guice compare to Spring?" 问题的回答。

Guice is anything but a rehash of Spring.

Guice 出现在 Java 5 推出之后,所以能够更充分的利用 Java 提供的注解、泛型等新特性(Spring vs. Guice)。

04-总结

在今天的文章中,我介绍了 Google 开源的 Guice 框架。 它是一个类似于 Spring IoC 的框架,提供了基于依赖注入的编程范式实现。 到目前为止,我介绍了 Guice 中的核心概念,以及使用方式。 然后,我又介绍了 Guice 对 AOP 的支持,并通过一个披萨订购服务演示了如何实现的切面增强。 最后,给出了多个 DI 实现框架的对比及一些参考资料。

希望今天的内容能对你有所帮助。如果认为对你有用,请点赞、收藏支持,谢谢!

标签: #谷歌框架框架全版本