Guice 快速入门

刺骨的言语ヽ痛彻心扉 2024-04-17 05:29 175阅读 0赞

Guice是谷歌推出的一个轻量级依赖注入框架,帮助我们解决Java项目中的依赖注入问题。如果使用过Spring的话,会了解到依赖注入是个非常方便的功能。不过假如只想在项目中使用依赖注入,那么引入Spring未免大材小用了。这时候我们可以考虑使用Guice。本文参考了Guice官方文档,详细信息可以直接查看Guice文档。

基本使用

引入依赖

如果使用Maven的话,添加下面的依赖项。

  1. <dependency>
  2. <groupId>com.google.inject</groupId>
  3. <artifactId>guice</artifactId>
  4. <version>4.1.0</version>
  5. </dependency>

如果使用Gradle的话,添加下面的依赖项。

  1. compile group: 'com.google.inject', name: 'guice', version: '4.1.0'

当构建工具解决完项目的依赖之后,我们就可以开始使用Guice了。

项目骨架

首先我们来假设一个简单的项目框架。首先我们需要一个业务接口,简单的包含一个方法用于执行业务逻辑。它的实现也非常简单。

  1. public interface UserService {
  2. void process();
  3. }
  4. public class UserServiceImpl implements UserService {
  5. @Override
  6. public void process() {
  7. System.out.println("我需要做一些业务逻辑");
  8. }
  9. }

然后我们需要一个日志接口,它和它的实现也非常简单。

  1. public interface LogService {
  2. void log(String msg);
  3. }
  4. public class LogServiceImpl implements LogService {
  5. @Override
  6. public void log(String msg) {
  7. System.out.println("------LOG:" + msg);
  8. }
  9. }

最后是一个系统接口和相应的实现。在实现中我们使用了业务接口和日志接口处理业务逻辑和打印日志信息。

  1. public interface Application {
  2. void work();
  3. }
  4. public class MyApp implements Application {
  5. private UserService userService;
  6. private LogService logService;
  7. @Inject
  8. public MyApp(UserService userService, LogService logService) {
  9. this.userService = userService;
  10. this.logService = logService;
  11. }
  12. @Override
  13. public void work() {
  14. userService.process();
  15. logService.log("程序正常运行");
  16. }
  17. }

简单的依赖注入

首先来配置依赖关系。我们继承AbstractModule类,并重写configure方法即可。在configure方法中,我们可以调用AbstractModule类提供的一些方法来配置依赖关系。最常用的方式就是bind(接口或父类).to(实现类或子类)的方式来设置依赖关系。

  1. public class MyAppModule extends AbstractModule {
  2. @Override
  3. protected void configure() {
  4. bind(LogService.class).to(LogServiceImpl.class);
  5. bind(UserService.class).to(UserServiceImpl.class);
  6. bind(Application.class).to(MyApp.class);
  7. }
  8. }

这样一来,当Guice遇到接口或父类需要注入具体实现的时候,就会使用这里配置的实现类或子类来注入。如果希望在构造器中注入依赖的话,只需要添加@Inject注解即可。

Guice配置完之后,我们需要调用Guice.createInjector方法传入配置类来创建一个注入器,然后使用注入器的getInstance方法获取目标类,Guice会按照配置帮我们注入所有依赖。我们使用单元测试来看看效果。

  1. public class MyAppTest {
  2. private static Injector injector;
  3. @BeforeClass
  4. public static void init() {
  5. injector = Guice.createInjector(new MyAppModule());
  6. }
  7. @Test
  8. public void testMyApp() {
  9. Application myApp = injector.getInstance(Application.class);
  10. myApp.work();
  11. }
  12. }
  13. //程序结果
  14. //我需要做一些业务逻辑
  15. //------LOG:程序正常运行

依赖绑定

下面这些例子都是Guice文档上的例子。我算是简单的翻译了一下。

链式绑定

我们在绑定依赖的时候不仅可以将父类和子类绑定,还可以将子类和更具体的子类绑定。下面的例子中,当我们需要TransactionLog的时候,Guice最后会为我们注入MySqlDatabaseTransactionLog对象。

  1. public class BillingModule extends AbstractModule {
  2. @Override
  3. protected void configure() {
  4. bind(TransactionLog.class).to(DatabaseTransactionLog.class);
  5. bind(DatabaseTransactionLog.class).to(MySqlDatabaseTransactionLog.class);
  6. }
  7. }

注解绑定

当我们需要将多个同一类型的对象注入不同对象的时候,就需要使用注解区分这些依赖了。最简单的办法就是使用@Named注解进行区分。

首先需要在要注入的地方添加@Named注解。

  1. public class RealBillingService implements BillingService {
  2. @Inject
  3. public RealBillingService(@Named("Checkout") CreditCardProcessor processor,
  4. TransactionLog transactionLog) {
  5. ...
  6. }

然后在绑定中添加annotatedWith方法指定@Named中指定的名称。由于编译器无法检查字符串,所以Guice官方建议我们保守地使用这种方式。

  1. bind(CreditCardProcessor.class)
  2. .annotatedWith(Names.named("Checkout"))
  3. .to(CheckoutCreditCardProcessor.class);

如果希望使用类型安全的方式,可以自定义注解。

  1. @BindingAnnotation @Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME)
  2. public @interface PayPal {}

