Skip to content

Commit

Permalink
Implement ability to import Guice modules into Micronaut
Browse files Browse the repository at this point in the history
  • Loading branch information
graemerocher committed May 20, 2024
1 parent 8032ab5 commit 191ddc5
Show file tree
Hide file tree
Showing 50 changed files with 2,271 additions and 68 deletions.
4 changes: 0 additions & 4 deletions build.gradle

This file was deleted.

5 changes: 5 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
plugins {
id("io.micronaut.build.internal.docs")
id("io.micronaut.build.internal.quality-reporting")
}

Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,9 @@ plugins {
id 'io.micronaut.build.internal.guice-base'
id "io.micronaut.build.internal.module"
}

micronautBuild {
binaryCompatibility {
enabled.set(false)
}
}
44 changes: 8 additions & 36 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,50 +1,22 @@
#
# This file is used to declare the list of libraries
# which are used as dependencies in the project.
# See https://docs.gradle.org/7.4.2/userguide/platforms.html#sub:central-declaration-of-dependencies
#
# For Micronaut, we have 3 kinds of dependencies:
# - managed dependencies, which are exposed to consumers via a BOM (or version catalog)
# - managed BOMs, which are imported into the BOM that we generate
# - all other dependencies, which are implementation details
#
# If a library needs to appear in the BOM of the project, then it must be
# declared with the "managed-" prefix.
# If a BOM needs to be imported in the BOM of the project, then it must be
# declared with the "boms-" prefix.
# Both managed dependencies and BOMs need to have their version declared via
# a managed version (a version which alias starts with "managed-"

[versions]
micronaut = "4.4.8"
micronaut = "4.4.3"
micronaut-docs = "2.0.0"
micronaut-test = "4.2.1"
groovy = "4.0.17"
spock = "2.3-groovy-4.0"

# Managed versions appear in the BOM
# managed-somelib = "1.0"
# managed-somebom = "1.1"
managed-guice = "7.0.0"
managed-guava = "33.2.0-jre"

[libraries]
# Core
micronaut-core = { module = 'io.micronaut:micronaut-core-bom', version.ref = 'micronaut' }

#
# Managed dependencies appear in the BOM
#
# managed-somelib = { module = "group:artifact", version.ref = "managed-somelib" }

#
# Imported BOMs, also appearing in the generated BOM
#
# boms-somebom = { module = "com.foo:somebom", version.ref = "managed-somebom" }

# Other libraries used by the project but non managed

# micronaut-bom = { module = "io.micronaut:micronaut-bom", version.ref = "micronaut" }
# jdoctor = { module = "me.champeau.jdoctor:jdoctor-core", version.ref="jdoctor" }

[bundles]

[plugins]
managed-guice = { module = "com.google.inject:guice", version.ref = "managed-guice" }
managed-guava = { module = "com.google.guava:guava", version.ref = "managed-guava" }
junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api" }
junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine" }
junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params" }
9 changes: 9 additions & 0 deletions micronaut-guice-annotation/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
plugins {
id("io.micronaut.build.internal.guice-module")
}

