当前位置: 首页 > 工具软件 > Guice > 使用案例 >

maven源码阅读之一(Guice介绍)

王凯旋
2023-12-01

 guice文档 

guice是google开发的一个轻量级的IOC框架,maven则使用的是guice来进行其内部的IOC操作。而guice本身是比较简单的。下面的代码展示了guice的主要功能,也展示了它主要的角色。guice内部可以看作是一个<Key,Provider>的map。通过各种module将<Key,Provider>注册到guice中,guice则将这些<Key,Provider>的binding构建一个injector给用户,而用户则通过injector利用key来获取对那个的provider来获得对象。

public class Test {
    interface A{
        void say() ;
    }
    static class B implements A{
        @Override
        public void say() {
            System.out.println("hahaha");
        }
    }
    static class TestModule extends AbstractModule{
        @Override
        protected void configure() {
            bind(Key.get(A.class)).toProvider(() -> new B()).in(Singleton.class);
        }
    }
    public static void main(String[] args){
        Injector injector = Guice.createInjector(new TestModule()) ;
        A a = injector.getInstance(A.class) ;
        a.say();
    }
}

绑定方式

Guice提供了多种的对象的绑定的方式:

  • ConstructorBinding:这种方式是直接注册一个实例对象的构造器,通过这个构造器进行对象实例化
  • LinkedBinding: 这种方式是将key绑定到另一个key上
  • ProviderInstanceBinding:绑定Provider实例
  • InstanceBinding:直接绑定实现对象
  • ProviderKeyBinding:绑定Provider的key,从injector中找prvider对象
  • UntargettedBinding:只绑定一个key,通过@ImplementedBy等注解获取对应的实现类

下面展示了不同方式绑定的实例。

public class TestBinding {
    private static class A{}
    private static class B{}
    private static class C{C(){new RuntimeException().printStackTrace();}}
    private static class D{}
    private static class E{}
    @ImplementedBy(F.class)
    private interface IF{}
    private static class F implements IF{}
    private static class P implements Provider<E> {
        @Override
        public E get() {
            System.out.println("create e") ;
            return new E();
        }
    }
    public static void main(String[] args){
        Injector injector = Guice.createInjector(new AbstractModule() {
            @Override
            protected void configure() {
                Constructor constructor = A.class.getDeclaredConstructors()[0] ;
                bind(A.class).annotatedWith(Names.named("a")).toConstructor(constructor) ; //ConstructorBinding
                bind(B.class).annotatedWith(Names.named("b")).to(B.class); //LinkedBinding
                bind(C.class).annotatedWith(Names.named("c")).toProvider(()->new C()) ; //ProviderInstanceBinding
                bind(D.class).annotatedWith(Names.named("d")).toInstance(new D());//InstanceBinding
                bind(E.class).annotatedWith(Names.named("e")).toProvider(Key.get(P.class));//ProviderKeyBinding
                bind(IF.class) ; //UntargettedBinding
            }
        }) ;
        System.out.println(injector.getInstance(Key.get(A.class,Names.named("a"))));
        System.out.println(injector.getInstance(Key.get(B.class,Names.named("b"))));
        System.out.println(injector.getInstance(Key.get(C.class,Names.named("c"))));
        System.out.println(injector.getInstance(Key.get(D.class,Names.named("d"))));
        System.out.println(injector.getInstance(Key.get(E.class,Names.named("e"))));
        System.out.println(injector.getInstance(IF.class));
    }
}

参数配置

guice主要是提供了4种参数,这4种参数的配置直接是通过binder()中的方法来进行绑定的。

  • requireAtInjectOnConstructors:在找实现类的构造器时只找@Inject注解的构造器,而不找默认的无参数的构造器
  • requireExplictBinding:这表示所有所有的绑定都需要在module中进行绑定,像@ImplementsBy,直接通过实现类Key获取对应实现类的形式都是无法获取绑定
  • disableCircularProxies:禁止循环依赖
  • requireExactBindingAnnotations: 禁止带参数的注解匹配直接注解类,如@Named("a")注解不能匹配到直接注入的Named.class

下面则分别是上面4个参数地注解和其相关地解释

//A能正常获取,B不能,因为A的构造器中显式地加了@Inject注解
public class TestAtInjectOnConstructorOption {
    private static class A{@Inject A(){}}
    private static class B{}
    public static void main(String[] args){
        Injector injector = Guice.createInjector(new AbstractModule() {
            @Override
            protected void configure() {
                binder().requireAtInjectOnConstructors();
            }
        }) ;
        System.out.println(injector.getInstance(A.class)) ;
        System.out.println(injector.getInstance(B.class)) ;
    }
}


