背景

在日常写一些小工具或者小项目的时候,有依赖管理和依赖注入的需求,但是​​Spring(Boot)​​​体系作为​​DI​​​框架过于重量级,于是需要调研一款微型的​​DI​​​框架。​​Guice​​​是​​Google​​​出品的一款轻量级的依赖注入框架,使用它有助于解决项目中的依赖注入问题,提高了可维护性和灵活性。相对于重量级的​​Spring(Boot)​​​体系,​​Guice​​​项目只有一个小于​​1MB​​​的核心模块,如果核心需求是​​DI​​​(其实​​Guice​​​也提供了很低层次的​​AOP​​​实现),那么​​Guice​​应该会是一个合适的候选方案。


在查找Guice相关资料的时候,见到不少介绍文章吐槽Guice过于简陋,需要在Module中注册接口和实现的链接关系,显得十分简陋。原因是:Guice是极度精简的DI实现,没有提供Class扫描和自动注册的功能。下文会提供一些思路去实现ClassPath下的Bean自动扫描方案


依赖引入与入门示例

​Guice​​​在​​5.x​​​版本后整合了低版本的扩展类库,目前使用其所有功能只需要引入一个依赖即可,当前(​​2022-02​​前后)最新版本依赖为:

<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>5.1.0</version>
</dependency>

一个入门例子如下:

public class GuiceDemo {

public static void main(String[] args) throws Exception {
Injector injector = Guice.createInjector(new DemoModule());
Greeter first = injector.getInstance(Greeter.class);
Greeter second = injector.getInstance(Greeter.class);
System.out.printf("first hashcode => %s\n", first.hashCode());
first.sayHello();
System.out.printf("second hashcode => %s\n", second.hashCode());
second.sayHello();
}

@Retention(RUNTIME)
public @interface Count {

}

@Retention(RUNTIME)
public @interface Message {

}

@Singleton
public static class Greeter {

private final String message;

private final Integer count;

@Inject
public Greeter(@Message String message,
@Count Integer count) {
this.message = message;
this.count = count;
}

public void sayHello() {
for (int i = 1; i <= count; i++) {
System.out.printf("%s,count => %d\n", message, i);
}
}
}

public static class DemoModule extends AbstractModule {

@Override
public void configure() {
// bind(Greeter.class).in(Scopes.SINGLETON);
}

@Provides
@Count
public static Integer count() {
return 2;
}

@Provides
@Count
public static String message() {
return "vlts.cn";
}
}
}

执行​​main​​方法控制台输出:

first hashcode => 914507705
vlts.cn,count => 1
vlts.cn,count => 2
second hashcode => 914507705
vlts.cn,count => 1
vlts.cn,count => 2