然后在需要注入的类上应用。

  1. public class RealBillingService implements BillingService {
  2. @Inject
  3. public RealBillingService(@PayPal CreditCardProcessor processor,
  4. TransactionLog transactionLog) {
  5. ...
  6. }

在配置类中,使用方法也和@Named类似。

  1. bind(CreditCardProcessor.class)
  2. .annotatedWith(PayPal.class)
  3. .to(PayPalCreditCardProcessor.class);

实例绑定

有时候需要直接注入一个对象的实例,而不是从依赖关系中解析。如果我们要注入基本类型的话只能这么做。

  1. bind(String.class)
  2. .annotatedWith(Names.named("JDBC URL"))
  3. .toInstance("jdbc:mysql://localhost/pizza");
  4. bind(Integer.class)
  5. .annotatedWith(Names.named("login timeout seconds"))
  6. .toInstance(10);

如果使用toInstance方法注入的实例比较复杂的话,可能会影响程序启动。这时候可以使用@Provides方法代替。

@Provides方法

当一个对象很复杂,无法使用简单的构造器来生成的时候,我们可以使用@Provides方法,也就是在配置类中生成一个注解了@Provides的方法。在该方法中我们可以编写任意代码来构造对象。

  1. public class BillingModule extends AbstractModule {
  2. @Override
  3. protected void configure() {
  4. ...
  5. }
  6. @Provides
  7. TransactionLog provideTransactionLog() {
  8. DatabaseTransactionLog transactionLog = new DatabaseTransactionLog();
  9. transactionLog.setJdbcUrl("jdbc:mysql://localhost/pizza");
  10. transactionLog.setThreadPoolSize(30);
  11. return transactionLog;
  12. }
  13. }

@Provides方法也可以应用@Named和自定义注解,还可以注入其他依赖,Guice会在调用方法之前注入需要的对象。

  1. @Provides @PayPal
  2. CreditCardProcessor providePayPalCreditCardProcessor(
  3. @Named("PayPal API key") String apiKey) {
  4. PayPalCreditCardProcessor processor = new PayPalCreditCardProcessor();
  5. processor.setApiKey(apiKey);
  6. return processor;
  7. }

Provider绑定

如果项目中存在多个比较复杂的对象需要构建,使用@Provide方法会让配置类变得比较乱。我们可以使用Guice提供的Provider接口将复杂的代码放到单独的类中。办法很简单,实现Provider<T>接口的get方法即可。在Provider类中,我们可以使用@Inject任意注入对象。

  1. public class DatabaseTransactionLogProvider implements Provider<TransactionLog> {
  2. private final Connection connection;
  3. @Inject
  4. public DatabaseTransactionLogProvider(Connection connection) {
  5. this.connection = connection;
  6. }
  7. public TransactionLog get() {
  8. DatabaseTransactionLog transactionLog = new DatabaseTransactionLog();
  9. transactionLog.setConnection(connection);
  10. return transactionLog;
  11. }
  12. }

在配置类中使用toProvider方法绑定到Provider上即可。

  1. public class BillingModule extends AbstractModule {
  2. @Override
  3. protected void configure() {
  4. bind(TransactionLog.class)
  5. .toProvider(DatabaseTransactionLogProvider.class);
  6. }

作用域

默认情况下Guice会在每次注入的时候创建一个新对象。如果希望创建一个单例依赖的话,可以在实现类上应用@Singleton注解。

  1. @Singleton
  2. public class InMemoryTransactionLog implements TransactionLog {
  3. /* everything here should be threadsafe! */
  4. }

或者也可以在配置类中指定。

  1. bind(TransactionLog.class).to(InMemoryTransactionLog.class).in(Singleton.class);

@Provides方法中也可以指定单例。

  1. @Provides @Singleton
  2. TransactionLog provideTransactionLog() {
  3. ...
  4. }

如果一个类型上存在多个冲突的作用域,Guice会使用bind()方法中指定的作用域。如果不想使用注解的作用域,可以在bind()方法中将对象绑定为Scopes.NO_SCOPE

Guice和它的扩展提供了很多作用域,有单例Singleton,Session作用域SessionScoped,Request请求作用域RequestScoped等等。我们可以根据需要选择合适的作用域。

Servlet集成

Guice也可以和Servlet项目集成,这样我们就可以不用编写冗长的web.xml,以依赖注入的方式使用Servlet和相关组件。

安装Guice Servlet扩展

要在Servlet项目中集成Guice,首先需要安装Guice Servlet扩展。如果使用Maven,添加下面的依赖。

  1. <dependency>
  2. <groupId>com.google.inject.extensions</groupId>
  3. <artifactId>guice-servlet</artifactId>
  4. <version>4.1.0</version>
  5. </dependency>

如果使用Gradle,添加下面的依赖。

  1. compile group: 'com.google.inject.extensions', name: 'guice-servlet', version: '4.1.0'

添加依赖之后,在web.xml中添加下面的代码,让Guice过滤所有web请求。

  1. <filter>
  2. <filter-name>guiceFilter</filter-name>
  3. <filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
  4. </filter>
  5. <filter-mapping>
  6. <filter-name>guiceFilter</filter-name>
  7. <url-pattern>/*</url-pattern>
  8. </filter-mapping>

配置Injector

和普通的项目一样,Servlet项目同样需要配置Injector。一种比较好的办法就是在ContextListener中配置Injector。Guice的Servlet集成提供了GuiceServletContextListener,我们继承该类并在getInjector方法中配置Injector即可。

  1. public class MyGuiceServletConfigListener extends GuiceServletContextListener {
  2. @Override
  3. protected Injector getInjector() {
  4. return Guice.createInjector(new ServletModule());
  5. }
  6. }

当然仅仅继承GuiceServletContextListener还是不够的。我们还需要在web.xml中添加几行代码。

  1. <listener>
  2. <listener-class>yitian.study.servlet.MyGuiceConfigListener</listener-class>
  3. </listener>

配置Servlet和过滤器

默认的ServletModule就会启用一些功能。如果需要自定义Servlet和Filter,就需要继承ServletModule并重写configureServlets()方法。配置Servlet和Filter的方法和配置普通依赖注入类似。

  1. public class MyServletConfig extends ServletModule {
  2. @Override
  3. protected void configureServlets() {
  4. serve("/*").with(MainServlet.class);
  5. filter("/*").through(CharEncodingFilter.class);
  6. }
  7. }

然后将ServletModule替换为我们自己的实现类。

  1. public class MyGuiceConfigListener extends GuiceServletContextListener {
  2. @Override
  3. protected Injector getInjector() {
  4. return Guice.createInjector(new MyServletConfig());
  5. }
  6. }

注入Servlet相关对象

除了配置Servlet之外,Guice还允许我们把Request、Response和Session对象注入到非Servlet对象中。下面是Guice的一个例子。

  1. @RequestScoped
  2. class SomeNonServletPojo {
  3. @Inject
  4. SomeNonServletPojo(HttpServletRequest request, HttpServletResponse response, HttpSession session) {
  5. ...
  6. }
  7. }

我们还可以使用Guice注入请求参数。下面这个类的作用是获取所有请求参数并转换为字符串形式。

  1. @RequestScoped
  2. public class Information {
  3. @Inject
  4. @RequestParameters
  5. Map<String, String[]> params;
  6. public String getAllParameters() {
  7. return params.entrySet()
  8. .stream()
  9. .map(entry -> entry.getKey() + " : " + Arrays.toString(entry.getValue()))
  10. .collect(Collectors.joining(", "));
  11. }
  12. }

之后,我们就可以将该对象注入到Servlet中使用,将结果返回给页面。

  1. @Singleton
  2. public class MainServlet extends HttpServlet {
  3. @Inject
  4. private Injector injector;
  5. @Override
  6. protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  7. String name = req.getParameter("name");
  8. req.setAttribute("name", name);
  9. Information info = injector.getInstance(Information.class);
  10. req.setAttribute("params", info.getAllParameters());
  11. req.getRequestDispatcher("index.jsp").forward(req, resp);
  12. }
  13. @Override
  14. public void init() throws ServletException {
  15. super.init();
  16. }
  17. }

Guice Servlet扩展还有其他功能,这里就不在列举了。详情请参看Guice文档。

JSR-330标准

JSR-330是一项Java EE标准,指定了Java的依赖注入标准。Spring、Guice和Weld等很多框架都支持JSR-330。下面这个表格来自于Guice文档,我们可以看到JSR-330和Guice注解基本上可以互换。









































JSR-330javax.inject Guicecom.google.inject  
@Inject @Inject 在某些约束下可互换
@Named @Named 可互换
@Qualifier @BindingAnnotation 可互换
@Scope @ScopeAnnotation 可互换
@Singleton @Singleton 可互换
Provider Provider Guice的Provider继承了JSR-330的Provider

Guice官方推荐我们首选JSR-330标准的注解。

以上就是Guice的基本知识了。如果需要更详细的使用方法,请参考Guice文档。如果有兴趣还可以看看我的CSDN代码,包含了我的Guice练习代码。

作者:乐百川
链接:https://www.jianshu.com/p/a648322dc680
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

发表评论

表情:
评论列表 (有 0 条评论,175人围观)

还没有评论,来说两句吧...

相关阅读

    相关 IOC - Google Guice

    Google Guice是一个轻量级的依赖注入框架,专注于依赖注入和IoC,适用于中小型应用。 Spring Framework是一个全面的企业级框架,提供了广泛的功能,适

    相关 google guice hello world

    Guice是一个轻量级的Java依赖注入(DI)框架。 使用依赖注入有很多优点,但是手动操作常常会导致编写大量样板代码。Guice是一个框架,用于编写使用依赖注入的代码,而不

    相关 Guice 快速入门

    [Guice][]是谷歌推出的一个轻量级依赖注入框架,帮助我们解决Java项目中的依赖注入问题。如果使用过Spring的话,会了解到依赖注入是个非常方便的功能。不过假如只想在项

    相关 Elasticsearch 中的 Guice

    前言 Elasticsearch 源代码中使用了Guice框架进行依赖注入. 为了方便阅读源码, 此处我先通过模仿ES guice的使用方式简单写了一个基本Demo 方便