//IB因为禁止了ExplicitBindings而不能通过ImplementedBy注入对应的实现类
//导致不能获取IB而报错
public class TestRequireExplicitBinding {
    private static class A{
        @Inject IB b ;
    }
    @ImplementedBy(B.class)
    private interface IB{}
    private static class B implements IB{}
    public static void main(String[] args){
        Injector injector = Guice.createInjector(new AbstractModule() {
            @Override
            protected void configure() {
                bind(A.class).annotatedWith(Names.named("a")).to(A.class) ;
                binder().requireExplicitBindings();
            }
        }) ;
        System.out.println(injector.getInstance(A.class)) ;
        System.out.println(injector.getInstance(IB.class));
    }
}

//A和B循环依赖了,加了disableCircularProxies配置就会直接报错
public class TestDisableCircularProxiesOption {
    private static class A{
        @Inject B b ;
    }
    private static class B{
        @Inject A a ;
    }
    public static void main(String[] args){
        Injector injector = Guice.createInjector(new AbstractModule() {
            @Override
            protected void configure() {
                binder().disableCircularProxies();
            }
        }) ;
        System.out.println(injector.getInstance(A.class)) ;
    }
}

//绑定是Named.class这种注解形式,获取的时候是Names.named("a")这种形式
//如果不加requireExactBindingAnnotations,下面是可以获取到对应对象的
public class TestExactBindingAnnotations {
    private static class A {}
    public static void main(String[] args){
        Injector injector = Guice.createInjector(new AbstractModule() {
            @Override
            protected void configure() {
                bind(A.class).annotatedWith(Named.class).to(A.class).asEagerSingleton();
                //不加这个配置,下面是可以获取数据,加了就会报错
                binder().requireExactBindingAnnotations();
            }
        }) ;
        A a = injector.getInstance(Key.get(A.class,Names.named("a"))) ;
        System.out.println(a) ;
    }
}

TypeConverterBinding

这个主要时绑定转换器,它的是通过binder的convertToTypes方法注册的,主要作用是将字符串转换为对应的需要的类型。

下面代码是使用例子,可以看到它注册了一个注解为@Names("b")的字符串,并且绑定了一个对类型为B的转换器,这个转换器可以将字符串转换为实例B。但是没有将类型B注册到Injector中,但是可以直接从injector中获取类型为B的对象。

import com.google.inject.*;
import com.google.inject.matcher.AbstractMatcher;
import com.google.inject.name.Names;
public class TestConvertBinding {
    private static class B{
        String name ;
        B(String name){this.name=name;}
    }
    public static void main(String[] args){
        Injector injector = Guice.createInjector(new AbstractModule() {
            @Override
            protected void configure() {
                binder().convertToTypes(new AbstractMatcher<TypeLiteral<?>>() {
                    @Override
                    public boolean matches(TypeLiteral<?> typeLiteral) {
                        return typeLiteral.getRawType().isAssignableFrom(B.class) ;
                    }
                }, (value, toType) -> new B(value));
                binder().bindConstant().annotatedWith(Names.named("b")).to("hahaha->b");
            }
        }) ;
        B b = injector.getInstance(Key.get(B.class,Names.named("b"))) ;
        System.out.println(b.name) ;
    }
}

ProviderLookup和MembersInjectorLookup

这两个都是Lookup,即在初始化阶段就获取了对provider和membersInjector地对象,但是在初始化阶段是不能调用对应对象地相关方法,需要在初始化后等guice内部将代理对象注册到这些对象中,才能通过这些对象掉用代理对象来进行相应地操作。

public class TestProviderLookup {
    private static class A{}
    public static void main(String[] args){
        AtomicReference<Provider> reference = new AtomicReference<>();
        Guice.createInjector(new AbstractModule() {
            @Override
            protected void configure() {
                Provider provider = binder().getProvider(Key.get(A.class, Names.named("a")));
                reference.set(provider);
                bind(A.class).annotatedWith(Names.named("a")).to(A.class) ;
            }
        }) ;
        System.out.println(reference.get().get()) ;
    }
}