​Greeter​​​类需要注册为单例,​​Guice​​​中注册的实例如果不显式指定为单例,默认都是原型(​​Prototype​​​,每次重新构造一个新的实例)。​​Guice​​注册一个单例目前来看主要有三种方式:

  • 方式一:在类中使用注解​​@Singleton​​​(使用​​Injector#getInstance()​​会懒加载单例)
@Singleton
public static class Greeter {
......
}
  • 方式二:注册绑定关系的时候显式指定​​Scope​​​为​​Scopes.SINGLETON​
public static class DemoModule extends AbstractModule {

@Override
public void configure() {
bind(Greeter.class).in(Scopes.SINGLETON);
// 如果Greeter已经使用了注解@Singleton可以无需指定in(Scopes.SINGLETON),仅bind(Greeter.class)即可
}
}
  • 方式三:组合使用注解​​@Provides​​​和​​@Singleton​​​,效果类似于​​Spring​​​中的​​@Bean​​注解
public static class SecondModule extends AbstractModule {

@Override
public void configure() {
// config module
}

@Provides
@Singleton
public Foo foo() {
return new Foo();
}
}

public static class Foo {

}

上面的例子中,如果​​Greeter​​​类不使用​​@Singleton​​​,同时注释掉​​bind(Greeter.class).in(Scopes.SINGLETON);​​​,那么执行​​main​​​方法会发现两次从注入器中获取到的实例的​​hashCode​​​不一致,也就是两次从注入器中获取到的都是重新创建的实例(​​hashCode​​不相同):

轻量级DI框架Guice使用详解_ide

​Guice​​中所有单例默认是懒加载的,理解为单例初始化使用了​懒汉模式​,可以通过​​ScopedBindingBuilder#asEagerSingleton()​​标记单例为饥饿加载模式,可以理解为切换单例加载模式为​饿汉模式

Guice注入器初始化

​Guice​​​注入器接口​​Injector​​​是其核心​​API​​​,类比为​​Spring​​​中的​​BeanFactory​​​。​​Injector​​​初始化依赖于一或多个模块(​​com.google.inject.Module​​​)的实现。初始化​​Injector​​的示例如下:

public class GuiceInjectorDemo {

public static void main(String[] args) throws Exception {
Injector injector = Guice.createInjector(
new FirstModule(),
new SecondModule()
);
// injector.getInstance(Foo.class);
}

public static class FirstModule extends AbstractModule {

@Override
public void configure() {
// config module
}
}

public static class SecondModule extends AbstractModule {

@Override
public void configure() {
// config module
}
}
}

​Injector​​​支持基于当前实例创建子​​Injector​​​实例,类比于​​Spring​​​中的父子​​IOC​​容器:

public class GuiceChildInjectorDemo {

public static void main(String[] args) throws Exception {
Injector parent = Guice.createInjector(
new FirstModule()
);
Injector childInjector = parent.createChildInjector(new SecondModule());
}

public static class FirstModule extends AbstractModule {

@Override
public void configure() {
// config module
}
}

public static class SecondModule extends AbstractModule {

@Override
public void configure() {
// config module
}
}
}

子​​Injector​​​实例会继承父​​Injector​​​实例的所有状态(所有绑定、​​Scope​​、拦截器和转换器等)。

Guice心智模型


心智模型(Mental Model)的概念来自于认知心理学,心智模型指的是指认知主体运用概念对自身体验进行判断与分类的一种惯性化的心理机制或既定的认知框架


​Guice​​​在认知上可以理解为一个​​map​​​(文档中表示为​​map[^guice-map]​​​),应用程序代码可以通过这个​​map​​​声明和获取应用程序内的依赖组件。这个​​Guice Map​​​每一个​​Map.Entry​​有两个部分:

  • ​Guice Key​​​:​​Guice Map​​​中的键,用于获取该​​map​​中特定的值
  • ​Provider​​​:​​Guice Map​​中的值,用于创建应用于应用程序内的(组件)对象

这个抽象的​​Guice Map​​有点像下面这样的结构:

// com.google.inject.Key => com.google.inject.Provider
private final ConcurrentMap<Key<?>, Provider<?>> guiceMap = new ConcurrentHashMap<>();

​Guice Key​​​用于标识​​Guice Map​​​中的一个依赖组件,这个键是全局唯一的,由​​com.google.inject.Key​​​定义。鉴于​​Java​​​里面没有形参(也就是方法的入参列表或者返回值只有顺序和类型,没有名称),所以很多时候在构建​​Guice Key​​​的时候既需要依赖组件的类型,无法唯一确定组件类型的时候(例如一些定义常量的场景,只要满足常量的场景,对于类实例也是可行的),需要额外增加一个自定义注解用于生成组合的唯一标识​​Type + Annotation(Type)​​。例如:

  • ​@Message String​​​相当于​​Key<String>​
  • ​@Count int​​​相当于​​Key<Integer>​
public class GuiceMentalModelDemo {

public static void main(String[] args) {
Injector injector = Guice.createInjector(new EchoModule());
EchoService echoService = injector.getInstance(EchoService.class);
}

@Qualifier
@Retention(RUNTIME)
public @interface Count {

}

@Qualifier
@Retention(RUNTIME)
public @interface Message {

}

public static class EchoModule extends AbstractModule {

@Override
public void configure() {
bind(EchoService.class).in(Scopes.SINGLETON);
}

@Provides
@Message
public String messageProvider() {
return "foo";
}

@Provides
@Count
public Integer countProvider() {
return 10087;
}
}

public static class EchoService {

private final String messageValue;

private final Integer countValue;

@Inject
public EchoService(@Message String messageValue, @Count Integer countValue) {
this.messageValue = messageValue;
this.countValue = countValue;
}
}
}

​Guice​​注入器创建单例的处理逻辑类似于:

String messageValue = injector.getInstance(Key.get(String.class, Message.class));
Integer countValue = injector.getInstance(Key.get(Integer.class, Count.class));
EchoService echoService = new EchoService(messageValue, countValue);

这里的注解​​@Provides​​​在​​Guice​​​中的实现对应于​​Provider​​接口,该接口的定义十分简单:

interface Provider<T> {

/** Provides an instance of T.**/
T get();
}

​Guice Map​​​中所有的值都可以理解为一个​​Provider​​的实现,例如上面的例子可以理解为:

// messageProvider.get() => 'foo'
Provider<String> messageProvider = () -> EchoModule.messageProvider();
// countProvider.get() => 10087
Provider<Integer> countProvider = () -> EchoModule.countProvider();

依赖搜索和创建的过程也是根据条件创建​​Key​​​实例,然后在​​Guice Map​​​中定位唯一的于​​Provider​​​,然后通过该​​Provider​​​完成依赖组件的实例化,接着完成后续的依赖注入动作。这个过程在​​Guice​​文档中使用了一个具体的表格进行说明,这里贴一下这个表格:

​Guice DSL​​语法

对应的模型

​bind(key).toInstance(value)​

【​​instance binding​​】​​map.put(key,() -> value)​

​bind(key).toProvider(provider)​

【​​provider binding​​】​​map.put(key, provider)​

​bind(key).to(anotherKey)​

【​​linked binding​​】​​map.put(key, map.get(anotherKey))​

​@Provides Foo provideFoo(){...}​

【​​provider method binding​​】​​map.put(Key.get(Foo.class), module::provideFoo)​

​Key​​​实例的创建有很多衍生方法,可以满足单具体类型、具体类型加注解等多种实例化方式。依赖注入使用​​@Inject​​​注解,支持成员变量和构造注入,一个接口由多个实现的场景可以通过内建​​@Named​​​注解或者自定义注解指定具体注入的实现,但是需要在构建绑定的时候通过​​@Named​​注解或者自定义注解标记具体的实现。例如:

public class GuiceMentalModelDemo {

public static void main(String[] args) {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
public void configure() {
bind(MessageProcessor.class)
.annotatedWith(Names.named("firstMessageProcessor"))
.to(FirstMessageProcessor.class)
.in(Scopes.SINGLETON);
bind(MessageProcessor.class)
.annotatedWith(Names.named("secondMessageProcessor"))
.to(SecondMessageProcessor.class)
.in(Scopes.SINGLETON);
}
});
MessageClient messageClient = injector.getInstance(MessageClient.class);
messageClient.invoke("hello world");
}

