Skip to content

Commit

Permalink
add docs / support packages
Browse files Browse the repository at this point in the history
  • Loading branch information
graemerocher committed May 20, 2024
1 parent 191ddc5 commit e10346f
Show file tree
Hide file tree
Showing 16 changed files with 210 additions and 10 deletions.
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
projectVersion=1.0.0-SNAPSHOT
projectGroup=io.micronaut.guice

title=Micronaut guice
projectDesc=TODO
title=Micronaut Guice
projectDesc=Allows Importing Guice Modules
projectUrl=https://micronaut.io
githubSlug=micronaut-projects/micronaut-guice
developers=Graeme Rocher
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@
@AliasFor(member = "classes")
String[] classNames() default {};

/**
* Import the given packages as guice classes.
* @return An array of package names
*/
String[] packages() default {};

/**
* The environment where the modules should be active (Defaults to all environments).
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@
*/
package io.micronaut.guice.processor;

import static io.micronaut.core.util.StringUtils.EMPTY_STRING_ARRAY;

import com.google.inject.Module;
import com.google.inject.Provides;
import io.micronaut.context.annotation.Import;
import io.micronaut.context.annotation.Primary;
import io.micronaut.context.annotation.Requires;
import io.micronaut.core.annotation.AnnotationMetadata;
Expand All @@ -31,6 +34,7 @@
import io.micronaut.inject.processing.ProcessingException;
import io.micronaut.inject.visitor.TypeElementVisitor;
import io.micronaut.inject.visitor.VisitorContext;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

Expand All @@ -40,22 +44,41 @@ public class ImportModuleVisitor
public static final String MEMBER_ENVS = "environments";
public static final String MEMBER_MODULES = "modules";
public static final String MEMBER_CLASSES = "classes";
public static final String MEMBER_PACKAGES = "packages";