dependencies {
implementation(libs.managed.guice) {
exclude(group="com.google.guava", module = "guava")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright 2017-2024 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.guice.annotation;

import com.google.inject.Module;
import io.micronaut.context.annotation.AliasFor;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation that can be applied to the application entry point
* that allows the import of Guice modules.
*
* <p>Micronaut will import the modules and run them at startup when the application starts
* registering the provided beans using the Guice DSL.</p>
*
* <p>Note all features of Guice are supported, there exist the following limitations:</p>
*
* <ol>
* <li>Guice Scopes are not supported</li>
* <li>Guice AOP/Interceptors are not supported</li>
* <li>Guice private modules are not supported</li>
* <li>Static Injection is not supported</li>
* <li>Guice TypeConverters are not supported (use {@link io.micronaut.core.convert.TypeConverter} instead.</li>
* <li>Guice Listeners are not supported (use {@link io.micronaut.context.event.BeanCreatedEventListener} instead.</li>
* <li>None of the {@code com.google.inject.spi} API is supported</li>
* </ol>
*
* <p>Note that if you create a runtime binding to a class with {@link com.google.inject.binder.LinkedBindingBuilder#to(Class)} that has no injection annotations you may need to import the bean first
* to allow the bean to be instantiated without reflection. This can be done with {@link io.micronaut.context.annotation.Import}</p>
*
* <p>Otherwise it is recommended to as a minimum use the {@link jakarta.inject.Inject} annotation on the constructor to avoid this need.</p>
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Guice {
/**
* Import the given Guice modules.
*
* <p>The modules are imported in the order defined by the array.</p>
*
* @return An array of module types
*/
Class<? extends Module>[] modules();

/**
* Import the given Guice classes.
*
* @return An array of classes to import
*/
Class<?>[] classes() default {};

/**
* Import the given named Guice classes.
*
* @return An array of class names to import
*/
@AliasFor(member = "classes")
String[] classNames() default {};

/**
* The environment where the modules should be active (Defaults to all environments).
*
* @return The environments.
*/
String[] environments() default {};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2017-2024 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.guice.annotation.internal;

import io.micronaut.context.annotation.Bean;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
* Internal meta-annotation for identifying Guice annotated beans.
*
* <p>A Guice annotated bean is a bean that is meta annotated with the annotation {@link com.google.inject.ScopeAnnotation}.</p>
*/
@Retention(RetentionPolicy.SOURCE)
@Internal
public @interface GuiceAnnotation {
@NonNull AnnotationValue<GuiceAnnotation> ANNOTATION_VALUE = AnnotationValue.builder(GuiceAnnotation.class).build();
}
4 changes: 0 additions & 4 deletions micronaut-guice-bom/build.gradle

This file was deleted.

10 changes: 10 additions & 0 deletions micronaut-guice-bom/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
plugins {
id("io.micronaut.build.internal.guice-base")
id("io.micronaut.build.internal.bom")
}

micronautBuild {
binaryCompatibility {
enabled.set(false)
}
}
13 changes: 13 additions & 0 deletions micronaut-guice-processor/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
plugins {
id("io.micronaut.build.internal.guice-module")
}

dependencies {
implementation(projects.micronautGuiceAnnotation)
implementation(mn.micronaut.core.processor)
implementation(libs.managed.guice) {
exclude(group="com.google.guava", module = "guava")
}
testImplementation(mn.micronaut.inject.java.test)
testImplementation(projects.micronautGuice)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2017-2024 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.guice.processor;

import com.google.inject.BindingAnnotation;
import io.micronaut.core.annotation.AnnotationUtil;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.guice.annotation.internal.GuiceAnnotation;
import io.micronaut.inject.annotation.TypedAnnotationTransformer;
import io.micronaut.inject.visitor.VisitorContext;
import java.util.List;

/**
* Transforms {@link com.google.inject.BindingAnnotation} to {@link jakarta.inject.Qualifier}.
*/
public class BindingAnnotationTransformer
implements TypedAnnotationTransformer<BindingAnnotation> {
@Override
public Class<BindingAnnotation> annotationType() {
return BindingAnnotation.class;
}

@Override
public List<AnnotationValue<?>> transform(AnnotationValue<BindingAnnotation> annotation, VisitorContext visitorContext) {
return List.of(
AnnotationValue.builder(AnnotationUtil.QUALIFIER)
.build(),
GuiceAnnotation.ANNOTATION_VALUE
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright 2017-2024 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.guice.processor;

import com.google.inject.RestrictedBindingSource;
import io.micronaut.context.annotation.Bean;
import io.micronaut.core.annotation.AnnotationClassValue;
import io.micronaut.guice.annotation.internal.GuiceAnnotation;
import io.micronaut.inject.ast.ClassElement;
import io.micronaut.inject.ast.ConstructorElement;
import io.micronaut.inject.processing.ProcessingException;
import io.micronaut.inject.visitor.TypeElementVisitor;
import io.micronaut.inject.visitor.VisitorContext;
import java.util.Set;

/**
* Guice beans have a only 1 binding type. This visitor resolves that.
*/
public class GuiceBeanVisitor
implements TypeElementVisitor<Object, Object> {
@Override
public VisitorKind getVisitorKind() {
return VisitorKind.ISOLATING;
}

@Override
public Set<String> getSupportedAnnotationNames() {
return Set.of(GuiceAnnotation.class.getName(), "com.google.inject.*");
}

@Override
public void visitConstructor(ConstructorElement element, VisitorContext context) {
}

@Override
public void visitClass(ClassElement element, VisitorContext context) {
if (element.hasDeclaredAnnotation(RestrictedBindingSource.class)) {
throw new ProcessingException(element, "The @RestrictedBindingSource annotation is not supported");
}
if (element.hasStereotype(GuiceAnnotation.class)) {
exposeOnlyType(element);
}
}

private static void exposeOnlyType(ClassElement element) {
if (!element.isPresent(Bean.class, "typed")) {
element.annotate(Bean.class, builder ->
builder.member("typed", new AnnotationClassValue<>(element.getName()))
);
}
}
}
Loading

0 comments on commit 191ddc5

Please sign in to comment.