interface MessageProcessor {

void process(String message);
}

public static class FirstMessageProcessor implements MessageProcessor {

@Override
public void process(String message) {
System.out.println("FirstMessageProcessor process message => " + message);
}
}

public static class SecondMessageProcessor implements MessageProcessor {

@Override
public void process(String message) {
System.out.println("SecondMessageProcessor process message => " + message);
}
}

@Singleton
public static class MessageClient {

@Inject
@Named("secondMessageProcessor")
private MessageProcessor messageProcessor;

public void invoke(String message) {
messageProcessor.process(message);
}
}
}

// 控制台输出:SecondMessageProcessor process message => hello world

​@Named​​​注解这里可以换成任意的自定义注解实现,不过注意自定义注解需要添加元注解​​@javax.inject.Qualifier​​​,最终的效果是一致的,内置的​​@Named​​​就能满足大部分的场景。最后,每个组件注册到​​Guice​​中,该组件的所有依赖会形成一个有向图,注入该组件的时候会递归注入该组件自身的所有依赖,这个遍历注入流程遵循​深度优先​。​​Guice​​​会校验组件的依赖有向图的合法性,如果该有向图是非法的,会抛出​​CreationException​​异常。

Guice支持的绑定

​Guice​​​提供​​AbstractModule​​​抽象模块类给使用者继承,覆盖​​configure()​​​方法,通过​​bind()​​​相关​​API​​创建绑定。


