DI improvements (#1717)

* Code cleanup
* Provide build path with causes when an exception occur
* Fix toString to display meaningful info
* Javadoc
* Add @NonNull and @Overrides annotations
* Support for @Nullable on fields and parameters
This commit is contained in:
Guillaume Nodet 2024-09-12 08:14:46 +02:00 committed by GitHub
parent 36de1c6e51
commit bcd5d9c9f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 183 additions and 84 deletions

View File

@ -29,6 +29,13 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Indicates that the annotated bean has a lifespan limited to a given mojo execution,
* which means each mojo execution will result in a different instance being injected.
* <p>
* The following objects will be bound to the mojo execution scope:
* <ul>
* <li>{@code org.apache.maven.api.MojoExecution}</li>
* <li>{@code org.apache.maven.api.Project}</li>
* <li>{@code org.apache.maven.api.plugin.Log}</li>
* </ul>
*
* @since 4.0.0
*/

View File

@ -29,6 +29,8 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Indicates that annotated component should be instantiated before session execution starts
* and discarded after session execution completes.
* <p>
* A {@code org.apache.maven.api.Session} object is available in the scope of this annotation.
*
* @since 4.0.0
*/

View File

@ -30,5 +30,5 @@ import java.lang.annotation.RetentionPolicy;
*/
@Experimental
@Documented
@Retention(RetentionPolicy.CLASS)
@Retention(RetentionPolicy.RUNTIME)
public @interface Nullable {}

View File

@ -48,6 +48,7 @@ import org.apache.maven.di.Injector;
import org.apache.maven.di.Key;
import org.apache.maven.di.impl.Binding;
import org.apache.maven.di.impl.DIException;
import org.apache.maven.di.impl.Dependency;
import org.apache.maven.di.impl.InjectorImpl;
import org.apache.maven.execution.scope.internal.MojoExecutionScope;
import org.apache.maven.session.scope.internal.SessionScope;
@ -66,7 +67,8 @@ public class SisuDiBridgeModule extends AbstractModule {
injector = new InjectorImpl() {
@Override
public <Q> Supplier<Q> getCompiledBinding(Key<Q> key) {
public <Q> Supplier<Q> getCompiledBinding(Dependency<Q> dep) {
Key<Q> key = dep.key();
Set<Binding<Q>> res = getBindings(key);
if (res != null && !res.isEmpty()) {
List<Binding<Q>> bindingList = new ArrayList<>(res);
@ -119,6 +121,9 @@ public class SisuDiBridgeModule extends AbstractModule {
// ignore
e.printStackTrace();
}
if (dep.optional()) {
return () -> null;
}
throw new DIException("No binding to construct an instance for key "
+ key.getDisplayString() + ". Existing bindings:\n"
+ getBoundKeys().stream()

View File

@ -21,6 +21,7 @@ package org.apache.maven.di;
import java.lang.annotation.Annotation;
import java.util.function.Supplier;
import org.apache.maven.api.annotations.Nonnull;
import org.apache.maven.di.impl.InjectorImpl;
public interface Injector {
@ -29,18 +30,24 @@ public interface Injector {
// Builder API
//
@Nonnull
static Injector create() {
return new InjectorImpl();
}
@Nonnull
Injector discover(ClassLoader classLoader);
@Nonnull
Injector bindScope(Class<? extends Annotation> scopeAnnotation, Scope scope);
@Nonnull
Injector bindScope(Class<? extends Annotation> scopeAnnotation, Supplier<Scope> scope);
@Nonnull
Injector bindImplicit(Class<?> cls);
@Nonnull
<T> Injector bindInstance(Class<T> cls, T instance);
//

View File

@ -34,16 +34,16 @@ import org.apache.maven.di.Key;
import static java.util.stream.Collectors.joining;
public abstract class Binding<T> {
private final Set<Key<?>> dependencies;
private final Set<Dependency<?>> dependencies;
private Annotation scope;
private int priority;
private Key<?> originalKey;
protected Binding(Key<? extends T> originalKey, Set<Key<?>> dependencies) {
protected Binding(Key<? extends T> originalKey, Set<Dependency<?>> dependencies) {
this(originalKey, dependencies, null, 0);
}
protected Binding(Key<?> originalKey, Set<Key<?>> dependencies, Annotation scope, int priority) {
protected Binding(Key<?> originalKey, Set<Dependency<?>> dependencies, Annotation scope, int priority) {
this.originalKey = originalKey;
this.dependencies = dependencies;
this.scope = scope;
@ -56,15 +56,18 @@ public abstract class Binding<T> {
public static <R> Binding<R> to(Key<R> originalKey, TupleConstructorN<R> constructor, Class<?>[] types) {
return Binding.to(
originalKey, constructor, Stream.of(types).map(Key::of).toArray(Key<?>[]::new));
originalKey,
constructor,
Stream.of(types).map(c -> new Dependency<>(Key.of(c), false)).toArray(Dependency<?>[]::new));
}
public static <R> Binding<R> to(Key<R> originalKey, TupleConstructorN<R> constructor, Key<?>[] dependencies) {
public static <R> Binding<R> to(
Key<R> originalKey, TupleConstructorN<R> constructor, Dependency<?>[] dependencies) {
return to(originalKey, constructor, dependencies, 0);
}
public static <R> Binding<R> to(
Key<R> originalKey, TupleConstructorN<R> constructor, Key<?>[] dependencies, int priority) {
Key<R> originalKey, TupleConstructorN<R> constructor, Dependency<?>[] dependencies, int priority) {
return new BindingToConstructor<>(originalKey, constructor, dependencies, priority);
}
@ -94,13 +97,17 @@ public abstract class Binding<T> {
this.scope,
this.priority) {
@Override
public Supplier<T> compile(Function<Key<?>, Supplier<?>> compiler) {
public Supplier<T> compile(Function<Dependency<?>, Supplier<?>> compiler) {
final Supplier<T> compiledBinding = Binding.this.compile(compiler);
final Consumer<T> consumer = bindingInitializer.compile(compiler);
return () -> {
T instance = compiledBinding.get();
consumer.accept(instance);
return instance;
try {
T instance = compiledBinding.get();
consumer.accept(instance);
return instance;
} catch (DIException e) {
throw new DIException("Error while initializing binding " + Binding.this, e);
}
};
}
@ -111,9 +118,9 @@ public abstract class Binding<T> {
};
}
public abstract Supplier<T> compile(Function<Key<?>, Supplier<?>> compiler);
public abstract Supplier<T> compile(Function<Dependency<?>, Supplier<?>> compiler);
public Set<Key<?>> getDependencies() {
public Set<Dependency<?>> getDependencies() {
return dependencies;
}
@ -122,7 +129,7 @@ public abstract class Binding<T> {
}
public String getDisplayString() {
return dependencies.stream().map(Key::getDisplayString).collect(joining(", ", "[", "]"));
return dependencies.stream().map(Dependency::getDisplayString).collect(joining(", ", "[", "]"));
}
public Key<?> getOriginalKey() {
@ -152,7 +159,7 @@ public abstract class Binding<T> {
}
@Override
public Supplier<T> compile(Function<Key<?>, Supplier<?>> compiler) {
public Supplier<T> compile(Function<Dependency<?>, Supplier<?>> compiler) {
return () -> instance;
}
@ -164,17 +171,17 @@ public abstract class Binding<T> {
public static class BindingToConstructor<T> extends Binding<T> {
final TupleConstructorN<T> constructor;
final Key<?>[] args;
final Dependency<?>[] args;
BindingToConstructor(
Key<? extends T> key, TupleConstructorN<T> constructor, Key<?>[] dependencies, int priority) {
Key<? extends T> key, TupleConstructorN<T> constructor, Dependency<?>[] dependencies, int priority) {
super(key, new HashSet<>(Arrays.asList(dependencies)), null, priority);
this.constructor = constructor;
this.args = dependencies;
}
@Override
public Supplier<T> compile(Function<Key<?>, Supplier<?>> compiler) {
public Supplier<T> compile(Function<Dependency<?>, Supplier<?>> compiler) {
return () -> {
Object[] args =
Stream.of(this.args).map(compiler).map(Supplier::get).toArray();
@ -184,7 +191,7 @@ public abstract class Binding<T> {
@Override
public String toString() {
return "BindingToConstructor[" + constructor + "]" + getDependencies();
return "BindingToConstructor[" + getOriginalKey() + "]" + getDependencies();
}
}
}

View File

@ -25,32 +25,30 @@ import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.apache.maven.di.Key;
import static java.util.stream.Collectors.toSet;
public abstract class BindingInitializer<T> {
private final Set<Key<?>> dependencies;
private final Set<Dependency<?>> dependencies;
protected BindingInitializer(Set<Key<?>> dependencies) {
protected BindingInitializer(Set<Dependency<?>> dependencies) {
this.dependencies = dependencies;
}
public Set<Key<?>> getDependencies() {
public Set<Dependency<?>> getDependencies() {
return dependencies;
}
public abstract Consumer<T> compile(Function<Key<?>, Supplier<?>> compiler);
public abstract Consumer<T> compile(Function<Dependency<?>, Supplier<?>> compiler);
public static <T> BindingInitializer<T> combine(List<BindingInitializer<T>> bindingInitializers) {
Set<Key<?>> deps = bindingInitializers.stream()
Set<Dependency<?>> deps = bindingInitializers.stream()
.map(BindingInitializer::getDependencies)
.flatMap(Collection::stream)
.collect(toSet());
return new BindingInitializer<T>(deps) {
return new BindingInitializer<>(deps) {
@Override
public Consumer<T> compile(Function<Key<?>, Supplier<?>> compiler) {
public Consumer<T> compile(Function<Dependency<?>, Supplier<?>> compiler) {
return instance -> bindingInitializers.stream()
.map(bindingInitializer -> bindingInitializer.compile(compiler))
.forEach(i -> i.accept(instance));

View File

@ -0,0 +1,31 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* http://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 org.apache.maven.di.impl;
import org.apache.maven.di.Key;
public record Dependency<T>(Key<T> key, boolean optional) {
public String getDisplayString() {
String s = key.getDisplayString();
if (optional) {
s = "?" + s;
}
return s;
}
}

View File

@ -64,12 +64,14 @@ public class InjectorImpl implements Injector {
bindScope(Singleton.class, new SingletonScope());
}
@Override
public <T> T getInstance(Class<T> key) {
return getInstance(Key.of(key));
}
@Override
public <T> T getInstance(Key<T> key) {
return getCompiledBinding(key).get();
return getCompiledBinding(new Dependency<>(key, false)).get();
}
@SuppressWarnings("unchecked")
@ -88,7 +90,7 @@ public class InjectorImpl implements Injector {
try (InputStream is = enumeration.nextElement().openStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(Objects.requireNonNull(is)))) {
for (String line :
reader.lines().filter(l -> !l.startsWith("#")).collect(Collectors.toList())) {
reader.lines().filter(l -> !l.startsWith("#")).toList()) {
Class<?> clazz = classLoader.loadClass(line);
bindImplicit(clazz);
}
@ -100,10 +102,12 @@ public class InjectorImpl implements Injector {
return this;
}
@Override
public Injector bindScope(Class<? extends Annotation> scopeAnnotation, Scope scope) {
return bindScope(scopeAnnotation, () -> scope);
}
@Override
public Injector bindScope(Class<? extends Annotation> scopeAnnotation, Supplier<Scope> scope) {
if (scopes.put(scopeAnnotation, scope) != null) {
throw new DIException(
@ -112,6 +116,7 @@ public class InjectorImpl implements Injector {
return this;
}
@Override
public <U> Injector bindInstance(Class<U> clazz, U instance) {
Key<?> key = Key.of(clazz, ReflectionUtils.qualifierOf(clazz));
Binding<U> binding = Binding.toInstance(instance);
@ -133,7 +138,7 @@ public class InjectorImpl implements Injector {
return this;
}
private LinkedHashSet<Key<?>> current = new LinkedHashSet<>();
private final LinkedHashSet<Key<?>> current = new LinkedHashSet<>();
private Injector doBind(Key<?> key, Binding<?> binding) {
if (!current.add(key)) {
@ -175,7 +180,8 @@ public class InjectorImpl implements Injector {
return bindings;
}
public <Q> Supplier<Q> getCompiledBinding(Key<Q> key) {
public <Q> Supplier<Q> getCompiledBinding(Dependency<Q> dep) {
Key<Q> key = dep.key();
Set<Binding<Q>> res = getBindings(key);
if (res != null && !res.isEmpty()) {
List<Binding<Q>> bindingList = new ArrayList<>(res);
@ -211,6 +217,9 @@ public class InjectorImpl implements Injector {
return () -> (Q) map(map);
}
}
if (dep.optional()) {
return () -> null;
}
throw new DIException("No binding to construct an instance for key "
+ key.getDisplayString() + ". Existing bindings:\n"
+ getBoundKeys().stream()
@ -312,14 +321,13 @@ public class InjectorImpl implements Injector {
this.mapper = mapper;
}
@SuppressWarnings("NullableProblems")
@Override
public Set<Entry<K, V>> entrySet() {
return new AbstractSet<Entry<K, V>>() {
return new AbstractSet<>() {
@Override
public Iterator<Entry<K, V>> iterator() {
Iterator<Entry<K, T>> it = delegate.entrySet().iterator();
return new Iterator<Entry<K, V>>() {
return new Iterator<>() {
@Override
public boolean hasNext() {
return it.hasNext();

View File

@ -171,14 +171,14 @@ public final class ReflectionUtils {
List<Constructor<?>> constructors = Arrays.asList(cls.getDeclaredConstructors());
List<Constructor<?>> injectConstructors = constructors.stream()
.filter(c -> c.isAnnotationPresent(Inject.class))
.collect(toList());
.toList();
List<Method> factoryMethods = Arrays.stream(cls.getDeclaredMethods())
.filter(method -> method.getReturnType() == cls && Modifier.isStatic(method.getModifiers()))
.collect(toList());
.toList();
List<Method> injectFactoryMethods = factoryMethods.stream()
.filter(method -> method.isAnnotationPresent(Inject.class))
.collect(toList());
.toList();
if (!injectConstructors.isEmpty()) {
if (injectConstructors.size() > 1) {
@ -240,10 +240,12 @@ public final class ReflectionUtils {
public static <T> BindingInitializer<T> fieldInjector(Key<T> container, Field field) {
field.setAccessible(true);
Key<Object> key = keyOf(container.getType(), field.getGenericType(), field);
return new BindingInitializer<T>(Collections.singleton(key)) {
boolean optional = field.isAnnotationPresent(Nullable.class);
Dependency<Object> dep = new Dependency<>(key, optional);
return new BindingInitializer<T>(Collections.singleton(dep)) {
@Override
public Consumer<T> compile(Function<Key<?>, Supplier<?>> compiler) {
Supplier<?> binding = compiler.apply(key);
public Consumer<T> compile(Function<Dependency<?>, Supplier<?>> compiler) {
Supplier<?> binding = compiler.apply(dep);
return (T instance) -> {
Object arg = binding.get();
try {
@ -258,10 +260,10 @@ public final class ReflectionUtils {
public static <T> BindingInitializer<T> methodInjector(Key<T> container, Method method) {
method.setAccessible(true);
Key<?>[] dependencies = toDependencies(container.getType(), method);
Dependency<?>[] dependencies = toDependencies(container.getType(), method);
return new BindingInitializer<T>(new HashSet<>(Arrays.asList(dependencies))) {
@Override
public Consumer<T> compile(Function<Key<?>, Supplier<?>> compiler) {
public Consumer<T> compile(Function<Dependency<?>, Supplier<?>> compiler) {
return instance -> {
Object[] args = getDependencies().stream()
.map(compiler)
@ -279,35 +281,31 @@ public final class ReflectionUtils {
};
}
public static Key<?>[] toDependencies(@Nullable Type container, Executable executable) {
Key<?>[] keys = toArgDependencies(container, executable);
public static Dependency<?>[] toDependencies(@Nullable Type container, Executable executable) {
Dependency<?>[] keys = toArgDependencies(container, executable);
if (executable instanceof Constructor || Modifier.isStatic(executable.getModifiers())) {
return keys;
} else {
Key<?>[] nkeys = new Key[keys.length + 1];
nkeys[0] = Key.ofType(container);
Dependency<?>[] nkeys = new Dependency[keys.length + 1];
nkeys[0] = new Dependency<>(Key.ofType(container), false);
System.arraycopy(keys, 0, nkeys, 1, keys.length);
return nkeys;
}
}
private static Key<?>[] toArgDependencies(@Nullable Type container, Executable executable) {
private static Dependency<?>[] toArgDependencies(@Nullable Type container, Executable executable) {
Parameter[] parameters = executable.getParameters();
Key<?>[] dependencies = new Key<?>[parameters.length];
Dependency<?>[] dependencies = new Dependency<?>[parameters.length];
if (parameters.length == 0) {
return dependencies;
}
Type type = parameters[0].getParameterizedType();
Parameter parameter = parameters[0];
dependencies[0] = keyOf(container, type, parameter);
Type[] genericParameterTypes = executable.getGenericParameterTypes();
boolean hasImplicitDependency = genericParameterTypes.length != parameters.length;
for (int i = 1; i < dependencies.length; i++) {
type = genericParameterTypes[hasImplicitDependency ? i - 1 : i];
parameter = parameters[i];
dependencies[i] = keyOf(container, type, parameter);
for (int i = 0; i < dependencies.length; i++) {
Type type = genericParameterTypes[i];
Parameter parameter = parameters[i];
boolean optional = parameter.isAnnotationPresent(Nullable.class);
dependencies[i] = new Dependency<>(keyOf(container, type, parameter), optional);
}
return dependencies;
}
@ -353,7 +351,7 @@ public final class ReflectionUtils {
public static <T> Binding<T> bindingFromConstructor(Key<T> key, Constructor<T> constructor) {
constructor.setAccessible(true);
Key<?>[] dependencies = toDependencies(key.getType(), constructor);
Dependency<?>[] dependencies = toDependencies(key.getType(), constructor);
Binding<T> binding = Binding.to(
key,

View File

@ -186,8 +186,7 @@ public class Types {
if (type instanceof Class) {
return type;
}
if (type instanceof TypeVariable<?>) {
TypeVariable<?> typeVariable = (TypeVariable<?>) type;
if (type instanceof TypeVariable<?> typeVariable) {
Type actualType = bindings.apply(typeVariable);
if (actualType == null) {
throw new TypeNotBoundException("Type variable not found: " + typeVariable + " ( "
@ -195,8 +194,7 @@ public class Types {
}
return actualType;
}
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
if (type instanceof ParameterizedType parameterizedType) {
Type[] typeArguments = parameterizedType.getActualTypeArguments();
Type[] typeArguments2 = new Type[typeArguments.length];
for (int i = 0; i < typeArguments.length; i++) {
@ -209,8 +207,7 @@ public class Types {
Type componentType = ((GenericArrayType) type).getGenericComponentType();
return new GenericArrayTypeImpl(bind(componentType, bindings));
}
if (type instanceof WildcardType) {
WildcardType wildcardType = (WildcardType) type;
if (type instanceof WildcardType wildcardType) {
Type[] upperBounds = wildcardType.getUpperBounds();
Type[] upperBounds2 = new Type[upperBounds.length];
for (int i = 0; i < upperBounds.length; i++) {
@ -309,8 +306,7 @@ public class Types {
return original;
}
if (original instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) original;
if (original instanceof ParameterizedType parameterizedType) {
Type[] typeArguments = parameterizedType.getActualTypeArguments();
Type[] repackedTypeArguments = simplifyTypes(typeArguments);
@ -329,8 +325,7 @@ public class Types {
throw new IllegalArgumentException("Key should not contain a type variable: " + original);
}
if (original instanceof WildcardType) {
WildcardType wildcardType = (WildcardType) original;
if (original instanceof WildcardType wildcardType) {
Type[] upperBounds = wildcardType.getUpperBounds();
if (upperBounds.length == 1) {
Type upperBound = upperBounds[0];
@ -398,8 +393,7 @@ public class Types {
private static boolean isAssignable(Type to, Type from, boolean strict) {
if (to instanceof WildcardType || from instanceof WildcardType) {
Type[] toUppers, toLowers;
if (to instanceof WildcardType) {
WildcardType wildcardTo = (WildcardType) to;
if (to instanceof WildcardType wildcardTo) {
toUppers = wildcardTo.getUpperBounds();
toLowers = wildcardTo.getLowerBounds();
} else {
@ -408,10 +402,9 @@ public class Types {
}
Type[] fromUppers, fromLowers;
if (from instanceof WildcardType) {
WildcardType wildcardTo = (WildcardType) to;
fromUppers = wildcardTo.getUpperBounds();
fromLowers = wildcardTo.getLowerBounds();
if (from instanceof WildcardType wildcardFrom) {
fromUppers = wildcardFrom.getUpperBounds();
fromLowers = wildcardFrom.getLowerBounds();
} else {
fromUppers = new Type[] {from};
fromLowers = strict ? fromUppers : NO_TYPES;
@ -523,10 +516,9 @@ public class Types {
@Override
public boolean equals(Object other) {
if (!(other instanceof ParameterizedType)) {
if (!(other instanceof ParameterizedType that)) {
return false;
}
ParameterizedType that = (ParameterizedType) other;
return this.getRawType().equals(that.getRawType())
&& Objects.equals(this.getOwnerType(), that.getOwnerType())
&& Arrays.equals(this.getActualTypeArguments(), that.getActualTypeArguments());
@ -613,10 +605,9 @@ public class Types {
@Override
public boolean equals(Object other) {
if (!(other instanceof WildcardType)) {
if (!(other instanceof WildcardType that)) {
return false;
}
WildcardType that = (WildcardType) other;
return Arrays.equals(this.getUpperBounds(), that.getUpperBounds())
&& Arrays.equals(this.getLowerBounds(), that.getLowerBounds());
}
@ -671,10 +662,9 @@ public class Types {
@Override
public boolean equals(Object other) {
if (!(other instanceof GenericArrayType)) {
if (!(other instanceof GenericArrayType that)) {
return false;
}
GenericArrayType that = (GenericArrayType) other;
return this.getGenericComponentType().equals(that.getGenericComponentType());
}
@ -700,8 +690,7 @@ public class Types {
return Arrays.stream(((ParameterizedType) type).getActualTypeArguments())
.map(Types::getSimpleName)
.collect(joining(",", "<", ">"));
} else if (type instanceof WildcardType) {
WildcardType wildcardType = (WildcardType) type;
} else if (type instanceof WildcardType wildcardType) {
Type[] upperBounds = wildcardType.getUpperBounds();
Type[] lowerBounds = wildcardType.getLowerBounds();
return "?"

View File

@ -25,6 +25,7 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.maven.api.annotations.Nullable;
import org.apache.maven.api.di.Inject;
import org.apache.maven.api.di.Named;
import org.apache.maven.api.di.Priority;
@ -42,6 +43,7 @@ import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertNull;
@SuppressWarnings("unused")
public class InjectorImplTest {
@ -328,4 +330,49 @@ public class InjectorImplTest {
@Named
static class Third {}
}
@Test
void testNullableOnField() {
Injector injector = Injector.create().bindImplicit(NullableOnField.class);
NullableOnField.MyMojo mojo = injector.getInstance(NullableOnField.MyMojo.class);
assertNotNull(mojo);
assertNull(mojo.service);
}
static class NullableOnField {
@Named
interface MyService {}
@Named
static class MyMojo {
@Inject
@Nullable
MyService service;
}
}
@Test
void testNullableOnConstructor() {
Injector injector = Injector.create().bindImplicit(NullableOnConstructor.class);
NullableOnConstructor.MyMojo mojo = injector.getInstance(NullableOnConstructor.MyMojo.class);
assertNotNull(mojo);
assertNull(mojo.service);
}
static class NullableOnConstructor {
@Named
interface MyService {}
@Named
static class MyMojo {
private final MyService service;
@Inject
public MyMojo(@Nullable MyService service) {
this.service = service;
}
}
}
}