依赖注入框架Guice

2022/07/28 Java 共 8614 字,约 25 分钟

依赖注入Guice

Spring框架的依赖注入是家喻户晓的,但是在实际的开发中我们想使用便捷的依赖注入功能, 可以使用Google Guice,首先在你的maven项目里引入

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

Guice是一个非常干净的依赖注入框架,框架除了依赖注入功能之外,没有任何其它非相关模块功能。

注解 @Inject,@Singleton,@Implementedby

最常用的两个注解就是@Singleton和@Inject,Singleton表示构建的对象是单例的,Inject表示被标注的字段将使用Guice自动注入。在一般的项目中这两个注解一般可以完成90%以上的装配工作。

Guice需要实例化对象,请确保相应被实例化的对象有默认构造器。

当某个接口有多个实现时,我们使用@ImplementedBy注解在接口定义上,指定接口的具体实现类

import javax.inject.Singleton;

import com.google.inject.Guice;
import com.google.inject.ImplementedBy;
import com.google.inject.Inject;
import com.google.inject.Injector;

@ImplementedBy(SimpleHelloPrinter.class)
interface IHelloPrinter {
	void print();
}

@Singleton
class SimpleHelloPrinter implements IHelloPrinter {

	public void print() {
		System.out.println("Hello, Simple World");
	}

}

@Singleton
class ComplexHelloPrinter implements IHelloPrinter {

	public void print() {
		System.out.println("Hello, Complex World");
	}

}

@Singleton
public class Sample {

	@Inject
	private IHelloPrinter printer;

	public void hello() {
		printer.print();
	}

	public static void main(String[] args) {
		Injector injector = Guice.createInjector();
		Sample sample = injector.getInstance(Sample.class);
		sample.hello();
	}

}

如果我们不用Singleton标注,每次获取实例时,Guice会重新构造一个,这个会有反射构造器的性能损耗,在高性能场景下,请谨慎。

用Module定义装配规则

我们可以不使用@ImplementedBy注解,因为这样不优雅,谁会在定义接口的时候就能预知实现类的名称呢。我们可以使用Guice Module定义装配规则,它相当于Spring的XML文件,只不过它的装配规则都是使用代码定义的。你可能会辩解说代码定义怎么能比得上XML定义呢,其实Guice Module在一个大型项目中也是非常的简洁,一般只会占用几十行代码,Module里面配置的仅仅是特殊的专配规则。能规则的可读性而言,代码要比XML舒服的多。

要点:

  1. 继承AbstractModule
  2. 在覆盖的configure()方法里手动bind(接口).to(实现类) 效果和在实现类上@ImplementedBy一样
import javax.inject.Singleton;

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;

interface IHelloPrinter {
	void print();
}

@Singleton
class SimpleHelloPrinter implements IHelloPrinter {

	public void print() {
		System.out.println("Hello, Simple World");
	}

}

@Singleton
class ComplexHelloPrinter implements IHelloPrinter {

	public void print() {
		System.out.println("Hello, Complex World");
	}

}

class SampleModule extends AbstractModule {

	@Override
	protected void configure() {
		bind(IHelloPrinter.class).to(SimpleHelloPrinter.class);
	}

}

@Singleton
public class Sample {

	@Inject
	private IHelloPrinter printer;

	public void hello() {
		printer.print();
	}

	public static void main(String[] args) {
		Injector injector = Guice.createInjector(new SampleModule());
		Sample sample = injector.getInstance(Sample.class);
		sample.hello();
	}

}

@Name给实现类起别名指定bind

我们还可以使用@Named名称指令来指定依赖注入实现

要点

  • 给要注入的实现类起别名,在configre里bind设置
  • @Name 辅助@Inject 引入别名对应的注入,还是需要@Inject
import javax.inject.Named;
import javax.inject.Singleton;

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.name.Names;

interface IHelloPrinter {
	void print();
}

@Singleton
class SimpleHelloPrinter implements IHelloPrinter {

	public void print() {
		System.out.println("Hello, Simple World");
	}

}

@Singleton
class ComplexHelloPrinter implements IHelloPrinter {

	public void print() {
		System.out.println("Hello, Complex World");
	}

}

class SampleModule extends AbstractModule {

	@Override
	protected void configure() {
		bind(IHelloPrinter.class).annotatedWith(Names.named("simple")).to(SimpleHelloPrinter.class);
		bind(IHelloPrinter.class).annotatedWith(Names.named("complex")).to(ComplexHelloPrinter.class);
	}
	
}

@Singleton
public class Sample {

	@Inject
	@Named("simple")
	private IHelloPrinter simplePrinter;

	@Inject
	@Named("complex")
	private IHelloPrinter complexPrinter;

	public void hello() {
		simplePrinter.print();
		complexPrinter.print();
	}

	public static void main(String[] args) {
		Injector injector = Guice.createInjector(new SampleModule());
		Sample sample = injector.getInstance(Sample.class);
		sample.hello();
	}

}

@Inject放构造函数/set方法上注入

我们不使用字段注入,改用构造器注入,如果我们需要在构造器里做一些特别的初始化工作

其实是自动根据入参来注入, 单参数多参数都可以。 也可以根据set方法注入。

package io.edurt.lc.guice;
import com.google.inject.Guice;
import com.google.inject.Inject;
public class TestGuiceConstructor
{
    private GuiceConstructorService service;
    @Inject
    public TestGuiceConstructor(GuiceConstructorService service)
    {
        this.service = service;
    }
    public GuiceConstructorService getService()
    {
        return service;
    }
    public static void main(String[] args)
    {
        TestGuiceConstructor test = Guice.createInjector().getInstance(TestGuiceConstructor.class);
        test.getService().print("Test Case 1");
    }
}

Guice 获取实例

@Test
public void test_service()
{
    Injector injector = Guice.createInjector(new GuiceBasicModule());
    GuiceBasicService service = injector.getInstance(GuiceBasicService.class);
    service.print("Hello Guice");
}

静态方法注入

关键代码和方法

Guice.createInjector(binder -> binder.requestStaticInjection(TestGuiceStatic.class));

其实是手动创建注入。


package io.edurt.lc.guice;
import com.google.inject.Guice;
import com.google.inject.Inject;
public class TestGuiceStatic
{
    @Inject
    private static GuiceConstructorService service;
    public static void main(String[] args)
    {
        Guice.createInjector(binder -> binder.requestStaticInjection(TestGuiceStatic.class));
        TestGuiceStatic.service.print("Static");
    }
}

@Provides 类似Spring的@bean

Guice provides a way to create bindings with complex objects using @Provides annotation.

@Provides放函数上 return值就是可注入的Bean. Provides需要放在继承AbstrctModule的类函数上。

//Binding Module
class TextEditorModule extends AbstractModule {

   @Override
   protected void configure() {} 

   @Provides
   public SpellChecker provideSpellChecker(){

      String dbUrl = "jdbc:mysql://localhost:5326/emp";
      String user = "user";
      int timeout = 100;

      SpellChecker SpellChecker = new SpellCheckerImpl(dbUrl, user, timeout);
      return SpellChecker;
   }
}

@BindingAnnotation 指定bind

参考

通过自定义一个注解,然后注解上再加一个注解@BindingAnnotation就可以让@Inject注入时再指定这个自定义注解对应的实现类了,方便一个接口对应多个实现类,通过自定义注解区分对应到哪个实现类,和@Name类似。

As we can bind a type with its implementation. In case we want to map a type with multiple implmentations, we can create custom annotation as well. See the below example to understand the concept.

Create a binding annotation

@BindingAnnotation @Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME)
@interface WinWord {}
  • @BindingAnnotation - Marks annotation as binding annotation.

Mapping using binding annotation

bind(SpellChecker.class).annotatedWith(WinWord.class).to(WinWordSpellCheckerImpl.class);

以上代码写在binding module里,对一个接口通过一直注解关联到对应实现类。

//Binding Module
class TextEditorModule extends AbstractModule {

   @Override
   protected void configure() {
      bind(SpellChecker.class).annotatedWith(WinWord.class)
         .to(WinWordSpellCheckerImpl.class);    
   } 
}

Inject using binding annotation

用的时候和@Name类似,指定注解来指定了实现类。

@Inject
public TextEditor(@WinWord SpellChecker spellChecker) {
   this.spellChecker = spellChecker;
}

Guice AOP

details

Important Classes

  • Matcher - Matcher is an interface to either accept or reject a value. In Guice AOP, we need two matchers: one to define which classes participate, and another for the methods of those classes.
  • MethodInterceptor - MethodInterceptors are executed when a matching method is called. They can inspect the call: the method, its arguments, and the receiving instance. We can perform cross-cutting logic and then delegate to the underlying method. Finally, we may inspect the return value or exception and return.

指定类和指定方法,再加个拦截器。

  • 重要方法: bindInterceptor, 写在Module里。

  • 拦截器需继承MethodInterceptor

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.matcher.Matchers;

public class GuiceTester {
   public static void main(String[] args) {
      Injector injector = Guice.createInjector(new TextEditorModule());
      TextEditor editor = injector.getInstance(TextEditor.class);
      editor.makeSpellCheck(); 
   } 
}

class TextEditor {
   private SpellChecker spellChecker;

   @Inject
   public TextEditor(SpellChecker spellChecker) {
      this.spellChecker = spellChecker;
   }

   public void makeSpellCheck(){
      spellChecker.checkSpelling();
   }
}

//Binding Module
class TextEditorModule extends AbstractModule {

   @Override
   protected void configure() {
      bind(SpellChecker.class).to(SpellCheckerImpl.class);
      bindInterceptor(Matchers.any(), 
         Matchers.annotatedWith(CallTracker.class), 
         new CallTrackerService());
   } 
}

//spell checker interface
interface SpellChecker {
   public void checkSpelling();
}

//spell checker implementation
class SpellCheckerImpl implements SpellChecker {

   @Override @CallTracker
   public void checkSpelling() {
      System.out.println("Inside checkSpelling." );
   } 
}

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD)
@interface CallTracker {}

class CallTrackerService implements MethodInterceptor  {

   @Override
   public Object invoke(MethodInvocation invocation) throws Throwable {
      System.out.println("Before " + invocation.getMethod().getName());
      Object result = invocation.proceed();
      System.out.println("After " + invocation.getMethod().getName());
      return result;
   }
}

Spring vs Guice

参考

参考

https://zhuanlan.zhihu.com/p/32299568

参考

Search

    Table of Contents