Guice中的Binding其实就是前面提到的Mental Model中Guice Map中的键和值的映射关系,Guice提供多种注册这个绑定关系的API


这里仅介绍最常用的绑定类型:

  • ​Linked Binding​
  • ​Instance Binding​
  • ​Provider Binding​
  • ​Constructor Binding​
  • ​Untargeted Binding​
  • ​Multi Binding​
  • ​JIT Binding​

Linked Binding

​Linked Binding​​用于映射一个类型和此类型的实现类型,使用起来如下:

bind(接口类型.class).to(实现类型.class);

具体例子:

public class GuiceLinkedBindingDemo {

public static void main(String[] args) {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
public void configure() {
bind(Foo.class).to(Bar.class).in(Scopes.SINGLETON);
}
});
Foo foo = injector.getInstance(Foo.class);
}

interface Foo {

}

public static class Bar implements Foo {

}
}

​Linked Binding​​​常用于这种一个接口一个实现的场景。目标类型上添加了​​@Singleton​​​注解,那么编程式注册绑定时候可以无需调用​​in(Scopes.SINGLETON)​​。

Instance Binding

​Instance Binding​​用于映射一个类型和此类型的实现类型​实例​,也包括常量的绑定。以前一小节的例子稍微改造成​​Instance Binding​​的模式如下:

final Bar bar = new Bar();
bind(Foo.class).toInstance(bar);

# 或者添加Named注解
bind(Foo.class).annotatedWith(Names.named("bar")).toInstance(bar);

# 常量绑定
bindConstant().annotatedWith(Names.named("key")).to(value);

可以基于这种方式进行常量的绑定,例如:

public class GuiceInstanceBindingDemo {

public static void main(String[] args) {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
public void configure() {
bind(String.class).annotatedWith(Names.named("host")).toInstance("localhost");
bind(Integer.class).annotatedWith(Names.named("port")).toInstance(8080);
bindConstant().annotatedWith(Protocol.class).to("HTTPS");
bind(HttpClient.class).to(DefaultHttpClient.class).in(Scopes.SINGLETON);
}
});
HttpClient httpClient = injector.getInstance(HttpClient.class);
httpClient.print();
}

@Qualifier
@Retention(RUNTIME)
public @interface Protocol {

}

interface HttpClient {

void print();
}

public static class DefaultHttpClient implements HttpClient {

@Inject
@Named("host")
private String host;

@Inject
@Named("port")
private Integer port;

@Inject
@Protocol
private String protocol;

@Override
public void print() {
System.out.printf("host => %s, port => %d, protocol => %s\n", host, port, protocol);
}
}
}
// 输出结果:host => localhost, port => 8080, protocol => HTTPS

Provider Binding

​Provider Binding​​​,可以指定某个类型和该类型的​​Provider​​​实现类型进行绑定,有点像设计模式中的简单工厂模式,可以类比为​​Spring​​​中的​​FactoryBean​​接口。举个例子:

public class GuiceProviderBindingDemo {

public static void main(String[] args) throws Exception {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
public void configure() {
bind(Key.get(Foo.class)).toProvider(FooProvider.class).in(Scopes.SINGLETON);
}
});
Foo s1 = injector.getInstance(Key.get(Foo.class));
Foo s2 = injector.getInstance(Key.get(Foo.class));
}

public static class Foo {

}

public static class FooProvider implements Provider<Foo> {

private final Foo foo = new Foo();

@Override
public Foo get() {
System.out.println("Get Foo from FooProvider...");
return foo;
}
}
}
// Get Foo from FooProvider...


这里也要注意,如果标记Provider为单例,那么在Injector中获取创建的实例,只会调用一次get()方法,也就是懒加载


