轻量级DI框架Guice使用详解
背景
在日常写一些小工具或者小项目的时候,有依赖管理和依赖注入的需求,但是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
不相同):
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
此例子需要引入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
会是一个十分合适的选择。
参考资料:
(本文完 c-4-d e-a-20220221)