• Guice 4.1教程


    Guice是Google开发的一个开源轻量级的依赖注入框架,运行速度快,使用简单。

    项目地址:https://github.com/google/guice/

    最新的版本是4.1,本文基于此版本。

    0. 什么是依赖注入?

    依赖注入(Dependency Injection)是一种思想。

    在一般的编程中,如果我们想要使用一个class的实例,那么必须要调用构造方法new class()手动将其实例化,如果这个class中又有对其他class属性的引用,那么在构造方法中,又要调用其他class的构造方法将其实例化。

    这就是“依赖”。

    Guice这种依赖注入框架的作用就是接管class之间的依赖关系,如果我们想要使用一个class的实例,Guice会自动生成实现类的实例并交给我们。

    这个过程就叫做“注入”。

    1. 利用Module的依赖注入

    //定义Dog接口
    public interface Dog {
        void bark();
    }
    
    //定义BlackDog实现类
    public class BlackDog implements Dog{
        @Override
        public void bark() {
            System.out.println("i am black dog");
        }
    }
    
    //依赖注入
    public class GuiceTest {
        public static void main(String[] args) {
            Injector injector = Guice.createInjector(new Module() {
                @Override
                public void configure(Binder binder) {
                    binder.bind(Dog.class).to(BlackDog.class);
                }
            });
            Dog dog = injector.getInstance(Dog.class);
            dog.bark();
        }
    }

    输出结果如下

    i am black dog

    这个例子非常简单,先在自定义的Module中将Dog接口与BlackDog实现类关联起来。此时如果调用getInstance方法想要获取Dog接口的实例,就自动创建一个BlackDog的实例并返回。

    2. 绑定到实例的依赖注入

    public static void main(String[] args) {
            Injector injector = Guice.createInjector(new Module() {
                @Override
                public void configure(Binder binder) {
                    binder.bind(Dog.class).toInstance(new BlackDog());
                }
            });
    
            Dog dog = injector.getInstance(Dog.class);
            dog.bark();
        }

    可以看到Dog接口已经与某个BlackDog的实例绑定起来了。

    3. 利用Provider的依赖注入

        public static void main(String[] args) {
            Injector injector = Guice.createInjector(new Module() {
                @Override
                public void configure(Binder binder) {
                    binder.bind(Dog.class).toProvider(new Provider<Dog>() {
                        Dog dog = new BlackDog();
    
                        @Override
                        public Dog get() {
                            return dog;
                        }
                    });
                }
            });
    
            Dog dog = injector.getInstance(Dog.class);
            dog.bark();
        }

    换了一种方法的绑定,在自定义的Provider类中,我们可以执行一些复杂的操作。

    4. 基于注解的依赖注入

    可以在接口上添加@ImplementedBy完成绑定操作

    @ImplementedBy(BlackDog.class)
    public interface Dog {
        void bark();
    }
    
    public class BlackDog implements Dog{
        @Override
        public void bark() {
            System.out.println("i am black dog");
        }
    }
    
    public class GuiceTest {
        public static void main(String[] args) {
            Injector injector = Guice.createInjector();
            Dog dog = injector.getInstance(Dog.class);
            dog.bark();
        }
    }

    可以看到没有为Guice指定任何Module,也完成了依赖注入的操作

    当然,也可以用@ProvidedBy注解完成绑定操作

    @ProvidedBy(BlackDogProvider.class)
    public interface Dog {
        void bark();
    }
    
    public class BlackDogProvider implements Provider<Dog> {
        @Override
        public Dog get() {
            return new BlackDog();
        }
    }
    
    public class GuiceTest {
        public static void main(String[] args) {
            Injector injector = Guice.createInjector();
            Dog dog = injector.getInstance(Dog.class);
            dog.bark();
        }
    }

    基于注解的依赖注入其实与基于Module的依赖注入差别不大,只是口味上的区别

    但是Module里的配置会覆盖注解里的配置

    5. 单例

    在默认情况下,每次调用getInstance方法,都会创建一个新的对象并返回(Provider注入与实例注入除外)

    @ImplementedBy(BlackDog.class)
    public interface Dog {
        void bark();
    }
    
    public class BlackDog implements Dog{
        @Override
        public void bark() {
            System.out.println("i am black dog");
        }
    }
    
    public class GuiceTest {
        public static void main(String[] args) {
            Injector injector = Guice.createInjector();
            for (int i = 0; i < 10; i++) {
                System.out.println(injector.getInstance(Dog.class).hashCode());
            }
        }
    }

    调用10次getInstance方法,分别计算得到对象的hashCode,结果如下

    1709366259
    1335298403
    1643691748
    2068434592
    143110009
    2142003995
    1535634836
    1846412426
    1539805781
    1206883981

    可以看出,每次调用getInstance方法,都会生成一个全新的对象。

    如果我们希望返回的是同一个对象(单例模式),那么只需要在实现类上加一个@Singleton注解

    @ImplementedBy(BlackDog.class)
    public interface Dog {
        void bark();
    }
    
    @Singleton
    public class BlackDog implements Dog{
        @Override
        public void bark() {
            System.out.println("i am black dog");
        }
    }
    
    public class GuiceTest {
        public static void main(String[] args) {
            Injector injector = Guice.createInjector();
            for (int i = 0; i < 10; i++) {
                System.out.println(injector.getInstance(Dog.class).hashCode());
            }
        }
    }

    运行结果如下:

    1632492873
    1632492873
    1632492873
    1632492873
    1632492873
    1632492873
    1632492873
    1632492873
    1632492873
    1632492873

    可以看到@Singleton注解已经生效,调用getInstance方法返回的对象已经是单例的了

    6. 属性注入

    Guice可以在某个类初始化时,为它的属性自动注入实例,无需我们手动赋值

    @ImplementedBy(BlackDog.class)
    public interface Dog {
        void bark();
    }
    
    public class BlackDog implements Dog{
        @Override
        public void bark() {
            System.out.println("i am black dog");
        }
    }
    
    public class GuiceTest {
        @Inject
        private Dog dog;
    
        public static void main(String[] args) {
            Injector injector = Guice.createInjector();
    
            GuiceTest guiceTest = injector.getInstance(GuiceTest.class);
            guiceTest.dog.bark();
        }
    }

    程序的输出结果为:

    i am black dog

    可以看到,通过getInstance方法创建出来的GuiceTest实例中的dog属性,已经被自动注入了一个BlackDog实例。

    这就是@Inject注解的效果,如果dog属性没有这个@Inject注解,程序会抛出NullPointerException

    7. 构造函数注入

    如果在构造函数上添加@Inject注解,那么可以实现在对象初始化的时候自动提供参数,代码如下

    public class GuiceTest {
        private Dog dog;
    
        @Inject
        public GuiceTest(Dog dog) {
            this.dog = dog;
        }
    
        public static void main(String[] args) {
            Injector injector = Guice.createInjector();
    
            GuiceTest guiceTest = injector.getInstance(GuiceTest.class);
            guiceTest.dog.bark();
        }
    }

    在调用getInstance获取GuiceTest实例的时候,会自动调用唯一的GuiceTest构造方法并传入参数

    需要注意的是,此时必须有且只有一个带有@Inject注解的构造方法,否则会抛出类似如下的异常:

    GuiceTest has more than one constructor annotated with @Inject. Classes must have either one (and only one) constructor annotated with @Inject or a zero-argument constructor that is not private.

    8. Setter注入

    与之前提到的属性注入非常类似,只需要在setter方法上添加一个@Inject注解即可。

    public class GuiceTest {
        private Dog dog;
    
        public Dog getDog() {
            return dog;
        }
    
        @Inject
        public void setDog(Dog dog) {
            this.dog = dog;
        }
    
        public static void main(String[] args) {
            Injector injector = Guice.createInjector();
    
            GuiceTest guiceTest = injector.getInstance(GuiceTest.class);
            guiceTest.dog.bark();
        }
    }

    9. 静态变量注入

    上面的注入都是针对某个类的非静态属性进行注入,如果我们对某个类的静态属性进行注入,会发生什么呢?

    public class GuiceTest {
        @Inject private static Dog dog;
    
        public static void main(String[] args) {
            Injector injector = Guice.createInjector();
    
            GuiceTest guiceTest = injector.getInstance(GuiceTest.class);
            guiceTest.dog.bark();
        }
    }

    程序的执行结果是:

    Exception in thread "main" java.lang.NullPointerException

    很显然,注入失败了。因为getInstance方法是针对某个类的实例进行注入,而类的静态属性是被这个类的所有对象全局共享的,所以直接注入会失败。

    正确的注入姿势如下:

    public class GuiceTest {
        @Inject private static Dog dog;
    
        public static void main(String[] args) {
            Injector injector = Guice.createInjector(new Module() {
                @Override
                public void configure(Binder binder) {
                    binder.requestStaticInjection(GuiceTest.class);
                }
            });
            
            GuiceTest.dog.bark();
        }
    }

    程序的执行结果如下:

    i am black dog

    注入成功!

    虽然目前还不知道具体的实现原理,但是可以猜出Guice一开始就直接往GuiceTest的静态变量里注入了对象。

    10. 直接注入属性

    还有一种简单暴力的方式来完成属性的依赖注入

    public class GuiceTest {
        @Inject private Dog dog;
    
        public static void main(String[] args) {
            Injector injector = Guice.createInjector();
    
            GuiceTest guiceTest = new GuiceTest();
            injector.injectMembers(guiceTest);
            guiceTest.dog.bark();
        }
    }

    对GuiceTest的实例直接调用injectMembers方法,完成对实例的属性的依赖注入

    11. 单接口多实例

    如果一个接口有多个实现类,Guice提供了一种非常灵活的注入方案

    public interface Dog {
        void bark();
    }
    
    public class BlackDog implements Dog{
        @Override
        public void bark() {
            System.out.println("i am black dog");
        }
    }
    
    public class WhiteDog implements Dog {
        @Override
        public void bark() {
            System.out.println("i am white dog");
        }
    }
    
    @BindingAnnotation
    @Target({FIELD, PARAMETER, METHOD})
    @Retention(RUNTIME)
    public @interface Black {
    }
    
    @BindingAnnotation
    @Target({FIELD, PARAMETER, METHOD})
    @Retention(RUNTIME)
    public @interface White {
    }
    
    public class GuiceTest {
        @Inject @Black private Dog dog;
    
        public static void main(String[] args) {
            Injector injector = Guice.createInjector(new Module() {
                @Override
                public void configure(Binder binder) {
                    binder.bind(Dog.class).annotatedWith(White.class).to(WhiteDog.class);
                    binder.bind(Dog.class).annotatedWith(Black.class).to(BlackDog.class);
                }
            });
    
            GuiceTest guiceTest = new GuiceTest();
            injector.injectMembers(guiceTest);
            guiceTest.dog.bark();
        }
    }

    运行结果如下:

    i am black dog

    额外定义了@Black与@White注解,然后在Module里定义了两条规则,这样被@Black注解修饰的dog属性就被注入了BlackDog的依赖。

    非常灵活,在特定场景下十分有用。

    比方说可能实现类分为Debug与Release两类,调试的时候所有待注入的属性都用@Debug注解修饰,发布前将所有@Debug替换成@Release即可。

    12. @Named注解

    如果懒得额外定义注解,那么可以直接使用Guice提供的@Named注解完成相似的功能。

    public class GuiceTest {
        @Inject @Named("Black") private Dog dog;
    
        public static void main(String[] args) {
            Injector injector = Guice.createInjector(new Module() {
                @Override
                public void configure(Binder binder) {
                    binder.bind(Dog.class).annotatedWith(Names.named("White")).to(WhiteDog.class);
                    binder.bind(Dog.class).annotatedWith(Names.named("Black")).to(BlackDog.class);
                }
            });
    
            GuiceTest guiceTest = new GuiceTest();
            injector.injectMembers(guiceTest);
            guiceTest.dog.bark();
        }
    }

    可以看到@Named注解也能完成指定实现类的关联工作

    总结

    本文简单介绍了Guice这个轻量级的依赖注入框架的基本使用方法。

    虽然简单,但是也展现了依赖注入思想的精华之处:由容器来解决依赖对象的构建问题,程序员只需要提供属性的关联关系即可。使得程序松耦合,减少代码之间的依赖关系,便于后续的修改。

    更详细的信息,请参见官方Wiki

  • 相关阅读:
    查找算法(I) 顺序查找 二分查找 索引查找
    快速排序 Quick Sort
    Activity生命周期
    Android中资源文件的使用
    排序算法
    插入排序(I)Insert Sort
    Java eclipse调试技巧什么的。。
    HTTP协议解析
    python技巧26[str+unicode+codecs]
    python类库26[PySide之helloworld]
  • 原文地址:https://www.cnblogs.com/stevenczp/p/7326953.html
Copyright © 2020-2023  润新知