public class TestMembersInjector {
    private static class A{
        @Inject @Named("b") B b ;
    }
    private static class B{}
    public static void main(String[] args){
        AtomicReference<MembersInjector> reference = new AtomicReference<>();
        Guice.createInjector(new AbstractModule() {
            @Override
            protected void configure() {
                MembersInjector<A> membersInjector = binder().getMembersInjector(A.class) ;
                reference.set(membersInjector);
                bind(B.class).annotatedWith(Names.named("b")).to(B.class).asEagerSingleton(); ;
            }
        }) ;
        A a1 = new A() ;
        reference.get().injectMembers(a1);
    }
}

StaticInjectionRequest和InjectionRequest

这两个都是进行属性注入操作,不同的是StaticInjectionRequest是对静态变量进行属性注入,而InjectionRequest则是对已经构建好的对象进行属性注入。

下面代码展示的是这两种不同的注入方式,可以看到两个都是再A类中注入B。不同的是上面是静态的属性B,并且传入的是A的class对象,而下面传入的是A对象,其也是对A对象进行属性注入。

public class TestStaticInjection {
    static class A{
        @Inject
        static B b ;
    }
    static class B{
    }
    public static void main(String[] args){
        Guice.createInjector(new AbstractModule() {
            @Override
            protected void configure() {
                binder().bind(B.class).annotatedWith(Names.named("foo")).to(B.class) ;
                binder().requestStaticInjection(A.class);
            }
        });
        System.out.println(A.b);
    }
}

public class TestInjectionRequest {
    private static class A{
        @Inject private B b ;
    }
    private static class B{}
    public static void main(String[] args){
        final A a = new A() ;
        Guice.createInjector(new AbstractModule() {
            @Override
            protected void configure() {
                binder().requestInjection(a);
                bind(B.class).annotatedWith(Names.named("b")).to(B.class) ;
            }
        });
        System.out.println(a.b);
   }
}

ScopeBinding

这个主要是可以自定义scope,guice自身提供地注解主要就是singleton和noScope两种,像maven就会自定义sessionScope来进行扩充,guice子定义scope主要是定义一个带有@ScopeAnnotation注解地注解,并且将这个注解绑定到实现了Scope类地对象中,guie就会对带有这个注解的对象通过注册的Scope对象来进行管理。

下面例子注册了一个@CacheAnno注解的scope,这个scope主要是维护一个cache,对里面的对象实现1秒钟过期的策略,可以看到在一次获取的三个A对象中,前面两个A对象是通过一个对象,而最后一个对象则不同,因为中间等了2秒,缓存消失了,故新创建了一个对象。

public class TestScopeBinder {
    private static class CacheScope implements Scope{
        Cache<Key,Object> cache = CacheBuilder.newBuilder().expireAfterAccess(1, TimeUnit.SECONDS).build();
        @Override
        public <T> Provider<T> scope(Key<T> key, Provider<T> unscoped) {
            return ()->{
                try {
                    return (T) cache.get(key,()->unscoped.get());
                } catch (ExecutionException e) {
                    throw new RuntimeException(e);
                }
            };
        }
    }
    @Retention(RUNTIME)
    @ScopeAnnotation
    private @interface CacheAnno{
    }
    @CacheAnno
    private static class A{}
    public static void main(String[] args) throws InterruptedException {
        Injector injector = Guice.createInjector(new AbstractModule() {
            @Override
            protected void configure() {
                bindScope(CacheAnno.class,new CacheScope());
            }
        });
        System.out.println(injector.getInstance(A.class));
        System.out.println(injector.getInstance(A.class));
        Thread.sleep(2000);
        System.out.println(injector.getInstance(A.class));
    }
}

TypeListenerBinding和ProvisionListenerBinding

这两种类主要是在构建和属性赋值期间进行相关的处理。下面展示了相关例子,以及对应的注释

/**
 * TypeListener主要是在构建binding对应的MembersInjectorImpl时调用hear方法
 * 在hear方法中注册了属性注册的后处理器,即在对应的属性注册完成后进行的操作
 * 并且注册了MembersInjector,正内部的memberInjector对A注入了B后进行了相关操纵又将A中的b属性赋值为空
 */
public class TestTypeListener {
    private static class A{
        @Inject  B b ;
    }
    @Singleton
    static class B{}
    public static void main(String[] args){
        Injector injector = Guice.createInjector(new AbstractModule() {
            @Override
            protected void configure() {
                binder().bindListener(Matchers.any(),new TypeListener(){
                    //hear方法在构建MembersInjectorImpl的时候进行调用
                    @Override
                    public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {
                        //此处注册的InjectionListener在每次所有的属性注册结束后进行调用
                        encounter.register((InjectionListener<I>) injectee -> {
                            System.out.println("after inject " + injectee) ;
                        });
                        encounter.register((MembersInjector<I>) instance -> {
                             if(instance instanceof A){
                                 A a = (A)instance ;
                                 System.out.println("a.b = " + a.b);
                                 a.b = null ;
                             }
                        });
                    }
                });
            }
        }) ;
        A a = injector.getInstance(A.class) ;
        System.out.println(a.b) ;
    }
}



public class TestProvisionListener {
    static class A{
        @Inject  B b ;
        A(){System.out.println("construct a");}
    }
    static class B{
        B(){System.out.println("construct b");}
    }
    public static void main(String[] args){
        Injector injector = Guice.createInjector(new AbstractModule() {
            @Override
            protected void configure() {
                binder().bindListener(Matchers.any(), new ProvisionListener() {
                    @Override
                    public <T> void onProvision(ProvisionInvocation<T> provision) {
                        System.out.println("=====before=====" + provision.getBinding());
                        provision.provision();
                        System.out.println("=====after========" + provision.getBinding());
                    }
                });
            }
        });
        injector.getInstance(A.class) ;
    }
}

PrivateElement

这主要是在当前的Binder下构建一个privateBinder,然后将不想外部能直接访问的binding注册到这个privateBinder中,而这个privateBinder最终会构建为一个当前injector的子injector,在当前的injector中无法直接访问这个子injector的对象,但是这个子injector对象内部是可以互相注入的,并且其还可以通过expose方法将其想要外部可以访问的对象暴露出去。

下面即为这种情况的例子,并且注释中对其进行了描述

/**
 * 此处A,B都在PrivateBinder中注册的,其实际是注册进了当前injector的子injector中
 * 当前的Injector是无法直接获取两个对象,但是这两个对象因为是自身在同一个子injector中
 * 故此他们之直接是可以互相注册的,所有B对象中是可以注册了A的,并且其expose了B,所以能在外部
 * 获取B对象,但是无法直接获取A对象
 */
public class TestPrivateElement {
    @Singleton
    private static class A{}
    private static class B{
        @Named("a") @Inject A a;
    }
    public static void main(String[] args){
        Injector injector = Guice.createInjector(new AbstractModule() {
            @Override
            protected void configure() {
                PrivateBinder binder = binder().newPrivateBinder();
                binder.bind(A.class).annotatedWith(Names.named("a")).to(A.class) ;
                binder.bind(B.class).annotatedWith(Names.named("b")).to(B.class) ;
                binder.expose(Key.get(B.class,Names.named("b")));
            }
        });

        B b = injector.getInstance(Key.get(B.class,Names.named("b"))) ;
        System.out.println(b.a);
        System.out.println(injector.getInstance(Key.get(A.class,Names.named("a")))) ;
    }
}

ProviderMethod

guice可以通过在module的实现类中利用@Provides,@ProvidesIntoSet,@ProvidesIntoMap等注解的形式注册binding,这种注册方式最终会将其转换为ProviderMethod的形式,ProviderMethod则会通过反射这些方法来获取这些数据。对于@ProvidesIntoSet这些集合的注解,其在注入单个binding时会将其加上一个@Element注解来区分单个binding,并且通过ModuleAnnotatedMethodScanner将其对应的集合的<Key,Provider>的binding注入。

public class TestModuleAnnotatedMethodScanner {
    private static class B{
    }
    public static void main(String[] args){
        Injector injector = Guice.createInjector(new AbstractModule() {
            @ProvidesIntoSet private String a(){return "a";}
            @ProvidesIntoSet private String b(){return "b";}

            @StringMapKey("bb")
            @ProvidesIntoMap
            private B bb(){return new B() ; }

            @StringMapKey("bb2")
            @ProvidesIntoMap
            private B bb2(){return new B() ; }

            @Provides
            private B bb3(){return new B();}

        });
        System.out.println(injector.getInstance(Key.get(new TypeLiteral<Map<String,B>>(){})));
        System.out.println(injector.getInstance(Key.get(new TypeLiteral<Set<String>>(){})));
        System.out.println(injector.getInstance(B.class));
    }
}

 

 类似资料: