In guice, the injector is used to assemble an object graph. When a type of instance is requested, the injector determines how to create an instance and parse dependencies Based on the object graph. To determine how to parse dependencies, you must configure the binding method of the injector.
To create a binding object, you can inherit from the abstractmodule class and overwrite its configure method. Call the BIND () method in the method to specify each binding, these methods carry the type check. If you use an incorrect type compiler, a compilation error is reported. If you have already written the module class, create a module class object and pass it as a parameter to guice. createinjector () to create a injector.
You can use the module object to create a link binding (linked bindings), instance bindings, @ provides methods, provider bindings, and constructor bindings) untargetted bindings ). These binding methods are collectively referred to as built-in binding, and there is also a kind of timely binding. If a dependency cannot be found in the built-in binding when parsing, guice will create a timely binding.
1. link binding (linkdedbindings)
Link binding maps a type to its implementation class, for example, ing the transactionlog interface to the implementation class databasetransactionlog:
public class BillingModule extends AbstractModule { @Override protected void configure() { bind(TransactionLog.class).to(DatabaseTransactionLog.class); }}
In this way, the databasetransactionlog object will be used when you call the injector. getinstance (transactionlog. Class) method or when the injector encounters a transactionlog dependency. The link is from a type to any child type of it, which includes the interface implementation class and the subclass of the class. Therefore, the following ing is also possible: BIND (databasetransactionlog. class ). to (mysqldatabasetransactionlog. class );
In addition, link binding supports the following method:
public class BillingModule extends AbstractModule { @Override protected void configure() { bind(TransactionLog.class).to(DatabaseTransactionLog.class); bind(DatabaseTransactionLog.class).to(MySqlDatabaseTransactionLog.class); }}
In this case, when a transactionlog object is requested, the injector returns a mysqldatabasetransactionlog object.
Ii. Bind Annotation
In some cases, you may want to set multiple Bindings for the same type. This can be achieved through binding annotations. The annotation and binding type are used to uniquely meet a binding,
Together, it is called a key. Example:
package example.pizza;import com.google.inject.BindingAnnotation;import java.lang.annotation.Target;import java.lang.annotation.Retention;import static java.lang.annotation.RetentionPolicy.RUNTIME;import static java.lang.annotation.ElementType.PARAMETER;import static java.lang.annotation.ElementType.FIELD;import static java.lang.annotation.ElementType.METHOD;@BindingAnnotation @Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME)public @interface PayPal {}
The key here is the @ bindingannotation meta annotation. When guice describes the annotation, it uses PayPal as the binding annotation.
Then use the annotatedwith statement in the configure method of the module, as follows:
BIND (creditcardprocessor. Class). annotatedwith (Paypal. Class). To (paypalcreditcardprocessor. Class );
In this way, creditcardprocessor is mapped to paypalcreditcardprocessor.
Usage:
public class RealBillingService implements BillingService {@Injectpublic RealBillingService(@PayPal CreditCardProcessor processor,TransactionLog transactionLog) {...}}
In another case, we can use the @ named annotation defined by guice, for example:
public class RealBillingService implements BillingService { @Inject public RealBillingService(@Named("Checkout") CreditCardProcessor processor,TransactionLog transactionLog) {... }}
To bind a specific name, use names. Named () to create an implementation and pass it to annotatedwith:
bind(CreditCardProcessor.class) .annotatedWith(Names.named("Checkout")) .to(CheckoutCreditCardProcessor.class);
Because the compiler does not check the string, guice recommends that we use less @ named annotations, but I personally think that as long as the name is not wrong when writing the code by myself,
This method is the easiest way to map multiple bindings to the same type. This is similar to the implementation method in spring. Can I specify a name for @ service, @ controller, and @ repository of spring?
Iii. instance bindings)
Through instance binding, We can bind a specific instance to a certain type. This is only applicable to the absence of other dependencies between these instance types, such as value objects:
bind(String.class) .annotatedWith(Names.named("JDBC URL")) .toInstance("jdbc:mysql://localhost/pizza"); bind(Integer.class) .annotatedWith(Names.named("login timeout seconds")) .toInstance(10);
Guice recommends that you avoid using. toinstance to create complex objects because it will delay application startup. Similarly, you can use the @ provides method.
4. @ provides Method
When you use the @ provides method to create an object, this method must be defined in the module class and it must be annotated with @ provides. The return value type of this method is the bound object. When the injector needs an instance of this type, it will call this method.
public class BillingModule extends AbstractModule { @Override protected void configure() { ... } @Provides TransactionLog provideTransactionLog() { DatabaseTransactionLog transactionLog = new DatabaseTransactionLog(); transactionLog.setJdbcUrl("jdbc:mysql://localhost/pizza"); transactionLog.setThreadPoolSize(30); return transactionLog; }}
If @ PayPal or @ named ("checkout") is bound to the Annotation on the @ provides method, guice takes precedence over binding the annotation. Before calling the @ provides method, guice will first parse the dependencies of the method:
@Provides @PayPalCreditCardProcessor providePayPalCreditCardProcessor(@Named("PayPal API key") String apiKey) {PayPalCreditCardProcessor processor = new PayPalCreditCardProcessor();processor.setApiKey(apiKey);return processor;}
Exception:
Guice cannot throw an exception in the @ provides method. If an exception is thrown, the exception is encapsulated in the provisionexception object.
5. provider bindings)
If the @ provides method becomes more complex, we may want to move them into a separate class. A provider class implements the provider interface, which is
A simple common interface for providing values.
public interface Provider<T> { T get();}
If the provider's implementation class has its own dependencies, you can add the @ inject annotation to its constructor for injection to ensure safe return of values.
public class DatabaseTransactionLogProvider implements Provider<TransactionLog> { private final Connection connection; @Inject public DatabaseTransactionLogProvider(Connection connection) { this.connection = connection; } public TransactionLog get() { DatabaseTransactionLog transactionLog = new DatabaseTransactionLog(); transactionLog.setConnection(connection); return transactionLog; }}
Finally, use the. toprovider statement to bind to the provider:
public class BillingModule extends AbstractModule { @Override protected void configure() { bind(TransactionLog.class) .toProvider(DatabaseTransactionLogProvider.class); }}
6. No target binding
Guice allows us to create a binding without specifying the target class, that is, there is no to Statement, which is useful for the specific class or the @ implementedby or @ providedby annotation type. For example:
BIND (myconcreteclass. Class );
BIND (anotherconcreteclass. Class). In (singleton. Class );
However, when binding annotations, we must specify the binding target for our dependencies, that is, it is a specific class, for example:
bind(MyConcreteClass.class).annotatedWith(Names.named("foo")) .to(MyConcreteClass.class);bind(AnotherConcreteClass.class).annotatedWith(Names.named("foo")) .to(AnotherConcreteClass.class) .in(Singleton.class);
7. constructor binding
Sometimes you may need to bind a certain type to any construction method. For example, the @ inject annotation cannot be added to the target class constructor because the class is
Provided by a third party, or the class has multiple build methods involved in dependency injection. At this point, the @ provides method is the best solution to solve this problem, because it can explicitly specify
Which constructor is called, and reflection is not required. However, the @ provides method is restricted in some places. For example, manually creating an object cannot be used in AOP.
For this reason, guice uses toconstructor () for binding, which requires us to use reflection to select the constructor and handle exceptions.
public class BillingModule extends AbstractModule { @Override protected void configure() { try { bind(TransactionLog.class).toConstructor( DatabaseTransactionLog.class.getConstructor(DatabaseConnection.class)); } catch (NoSuchMethodException e) { addError(e); } }}
In the above example, the databasetransactionlog class must have a constructor with the databaseconnection parameter. In this constructor, you do not need to use the @ inject annotation guice to automatically call this constructor. The bound scope of each toconstructor () Statement is independent. If you have created multiple Singleton bindings and used the same constructor method of the target class, each binding still has its own instance.
8. Timely binding
When the injector needs a certain type of instance, it needs to obtain a binding. Binding in the module class is called display binding. As long as they are available, the injector can use them. If you need an instance of a certain type, but it does not show the binding, the injector will try to create a timely binding (just-in-time bindings ), it is also known as JIT binding and implicit binding.
It can be used to create timely binding as follows:
A. There is a suitable construction method, that is, non-private, without parameters or a constructor marked with the @ inject annotation, for example:
public class PayPalCreditCardProcessor implements CreditCardProcessor { private final String apiKey; @Inject public PayPalCreditCardProcessor(@Named("PayPal API key") String apiKey) { this.apiKey = apiKey; }}
Guice does not create an internal class instance unless it has a static modifier, because the internal class contains an implicit reference pointing to the external class, and this implicit reference cannot be injected.
B. @ implementedby
@ Implementedby the annotation is used to tell the injector what is the default Implementation type of a type, which is similar to the link binding. Bind a subtype to a type as follows:
@ImplementedBy(PayPalCreditCardProcessor.class)public interface CreditCardProcessor { ChargeResult charge(String amount, CreditCard creditCard) throws UnreachableException;}
@ Implementedby (paypalcreditcardprocessor. Class) is equivalent to the following BIND () Statement:
bind(CreditCardProcessor.class).to(PayPalCreditCardProcessor.class);
If a type has both BIND () and @ implementedby annotations, the BIND () statement takes precedence. Be careful when using @ implementedby because it adds the compile-time dependency for the interface.
C. @ providedby
@ Providedby annotation is used to tell the injector what is implemented by the provider class, for example:
@ProvidedBy(DatabaseTransactionLogProvider.class)public interface TransactionLog { void logConnectException(UnreachableException e); void logChargeResult(ChargeResult result);}
This is equivalent to bind (transactionlog. Class). toprovider (databasetransactionlogprovider. Class );
Similar to the @ implementedby annotation, if a type uses both the BIND () Statement and the @ providedby annotation, the BIND () statement takes precedence.
Google guice binding method