@Override
public void visitClass(ClassElement element, VisitorContext context) {
@NonNull String[] moduleNames = element.stringValues(Guice.class, MEMBER_MODULES);
@NonNull String[] classNames = element.stringValues(Guice.class, MEMBER_CLASSES);
@NonNull String[] packages = element.stringValues(Guice.class, MEMBER_PACKAGES);
@NonNull String[] envs = element.stringValues(Guice.class, MEMBER_ENVS);
List<ClassElement> classElements = new ArrayList<>();
for (String className : classNames) {
ClassElement classElement = context.getClassElement(className).orElse(null);
if (classElement == null) {
throw new ProcessingException(element, "Guice class import [" + className + "] must be on the compilation classpath");
} else {
BeanElementBuilder builder = element.addAssociatedBean(classElement);
builder.inject();
builder.typed(classElement);
classElements.add(classElement);
}
}

if (ArrayUtils.isNotEmpty(packages)) {
for (String aPackage : packages) {
final ClassElement[] inPackage = context
.getClassElements(aPackage, "*");
for (ClassElement classElement : inPackage) {
if (!classElement.isAbstract() && !classElement.isPrivate() && !classElement.isAssignable(Module.class)) {
classElements.add(classElement);
}
}
}
}

for (ClassElement classElement : classElements) {
BeanElementBuilder builder = element.addAssociatedBean(classElement);
builder.inject();
builder.typed(classElement);
}
for (int i = 0; i < moduleNames.length; i++) {
String className = moduleNames[i];
ClassElement moduleElement = context.getClassElement(className).orElse(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@
import io.micronaut.context.exceptions.NoSuchBeanException;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.Order;
import io.micronaut.core.beans.BeanIntrospection;
import io.micronaut.core.beans.BeanIntrospector;
import io.micronaut.core.order.Ordered;
import io.micronaut.core.reflect.InstantiationUtils;
import io.micronaut.core.type.Argument;
Expand Down Expand Up @@ -456,8 +458,21 @@ public LinkedBindingBuilderImpl(Argument<T> argument) {

@Override
public ScopedBindingBuilder to(Class<? extends T> implementation) {
BeanProvider<T> provider = applicationContext.getBean(Argument.of(BeanProvider.class, implementation));
this.supplier = provider::get;
if (applicationContext.containsBean(implementation)) {
BeanProvider<T> provider = applicationContext.getBean(Argument.of(BeanProvider.class, implementation));
this.supplier = provider::get;
} else {
BeanIntrospection<? extends T> introspection = BeanIntrospector.SHARED.findIntrospection(implementation).orElse(null);
if (introspection != null) {
this.supplier = introspection::instantiate;
} else {
Message message = new Message(currentSource != null ? currentSource : implementation, "Cannot create binding to type that is not itself declared a bean. " +
"Considering adding @Guice(classes=" + implementation.getSimpleName() + ".class) below your @Guice declaration.");
throw new com.google.inject.ConfigurationException(
List.of(message)
);
}
}
return this;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package io.micronaut.guice.doc.examples.bindings.annotations;

// tag::class[]
import com.google.inject.AbstractModule;
import com.google.inject.Provides;

final class CreditCardProcessorModule extends AbstractModule {
public final class CreditCardProcessorModule extends AbstractModule {
@Override
protected void configure() {
// This uses the optional `annotatedWith` clause in the `bind()` statement
Expand All @@ -20,3 +21,4 @@ public CreditCardProcessor provideCheckoutProcessor(
return processor;
}
}
// end::class[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.micronaut.guice.doc.examples.bindings.annotations2;

import static org.junit.jupiter.api.Assertions.assertInstanceOf;

import io.micronaut.core.annotation.Introspected;
import io.micronaut.guice.annotation.Guice;
import io.micronaut.guice.doc.examples.bindings.annotations2.impl.CheckoutCreditCardProcessor;
import io.micronaut.guice.doc.examples.bindings.annotations2.impl.CreditCardProcessor;
import io.micronaut.guice.doc.examples.bindings.annotations2.impl.CreditCardProcessorModule;
import io.micronaut.guice.doc.examples.bindings.annotations2.impl.GoogleCheckout;
import io.micronaut.guice.doc.examples.bindings.annotations2.impl.PayPal;
import io.micronaut.guice.doc.examples.bindings.annotations2.impl.PayPalCreditCardProcessor;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Test;

@MicronautTest(startApplication = false)
@Guice(
modules = CreditCardProcessorModule.class,
packages = "io.micronaut.guice.doc.examples.bindings.annotations2.impl"
)
@Introspected(classes = PayPalCreditCardProcessor.class)
class BindingAnnotation2Test {
@Inject @PayPal
CreditCardProcessor paypalProcessor;
@Inject @GoogleCheckout
CreditCardProcessor checkoutProcessor;

@Test
void testInjectWithQualifiers() {
assertInstanceOf(CheckoutCreditCardProcessor.class, checkoutProcessor);
assertInstanceOf(PayPalCreditCardProcessor.class, paypalProcessor);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.micronaut.guice.doc.examples.bindings.annotations2.impl;

public class CheckoutCreditCardProcessor implements CreditCardProcessor {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.micronaut.guice.doc.examples.bindings.annotations2.impl;

public interface CreditCardProcessor {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.micronaut.guice.doc.examples.bindings.annotations2.impl;

// tag::class[]
import com.google.inject.AbstractModule;
import com.google.inject.Provides;

public final class CreditCardProcessorModule extends AbstractModule {
@Override
protected void configure() {
// This uses the optional `annotatedWith` clause in the `bind()` statement
bind(CreditCardProcessor.class)
.annotatedWith(PayPal.class)
.to(PayPalCreditCardProcessor.class);
}

// This uses binding annotation with a @Provides method
@Provides
@GoogleCheckout
public CreditCardProcessor provideCheckoutProcessor(
CheckoutCreditCardProcessor processor) {
return processor;
}
}
// end::class[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.micronaut.guice.doc.examples.bindings.annotations2.impl;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import com.google.inject.BindingAnnotation;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;


@BindingAnnotation
@Target({ FIELD, PARAMETER, METHOD })
@Retention(RUNTIME)
public @interface GoogleCheckout {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.micronaut.guice.doc.examples.bindings.annotations2.impl;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import jakarta.inject.Qualifier;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Qualifier
@Target({ FIELD, PARAMETER, METHOD })
@Retention(RUNTIME)
public @interface PayPal {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.micronaut.guice.doc.examples.bindings.annotations2.impl;

public class PayPalCreditCardProcessor implements CreditCardProcessor {
}
12 changes: 11 additions & 1 deletion src/main/docs/guide/introduction.adoc
Original file line number Diff line number Diff line change
@@ -1 +1,11 @@
TODO
This module allows importing https://github.com/google/guice[Guice] modules into a Micronaut application making the Guice bindings available for Dependency Injection.

Note that only a subset of the Guice API (primarily what is implemented is the https://google.github.io/guice/api-docs/latest/javadoc/com/google/inject/Binder.html[Guice Binding EDSL]) and the following features are not supported:

* Custom Guice Scopes (other than Singleton) are not supported
* Guice AOP/Interceptors are not supported (use Micronaut AOP instead)
* Guice private modules are not supported
* Static Injection is not supported
* Guice TypeConverters are not supported (use `io.micronaut.core.convert.TypeConverter` instead).
* Guice Listeners are not supported (use `io.micronaut.context.event.BeanCreatedEventListener` instead.)
* None of the `com.google.inject.spi` API is supported.
36 changes: 36 additions & 0 deletions src/main/docs/guide/modules.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
Micronaut is based on implicit binding where bindings are resolved at runtime based on the available types. Guice uses explicit binding where in the form of modules you write bindings from one type to another.

These guice modules typically extend `com.google.inject.AbstractModule` and use the https://google.github.io/guice/api-docs/latest/javadoc/com/google/inject/Binder.html[Guice Binding EDSL]).

This integration allows you to import modules written for Guice and use them in a Micronaut application.

By declaring the ann:guice.annotation.Guice[] annotation on your `Application` class (or any class that is the logical central point or your application) you can import an existing Guice module. For example given the module:

.Importing a Guice Module
snippet::io.micronaut.guice.doc.examples.bindings.annotations.CreditCardProcessorModule[tags="class", indent=0, project="micronaut-guice"]

You can declare:

[source,java]
----
@Guice(modules = CreditCardProcessorModule.class)
----

If the module has constructor parameter these will need to also be https://docs.micronaut.io/latest/guide/#beans[declared as beans] or https://docs.micronaut.io/latest/guide/#beanImport[imported].

TIP: If you want the modules only imported for a particular environment (like `TEST` or `DEVELOPMENT`) using the `environments` member of the `@Guice` annotation.

You can register one or more modules. The order the modules are installed is dictated by the order of the `modules` array in the annotation.

Note that when registering bindings the target type (in the above case the `to(PayPalCreditCardProcessor.class)` declaration) must itself be a bean that is available since Micronaut will not reflectively instantiate the type on demand like Guice does. Hence you may also need to declare the `classes` member:

[source,java]
----
@Guice(
modules = CreditCardProcessorModule.class,
classes = PayPalCreditCardProcessor.class
)
----

TIP: To import multiple classes for injection use 'packages'.

8 changes: 7 additions & 1 deletion src/main/docs/guide/quickStart.adoc
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
TODO
To use this module add the following annotation processor:

dependency:io.micronaut.guice:micronaut-guice-processor[scope="annotationProcessor"]

Then add the `micronaut-guice` module:

dependency:io.micronaut.guice:micronaut-guice[scope="compile"]
1 change: 1 addition & 0 deletions src/main/docs/guide/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ introduction:
releaseHistory: Release History
quickStart:
title: Quick Start
modules: Importing Guice Modules
repository: Repository

0 comments on commit e10346f

Please sign in to comment.