​@Provides​​​注解是​​Provider Binding​​​一种特化模式,可以在自定义的​​Module​​​实现中添加使用了​​@Provides​​​注解的返回对应类型实例的方法,这个用法跟​​Spring​​​里面的​​@Bean​​注解十分相似。一个例子如下:

public class GuiceAnnotationProviderBindingDemo {

public static void main(String[] args) throws Exception {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
public void configure() {

}

@Singleton
@Provides
public Foo fooProvider() {
System.out.println("init Foo from method fooProvider()...");
return new Foo();
}
});
Foo s1 = injector.getInstance(Key.get(Foo.class));
Foo s2 = injector.getInstance(Key.get(Foo.class));
}

public static class Foo {

}
}
// init Foo from method fooProvider()...

Constructor Binding

​Constructor Binding​​​需要显式绑定某个类型到其实现类型的一个明确入参类型的构造函数,目标构造函数不需要使用​​@Inject​​注解。例如:

public class GuiceConstructorBindingDemo {

public static void main(String[] args) throws Exception {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
public void configure() {
try {
bind(Key.get(JdbcTemplate.class))
.toConstructor(DefaultJdbcTemplate.class.getConstructor(DataSource.class))
.in(Scopes.SINGLETON);
} catch (NoSuchMethodException e) {
addError(e);
}
}
});
JdbcTemplate instance = injector.getInstance(JdbcTemplate.class);
}

interface JdbcTemplate {

}

public static class DefaultJdbcTemplate implements JdbcTemplate {

public DefaultJdbcTemplate(DataSource dataSource) {
System.out.println("init JdbcTemplate,ds => " + dataSource.hashCode());
}
}

public static class DataSource {

}
}
// init JdbcTemplate,ds => 1420232606

这里需要使用者捕获和处理获取构造函数失败抛出的​​NoSuchMethodException​​异常。

Untargeted Binding

​Untargeted Binding​​​用于注册绑定没有目标(实现)类型的特化场景,一般是没有实现接口的普通类型,在没有使用​​@Named​​​注解或者自定义注解绑定的前提下可以忽略​​to()​​​调用。但是如果使用了​​@Named​​​注解或者自定义注解进行绑定,​​to()​​调用一定不能忽略。例如:

public class GuiceUnTargetedBindingDemo {

public static void main(String[] args) throws Exception {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
public void configure() {
bind(Foo.class).in(Scopes.SINGLETON);
bind(Bar.class).annotatedWith(Names.named("bar")).to(Bar.class).in(Scopes.SINGLETON);
}
});
}

public static class Foo {

}

public static class Bar {

}
}

Multi Binding

​Multi Binding​​​也就是多(实例)绑定,使用特化的​​Binder​​​代理完成,这三种​​Binder​​代理分别是:

  • ​Multibinder​​​:可以简单理解为​​Type => Set<TypeImpl>​​​,注入类型为​​Set<Type>​
  • ​MapBinder​​​:可以简单理解为​​(KeyType, ValueType) => Map<KeyType, ValueTypeImpl>​​​,注入类型为​​Map<KeyType, ValueType>​
  • ​OptionalBinder​​​:可以简单理解为​​Type => Optional.ofNullable(GuiceMap.get(Type)).or(DefaultImpl)​​​,注入类型为​​Optional<Type>​

​Multibinder​​的使用例子:

public class GuiceMultiBinderDemo {

public static void main(String[] args) {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
public void configure() {
Multibinder<Processor> multiBinder = Multibinder.newSetBinder(binder(), Processor.class);
multiBinder.permitDuplicates().addBinding().to(FirstProcessor.class).in(Scopes.SINGLETON);
multiBinder.permitDuplicates().addBinding().to(SecondProcessor.class).in(Scopes.SINGLETON);
}
});
injector.getInstance(Client.class).process();
}

@Singleton
public static class Client {

@Inject
private Set<Processor> processors;

public void process() {
Optional.ofNullable(processors).ifPresent(ps -> ps.forEach(Processor::process));
}
}

interface Processor {

void process();
}

public static class FirstProcessor implements Processor {

@Override
public void process() {
System.out.println("FirstProcessor process...");
}
}

public static class SecondProcessor implements Processor {

@Override
public void process() {
System.out.println("SecondProcessor process...");
}
}
}
// 输出结果
FirstProcessor process...
SecondProcessor process...

​MapBinder​​的使用例子:

public class GuiceMapBinderDemo {

public static void main(String[] args) {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
public void configure() {
MapBinder<Type, Processor> mapBinder = MapBinder.newMapBinder(binder(), Type.class, Processor.class);
mapBinder.addBinding(Type.SMS).to(SmsProcessor.class).in(Scopes.SINGLETON);
mapBinder.addBinding(Type.MESSAGE_TEMPLATE).to(MessageTemplateProcessor.class).in(Scopes.SINGLETON);
}
});
injector.getInstance(Client.class).process();
}

@Singleton
public static class Client {

@Inject
private Map<Type, Processor> processors;

public void process() {
Optional.ofNullable(processors).ifPresent(ps -> ps.forEach(((type, processor) -> processor.process())));
}
}

public enum Type {

/**
* 短信
*/
SMS,

/**
* 消息模板
*/
MESSAGE_TEMPLATE
}

interface Processor {

void process();
}

public static class SmsProcessor implements Processor {

@Override
public void process() {
System.out.println("SmsProcessor process...");
}
}

public static class MessageTemplateProcessor implements Processor {

@Override
public void process() {
System.out.println("MessageTemplateProcessor process...");
}
}
}
// 输出结果
SmsProcessor process...
MessageTemplateProcessor process...

​OptionalBinder​​的使用例子:

public class GuiceOptionalBinderDemo {

public static void main(String[] args) {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
public void configure() {
// bind(Logger.class).to(LogbackLogger.class).in(Scopes.SINGLETON);
OptionalBinder.newOptionalBinder(binder(), Logger.class)
.setDefault()
.to(StdLogger.class)
.in(Scopes.SINGLETON);
}
});
injector.getInstance(Client.class).log("Hello World");
}

@Singleton
public static class Client {

@Inject
private Optional<Logger> logger;

public void log(String content) {
logger.ifPresent(l -> l.log(content));
}
}


interface Logger {

void log(String content);
}

public static class StdLogger implements Logger {

@Override
public void log(String content) {
System.out.println(content);
}
}
}

JIT Binding

​JIT Binding​​​也就是​​Just-In-Time Binding​​​,也可以称为隐式绑定(​​Implicit Binding​​)。隐式绑定需要满足:

  • 构造函数必须无参,并且非​​private​​修饰
  • 没有在​​Module​​​实现中激活​​Binder#requireAtInjectRequired()​

调用​​Binder#requireAtInjectRequired()​​​方法可以强制声明​​Guice​​​只使用带有​​@Inject​​​注解的构造器。调用​​Binder#requireExplicitBindings()​​​方法可以声明​​Module​​​内必须显式声明所有绑定,也就是禁用隐式绑定,所有绑定必须在​​Module​​的实现中声明。下面是一个隐式绑定的例子:

public class GuiceJustInTimeBindingDemo {

public static void main(String[] args) throws Exception {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
public void configure() {

}
});
Foo instance = injector.getInstance(Key.get(Foo.class));
}

public static class Foo {

public Foo() {
System.out.println("init Foo...");
}
}
}
// init Foo...

此外还有两个运行时绑定注解:

  • ​@ImplementedBy​​​:特化的​​Linked Binding​​,用于运行时绑定对应的目标类型
@ImplementedBy(MessageProcessor.class)
public interface Processor {

}
  • ​@ProvidedBy​​​:特化的​​Provider Binding​​​,用于运行时绑定对应的目标类型的​​Provider​​实现
@ProvidedBy(DruidDataSource.class)
public interface DataSource {

}

AOP特性

​Guice​​​提供了相对底层的​​AOP​​​特性,使用者需要自行实现​​org.aopalliance.intercept.MethodInterceptor​​​接口在方法执行点的前后插入自定义代码,并且通过​​Binder#bindInterceptor()​​注册方法拦截器。这里只通过一个简单的例子进行演示,模拟的场景是方法执行前和方法执行完成后分别打印日志,并且计算目标方法调用耗时:

public class GuiceAopDemo {

public static void main(String[] args) {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
public void configure() {
bindInterceptor(Matchers.only(EchoService.class), Matchers.any(), new EchoMethodInterceptor());
}
});
EchoService instance = injector.getInstance(Key.get(EchoService.class));
instance.echo("throwable");
}

public static class EchoService {

public void echo(String name) {
System.out.println(name + " echo");
}
}

public static class EchoMethodInterceptor implements MethodInterceptor {

@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
Method method = methodInvocation.getMethod();
String methodName = method.getName();
long start = System.nanoTime();
System.out.printf("Before invoke method => [%s]\n", methodName);
Object result = methodInvocation.proceed();
long end = System.nanoTime();
System.out.printf("After invoke method => [%s], cost => %d ns\n", methodName, (end - start));
return result;
}
}
}

// 输出结果
Before invoke method => [echo]
throwable echo
After invoke method => [echo], cost => 16013700 ns

自定义注入

通过​​TypeListener​​​和​​MembersInjector​​​可以实现目标类型实例的成员属性自定义注入扩展。例如可以通过下面的方式实现目标实例的​​org.slf4j.Logger​​属性的自动注入:

public class GuiceCustomInjectionDemo {

public static void main(String[] args) throws Exception {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
public void configure() {
bindListener(Matchers.any(), new LoggingListener());
}
});
injector.getInstance(LoggingClient.class).doLogging("Hello World");
}

public static class LoggingClient {

@Logging
private Logger logger;

public void doLogging(String content) {
Optional.ofNullable(logger).ifPresent(l -> l.info(content));
}
}

@Qualifier
@Retention(RUNTIME)
@interface Logging {

}

public static class LoggingMembersInjector<T> implements MembersInjector<T> {

private final Field field;
private final Logger logger;

public LoggingMembersInjector(Field field) {
this.field = field;
this.logger = LoggerFactory.getLogger(field.getDeclaringClass());
field.setAccessible(true);
}

@Override
public void injectMembers(T instance) {
try {
field.set(instance, logger);
} catch (IllegalAccessException e) {
throw new IllegalStateException(e);
} finally {
field.setAccessible(false);
}
}
}

public static class LoggingListener implements TypeListener {

@Override
public <I> void hear(TypeLiteral<I> typeLiteral, TypeEncounter<I> typeEncounter) {
Class<?> clazz = typeLiteral.getRawType();
while (Objects.nonNull(clazz)) {
for (Field field : clazz.getDeclaredFields()) {
if (field.getType() == Logger.class && field.isAnnotationPresent(Logging.class)) {
typeEncounter.register(new LoggingMembersInjector<>(field));
}
}
clazz = clazz.getSuperclass();
}
}
}
}
// 输出结果
[2022-02-22 00:51:33,516] [INFO] cn.vlts.guice.GuiceCustomInjectionDemo$LoggingClient [main] [] - Hello World

轻量级DI框架Guice使用详解_spring_02

此例子需要引入​​logback​​​和​​slf4j-api​​的依赖。

基于ClassGraph扫描和全自动注册绑定

​Guice​​​本身不提供类路径或者​​Jar​​​文件的类扫描功能,要实现类路径下的所有​​Bean​​​全自动注册绑定,需要依赖第三方类扫描框架,这里选用了一个性能比较高社区比较活跃的类库​​io.github.classgraph:classgraph​​​。引入​​ClassGraph​​的最新依赖:

<dependency>
<groupId>io.github.classgraph</groupId>
<artifactId>classgraph</artifactId>
<version>4.8.138</version>
</dependency>

编写自动扫描​​Module​​:

@RequiredArgsConstructor
public class GuiceAutoScanModule extends AbstractModule {

private final Set<Class<?>> bindClasses = new HashSet<>();

private final String[] acceptPackages;

private final String[] rejectClasses;

@Override
public void configure() {
ClassGraph classGraph = new ClassGraph();
ScanResult scanResult = classGraph
.enableClassInfo()
.acceptPackages(acceptPackages)
.rejectClasses(rejectClasses)
.scan();
ClassInfoList allInterfaces = scanResult.getAllInterfaces();
for (ClassInfo i : allInterfaces) {
ClassInfoList impl = scanResult.getClassesImplementing(i.getName());
if (Objects.nonNull(impl)) {
Class<?> ic = i.loadClass();
int size = impl.size();
if (size > 1) {
for (ClassInfo im : impl) {
Class<?> implClass = im.loadClass();
if (isSingleton(implClass)) {
String simpleName = im.getSimpleName();
String name = Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1);
bindNamedSingleInterface(ic, name, implClass);
}
}
} else {
for (ClassInfo im : impl) {
Class<?> implClass = im.loadClass();
if (isProvider(implClass)) {
bindProvider(ic, implClass);
}
if (isSingleton(implClass)) {
bindSingleInterface(ic, implClass);
}
}
}
}
}
ClassInfoList standardClasses = scanResult.getAllStandardClasses();
for (ClassInfo ci : standardClasses) {
Class<?> implClass = ci.loadClass();
if (!bindClasses.contains(implClass) && shouldBindSingleton(implClass)) {
bindSingleton(implClass);
}
}
bindClasses.clear();
ScanResult.closeAll();
}

private boolean shouldBindSingleton(Class<?> implClass) {
int modifiers = implClass.getModifiers();
return isSingleton(implClass) && !Modifier.isAbstract(modifiers) && !implClass.isEnum();
}

private void bindSingleton(Class<?> implClass) {
bindClasses.add(implClass);
bind(implClass).in(Scopes.SINGLETON);
}

@SuppressWarnings({"unchecked", "rawtypes"})
private void bindSingleInterface(Class<?> ic, Class<?> implClass) {
bindClasses.add(implClass);
bind((Class) ic).to(implClass).in(Scopes.SINGLETON);
}

@SuppressWarnings({"unchecked", "rawtypes"})
private void bindNamedSingleInterface(Class<?> ic, String name, Class<?> implClass) {
bindClasses.add(implClass);
bind((Class) ic).annotatedWith(Names.named(name)).to(implClass).in(Scopes.SINGLETON);
}

@SuppressWarnings({"unchecked", "rawtypes"})
private <T> void bindProvider(Class<?> ic, Class<?> provider) {
bindClasses.add(provider);
Type type = ic.getGenericInterfaces()[0];
ParameterizedType parameterizedType = (ParameterizedType) type;
Class target = (Class) parameterizedType.getActualTypeArguments()[0];
bind(target).toProvider(provider).in(Scopes.SINGLETON);
}

private boolean isSingleton(Class<?> implClass) {
return Objects.nonNull(implClass) && implClass.isAnnotationPresent(Singleton.class);
}

private boolean isProvider(Class<?> implClass) {
return isSingleton(implClass) && Provider.class.isAssignableFrom(implClass);
}
}

使用方式:

GuiceAutoScanModule module = new GuiceAutoScanModule(new String[]{"cn.vlts"}, new String[]{"*Demo", "*Test"});
Injector injector = Guice.createInjector(module);

​GuiceAutoScanModule​​​目前只是一个并不完善的示例,用于扫描​​cn.vlts​​​包下(排除类名以​​Demo​​​或者​​Test​​结尾的类)所有的类并且按照不同情况进行绑定注册,实际场景可能会更加复杂,可以基于类似的思路进行优化和调整。

小结

限于篇幅,本文只介绍了​​Guice​​​的基本使用、设计理念和不同类型的绑定方式注册,更深入的实践方案后面有机会应用在项目中的时候再基于案例详细聊聊​​Guice​​​的应用。另外,​​Guice​​​不是过时的组件,相对于​​SpringBoot​​​一个最简构建几十​​MB​​​的​​Flat Jar​​​,如果仅仅想要轻量级​​DI​​​功能,​​Guice​​会是一个十分合适的选择。

参考资料: