Junit 5 如何使用 Guice DI
Posted huyuchengus
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Junit 5 如何使用 Guice DI相关的知识,希望对你有一定的参考价值。
Guice 是一个依赖注入的小清新工具。
相比 Spring 的依赖管理来说,这个工具更加小巧,我们可以在测试中直接使用。
Junit 5
在 Junit 中使用就没有那么方便了,因为 Junit 没有 Guice 的注解。
你需要手动写一个类,在这个类中,对 Injector 的模块进行配置。
例如我们下面的代码:
package com.ossez.wechat.oa.api.test;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.ossez.wechat.common.exception.WxRuntimeException;
import com.ossez.wechat.oa.api.WeChatOfficialAccountService;
import com.ossez.wechat.oa.api.impl.okhttp.WeChatMsgService;
import com.ossez.wechat.oa.api.impl.okhttp.WeChatOfficialAccountServiceOkHttp;
import org.apache.commons.lang3.ObjectUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.io.SAXReader;
import org.junit.jupiter.api.BeforeAll;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.locks.ReentrantLock;
/**
* Init Guice DI
*
* @author YuCheng
*/
public class TestBase
private static final Logger log = LoggerFactory.getLogger(TestBase.class);
private static final String TEST_CONFIG_XML = "test-config.xml";
private static final Injector injector = Guice.createInjector(new AbstractModule()
@Override
public void configure()
try (InputStream inputStream = ClassLoader.getSystemResourceAsStream(TEST_CONFIG_XML))
if (ObjectUtils.isEmpty(inputStream))
throw new WxRuntimeException("测试配置文件【" + TEST_CONFIG_XML + "】未找到,请参照test-config-sample.xml文件生成");
// Init WeChat config for testing
Document document = new SAXReader().read(inputStream);
TestConfigStorage config = new TestConfigStorage();
config.setAppId(document.getRootElement().element("appId").getText());
config.setSecret(document.getRootElement().element("secret").getText());
config.setToken(document.getRootElement().element("token").getText());
config.setOpenid(document.getRootElement().element("openid").getText());
config.setAccessTokenLock(new ReentrantLock());
// Init WeChat Service
WeChatOfficialAccountService weChatOfficialAccountService = new WeChatOfficialAccountServiceOkHttp();
weChatOfficialAccountService.setWxMpConfigStorage(config);
weChatOfficialAccountService.addConfigStorage("another", config);
// Init WeChatMsgService
WeChatMsgService weChatMsgService = new WeChatMsgService(weChatOfficialAccountService);
bind(TestConfigStorage.class).toInstance(config);
bind(WeChatOfficialAccountService.class).toInstance(weChatOfficialAccountService);
bind(WeChatMsgService.class).toInstance(weChatMsgService);
catch (IOException e)
log.error(e.getMessage(), e);
catch (DocumentException e)
throw new RuntimeException(e);
);
@BeforeAll
public void setup()
injector.injectMembers(this);
在这个代码中,我们定义了一个 TestBase 的类,然后在测试启动的时候对齐进行了初始化和配置。
最主要的就是这个方法:`private static final Injector injector = Guice.createInjector(new AbstractModule()
在测试中使用
在测试中使用就比较简单了。
首先需要继承这个 TestBase,然后对需要的类进行注入后就可以直接使用了。
如上图,注入后直接使用。
Apache Druid源码导读--Google guice DI框架
文章目录
缘起
在大数据应用组件中,有两款OLAP引擎应用广泛,一款是偏离线处理的Kylin,另一个是偏实时的Druid。Kylin是一款国人开源的优秀离线OLAP引擎,基本上是Hadoop领域离线OLAP事实标准,在离线报表,指标分析领域应用广泛。而Apache Druid则在实时OLAP领域独领风骚,优异的性能、高可用、易扩展。Kylin的实现细节网上资料很多,而Druid很少,最近打算研究下源码,写几篇阅读导读,记录于此。
阅读Druid源码的第一个障碍莫过于Google Guice这个小巧的DI框架了。不了解Guice很难阅读Druid源码,而Guice在国内应用偏少,文章也少,无疑加大了难度。我把Druid中使用的guice扩展单独抽出来,放在了https://github.com/Skycrab/guice-module下,对druid源码注释放在了https://github.com/Skycrab/druid-comment。
本文主要介绍下Google Guice以及Druid中实现的guice扩展模块。
Google Guice介绍
Guice是Google开源的一个小巧的依赖注入框架。
下面主要介绍下与Spring的对比,以及guice几个核心的能力。
与Spring的对比
Guice与Spring没有直接竞争关系,Spring是复杂的技术栈,而Guice只专注于依赖注入。
Guice与Spring的表现方式也稍微有所区别。Guice觉得基于xml的方式过于隐晦,而自动注入(AutoWired)又过于灵活,所以Guice基于代码绑定实现,较为克制。
而基于Module的方式让Guice获得了巨大的灵活性与可复用性,可以简单理解为多个xml装配,但更加强大,可复用。
Example
看下面这个例子(git地址)
@Slf4j
public class GuiceExample
public static void main( String[] args ) throws Exception
Injector injector = Guice.createInjector(new Module()
@Override
public void configure(Binder binder)
binder.bind(ProduceService.class).to(KafkaPrduceService.class);
binder.bind(String.class).annotatedWith(Names.named("server")).toInstance("localhost:9002");
binder.bind(String.class).annotatedWith(Names.named("topic")).toInstance("test");
);
ProduceService produce = injector.getInstance(ProduceService.class);
produce.produce("hello guice");
public interface ProduceService
void produce(Object msg);
@Singleton
public static class KafkaPrduceService implements ProduceService
private String server;
private String topic;
@Inject
public KafkaPrduceService(@Named("server") String server, @Named("topic") String topic)
this.server = server;
this.topic = topic;
@Override
public void produce(Object msg)
log.info("produce --", server, topic, msg);
我们看到guice中的绑定关系是在Module中维护的,可以简单当做是spring的xml文件。Singleton代表该服务是单例的,通过@Inject注入需要的bean,如果需要的bean没有绑定,会通过默认构造函数实例化。
其它基本介绍可参考guice文档
覆盖已有绑定关系
Apache Druid好多模块是可以自定义替换的,一方面通过spi机制+ClassLoader加载扩展模块实现模块热插拔,另一方面通过Guice覆盖绑定关系将新实现注入到框架。下面要介绍的就是guice的覆盖绑定关系能力。
看下面这个例子
/**
* Created by yihaibo on 2019-09-29.
* Guice模块绑定覆盖
*/
public class GuiceOverrideExample
public static void main(String[] args)
List<Module> builtIns = ImmutableList.of(binder ->
binder.bind(Service.class).to(BuiltinService.class);
);
// List<Module> customs = ImmutableList.of();
List<Module> customs = ImmutableList.of(binder ->
binder.bind(Service.class).to(CustomService.class);
);
Injector injector = Guice.createInjector(Modules.override(builtIns).with(customs));
FrameWork frameWork = injector.getInstance(FrameWork.class);
frameWork.start();
public static class FrameWork
private Service service;
@Inject
public FrameWork(Service service)
this.service = service;
public void start()
this.service.run();
public interface Service
void run();
public static class BuiltinService implements Service
@Override
public void run()
System.out.println("BuiltinService");
public static class CustomService implements Service
@Override
public void run()
System.out.println("CustomService");
我们看到框架本来绑定的是BuiltinService,现在我们需要替换成CustomService,只需要Modules.override即可覆盖绑定关系。
默认绑定
在做基础库的时候,有时会依赖一些服务,但这些服务很可能被用户自定义,这时可以使用guice的默认绑定功能。
看下面代码
@Slf4j
public class GuiceOptionalBinderExample
public static void main(String[] args)
Injector injector = Guice.createInjector(new FrameWorkModule(), new Module()
@Override
public void configure(Binder binder)
//覆盖框架默认实现
//如果需要传递参数,可以1.Inject 2.使用Provider
OptionalBinder.newOptionalBinder(binder, Emit.class).setBinding().to(kafkaEmit.class);
);
TestService testService = injector.getInstance(TestService.class);
testService.test();
public static class TestService
private Emit emit;
@Inject
public TestService(Emit emit)
this.emit = emit;
public void test()
this.emit.emit("start TestService");
//-------应用代码
public static class kafkaEmit implements Emit
@Override
public void emit(Object object)
log.info("kafkaEmit emit");
//-------库代码
public static class FrameWorkModule implements Module
@Override
public void configure(Binder binder)
//库默认实现
OptionalBinder.newOptionalBinder(binder, Emit.class).setDefault().to(HttpEmit.class);
public interface Emit
void emit(Object object);
public static class HttpEmit implements Emit
@Override
public void emit(Object object)
log.info("HttpEmit emit");
Apache Druid中Guice模块
在Druid中有几个通用的Guice扩展,不了解会对代码阅读产生影响。
模块 | 功能 |
---|---|
guice-lifecycle | 实现生命周期托管,实现服务start、stop方法 |
guice-jsonconfig | Properties配置文件bean自动装配 |
guice-jersey-jetty | 内嵌jetty的jersey Restful |
guice-lifecycle
LifecycleModule提供服务托管能力,提供了4级服务优先级,框架会自动调用start和stop方法。该功能在druid应用非常广泛,是应用启动的原点。
/**
* Guice 生命周期管理
*/
public class GuiceLifecycleExample
public static void main( String[] args ) throws Exception
final Injector injector = Guice.createInjector(new LifecycleModule());
final Bootstrap bootstrap = injector.getInstance(Bootstrap.class);
bootstrap.run();
public static class Bootstrap
// 必须主动注入ManageLifecycle,否则需要通过Lifecycle.addHandler主动注册
private PrintLifecycle printLifecycle;
private Lifecycle lifecycle;
@Inject
public Bootstrap(Lifecycle lifecycle, PrintLifecycle printLifecycle)
this.lifecycle = lifecycle;
this.printLifecycle = printLifecycle;
public void run() throws Exception
System.out.println("Bootstrap run");
lifecycle.start();
lifecycle.join();
@ManageLifecycle
@Slf4j
public static class PrintLifecycle
@LifecycleStart
public void start()
System.out.println("PrintLifecycle start");
@LifecycleStop
public void stop()
System.out.println("PrintLifecycle stop");
guice-jsonconfig
jsonconfig提供了配置文件bean自动装配,并支持validation注解校验。Druid中读取配置功能都使用了该功能。
public class GuiceJsonConfigExample
public static void main( String[] args )
Injector injector = Guice.createInjector(new JsonConfigModule(), new Module()
@Override
public void configure(Binder binder)
JsonConfigProvider.bind(binder, "druid.server", DruidServerConfig.class);
@Provides
@Singleton
/**
* Properties代码注入需注意必须为string
*/
Properties provideProperties()
Properties props = new Properties();
props.put("druid.server.hostname", "0.0.0.0");
props.put("druid.server.port", "3333");
return props;
);
DruidServerConfig druidServerConfig = injector.getInstance(DruidServerConfig.class);
System.out.println(druidServerConfig.port);
System.out.println(druidServerConfig.hostname);
public static class DruidServerConfig
@JsonProperty
@NotNull
public String hostname = null;
@JsonProperty @Min(1025) public int port = 8080;
guice-jersey-jetty
jersey-jetty提供了内嵌jetty的jersey Restful。Druid中http接口都由jersey支持,由Jetty充当servlet容器。
/**
* Created by yihaibo on 2019-10-08.
* jersey restful简单列子
*/
@Slf4j
public class GuiceJerseryJettyExample
public static void main(String[] args) throws Exception
Injector injector = Guice.createInjector(new JsonConfigModule(), new JettyServerModule(), new Module()
@Override
public void configure(Binder binder)
Jerseys.addResource(binder, IndexResource.class);
@Provides @Singleton
Properties provideProperties()
Properties props = new Properties();
props.put("server.http.host", "0.0.0.0");
props.put("server.http.port", "9000");
return props;
);
JerseyJettyServer jerseyJettyServer = injector.getInstance(JerseyJettyServer.class);
jerseyJettyServer.start();
Thread.currentThread().join();
@Singleton
@Path("/index")
public static class IndexResource
private ServerConfig serverConfig;
@Inject
public IndexResource(ServerConfig serverConfig)
this.serverConfig = serverConfig;
@GET
@Produces(MediaType.APPLICATION_JSON)
public ServerConfig doGet(@Context final HttpServletRequest req)
return serverConfig;
以上是关于Junit 5 如何使用 Guice DI的主要内容,如果未能解决你的问题,请参考以下文章
Apache Druid源码导读--Google guice DI框架
Apache Druid源码导读--Google guice DI框架