Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions core/src/com/google/inject/ScopeAnnotation.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,16 @@

/**
* Annotates annotations which are used for scoping. Only one such annotation may apply to a single
* implementation class. You must also annotate scope annotations with {@code @Retention(RUNTIME)}.
* For example:
* implementation class, injectable constructor parameter, or injectable field. You must also
* annotate scope annotations with {@code @Retention(RUNTIME)}. To permit constructor-parameter- or
* field-local scoping, the scope annotation must target {@code PARAMETER} or {@code FIELD}
* respectively. For example:
*
* <pre>
* {@code @}Retention(RUNTIME)
* {@code @}Target(TYPE, METHOD)
* {@code @}Target({TYPE, METHOD, PARAMETER, FIELD})
* {@code @}ScopeAnnotation
* public {@code @}interface SessionScoped {}
* public {@code @}interface CustomScoped {}
* </pre>
*
* @author crazybob@google.com (Bob Lee)
Expand Down
6 changes: 4 additions & 2 deletions core/src/com/google/inject/Singleton.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@

/**
* Apply this to implementation classes when you want only one instance (per {@link Injector}) to be
* reused for all injections for that binding.
* reused for all injections for that binding. Apply this to an injectable constructor parameter or
* field to reuse one instance for that dependency across all objects created by that constructor,
* or across all fields of that instance sharing the same key, respectively.
*
* @author crazybob@google.com (Bob Lee)
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
@Retention(RUNTIME)
@ScopeAnnotation
public @interface Singleton {}
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ private <T> ConstructorInjector<T> createConstructor(InjectionPoint injectionPoi
int numErrorsBefore = errors.size();

SingleParameterInjector<?>[] constructorParameterInjectors =
injector.getParametersInjectors(injectionPoint.getDependencies(), errors);
injector.getConstructorParameterInjectors(injectionPoint, errors);

@SuppressWarnings("unchecked") // the injector type agrees with the injection point type
MembersInjectorImpl<T> membersInjector =
Expand Down
109 changes: 106 additions & 3 deletions core/src/com/google/inject/internal/InjectorImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,12 @@
import com.google.inject.spi.TypeConverterBinding;
import com.google.inject.util.Providers;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -1083,16 +1085,39 @@ public Map<TypeLiteral<?>, List<InjectionPoint>> getAllMembersInjectorInjectionP
/** Returns parameter injectors, or {@code null} if there are no parameters. */
SingleParameterInjector<?>[] getParametersInjectors(List<Dependency<?>> parameters, Errors errors)
throws ErrorsException {
return getParametersInjectors(parameters, null, errors);
}

/** Returns scoped parameter injectors for an injectable constructor. */
SingleParameterInjector<?>[] getConstructorParameterInjectors(
InjectionPoint injectionPoint, Errors errors) throws ErrorsException {
Constructor<?> constructor = (Constructor<?>) injectionPoint.getMember();
return getParametersInjectors(
injectionPoint.getDependencies(), constructor.getParameterAnnotations(), errors);
}

private SingleParameterInjector<?>[] getParametersInjectors(
List<Dependency<?>> parameters,
@Nullable Annotation[][] parameterAnnotations,
Errors errors)
throws ErrorsException {
if (parameters.isEmpty()) {
return null;
}

int numErrorsBefore = errors.size();
SingleParameterInjector<?>[] result = new SingleParameterInjector<?>[parameters.size()];
int i = 0;
for (Dependency<?> parameter : parameters) {
Map<Class<? extends Annotation>, Map<Key<?>, InternalFactory<?>>> scopedFactories =
parameterAnnotations == null ? null : new HashMap<>();
for (int i = 0; i < parameters.size(); i++) {
Dependency<?> parameter = parameters.get(i);
try {
result[i++] = createParameterInjector(parameter, errors.withSource(parameter));
Errors parameterErrors = errors.withSource(parameter);
result[i] =
parameterAnnotations == null
? createParameterInjector(parameter, parameterErrors)
: createConstructorParameterInjector(
parameter, parameterAnnotations[i], scopedFactories, parameterErrors);
} catch (ErrorsException rethrownBelow) {
// rethrown below
}
Expand All @@ -1102,13 +1127,91 @@ SingleParameterInjector<?>[] getParametersInjectors(List<Dependency<?>> paramete
return result;
}

private <T> SingleParameterInjector<T> createConstructorParameterInjector(
Dependency<T> dependency,
Annotation[] parameterAnnotations,
Map<Class<? extends Annotation>, Map<Key<?>, InternalFactory<?>>> scopedFactories,
Errors errors)
throws ErrorsException {
int numErrorsBefore = errors.size();
Class<? extends Annotation> scopeAnnotation =
findScopeAnnotation(errors, parameterAnnotations);
errors.throwIfNewErrors(numErrorsBefore);
if (scopeAnnotation == null) {
return createParameterInjector(dependency, errors);
}

return new SingleParameterInjector<>(
dependency, getScopedFactory(dependency, scopeAnnotation, scopedFactories, errors));
}

/**
* Returns a factory for {@code dependency} that's scoped by {@code scopeAnnotation}, reusing an
* existing scoped factory from {@code scopedFactories} if one was already created for the same
* scope annotation and key (e.g. for another parameter or field sharing the same declaring
* element).
*/
<T> InternalFactory<? extends T> getScopedFactory(
Dependency<T> dependency,
Class<? extends Annotation> scopeAnnotation,
Map<Class<? extends Annotation>, Map<Key<?>, InternalFactory<?>>> scopedFactories,
Errors errors)
throws ErrorsException {
Map<Key<?>, InternalFactory<?>> factoriesForScope =
scopedFactories.computeIfAbsent(scopeAnnotation, unused -> new HashMap<>());
InternalFactory<?> scopedFactory = factoriesForScope.get(dependency.getKey());
if (scopedFactory == null) {
int numErrorsBefore = errors.size();
Scoping scoping =
Scoping.makeInjectable(Scoping.forAnnotation(scopeAnnotation), this, errors);
errors.throwIfNewErrors(numErrorsBefore);

BindingImpl<? extends T> binding =
getBindingOrThrow(dependency.getKey(), errors, JitLimitation.NO_JIT);

Key<T> privateKey =
Key.get(dependency.getKey().getTypeLiteral(), UniqueAnnotations.create());
scopedFactory =
Scoping.scope(
privateKey, this, binding.getInternalFactory(), dependency, scoping);
factoriesForScope.put(dependency.getKey(), scopedFactory);
}

@SuppressWarnings("unchecked") // The factory was created for this dependency key.
InternalFactory<? extends T> typedScopedFactory =
(InternalFactory<? extends T>) scopedFactory;
return typedScopedFactory;
}

<T> SingleParameterInjector<T> createParameterInjector(
final Dependency<T> dependency, final Errors errors) throws ErrorsException {
BindingImpl<? extends T> binding =
getBindingOrThrow(dependency.getKey(), errors, JitLimitation.NO_JIT);
return new SingleParameterInjector<T>(dependency, binding);
}

/**
* Returns the factory for an injectable field, honoring any scope annotation on the field. The
* {@code scopedFactories} map is shared across the fields (and methods) of a single declaring
* type, so that multiple fields scoped by the same annotation and requesting the same key reuse
* one scoped instance, mirroring the behavior for scoped constructor parameters.
*/
<T> InternalFactory<? extends T> getFieldFactory(
Dependency<T> dependency,
Annotation[] fieldAnnotations,
Map<Class<? extends Annotation>, Map<Key<?>, InternalFactory<?>>> scopedFactories,
Errors errors)
throws ErrorsException {
int numErrorsBefore = errors.size();
Class<? extends Annotation> scopeAnnotation = findScopeAnnotation(errors, fieldAnnotations);
errors.throwIfNewErrors(numErrorsBefore);
if (scopeAnnotation == null) {
return getBindingOrThrow(dependency.getKey(), errors, JitLimitation.NO_JIT)
.getInternalFactory();
}
return getScopedFactory(dependency, scopeAnnotation, scopedFactories, errors);
}

/** Cached constructor injectors for each type */
final ConstructorInjectorStore constructors = new ConstructorInjectorStore(this);

Expand Down
11 changes: 10 additions & 1 deletion core/src/com/google/inject/internal/MembersInjectorStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,16 @@
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.inject.ConfigurationException;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.google.inject.spi.InjectionPoint;
import com.google.inject.spi.TypeListener;
import com.google.inject.spi.TypeListenerBinding;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

Expand Down Expand Up @@ -120,6 +124,10 @@ private <T> MembersInjectorImpl<T> createWithListeners(TypeLiteral<T> type, Erro
ImmutableList<SingleMemberInjector> getInjectors(
Set<InjectionPoint> injectionPoints, Errors errors) {
List<SingleMemberInjector> injectors = Lists.newArrayList();
// Shared across all fields of the type being processed, so that multiple scoped fields
// requesting the same key and scope annotation reuse a single scoped instance.
Map<Class<? extends Annotation>, Map<Key<?>, InternalFactory<?>>> scopedFieldFactories =
new HashMap<>();
for (InjectionPoint injectionPoint : injectionPoints) {
try {
Errors errorsForMember =
Expand All @@ -128,7 +136,8 @@ ImmutableList<SingleMemberInjector> getInjectors(
: errors.withSource(injectionPoint);
SingleMemberInjector injector =
injectionPoint.getMember() instanceof Field
? new SingleFieldInjector(this.injector, injectionPoint, errorsForMember)
? new SingleFieldInjector(
this.injector, injectionPoint, scopedFieldFactories, errorsForMember)
: new SingleMethodInjector(this.injector, injectionPoint, errorsForMember);
injectors.add(injector);
} catch (ErrorsException ignoredForNow) {
Expand Down
14 changes: 9 additions & 5 deletions core/src/com/google/inject/internal/SingleFieldInjector.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@
import static com.google.inject.internal.InternalMethodHandles.castReturnTo;
import static java.lang.invoke.MethodType.methodType;

import com.google.inject.internal.InjectorImpl.JitLimitation;
import com.google.inject.Key;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.InjectionPoint;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Map;

/** Sets an injectable field. */
final class SingleFieldInjector implements SingleMemberInjector {
Expand All @@ -34,7 +36,11 @@ final class SingleFieldInjector implements SingleMemberInjector {
final Dependency<?> dependency;
final InternalFactory<?> factory;

public SingleFieldInjector(InjectorImpl injector, InjectionPoint injectionPoint, Errors errors)
public SingleFieldInjector(
InjectorImpl injector,
InjectionPoint injectionPoint,
Map<Class<? extends Annotation>, Map<Key<?>, InternalFactory<?>>> scopedFactories,
Errors errors)
throws ErrorsException {
this.injectionPoint = injectionPoint;
this.field = (Field) injectionPoint.getMember();
Expand All @@ -43,9 +49,7 @@ public SingleFieldInjector(InjectorImpl injector, InjectionPoint injectionPoint,
// Ewwwww...
field.setAccessible(true);
factory =
injector
.getBindingOrThrow(dependency.getKey(), errors, JitLimitation.NO_JIT)
.getInternalFactory();
injector.getFieldFactory(dependency, field.getAnnotations(), scopedFactories, errors);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,13 @@ final class SingleParameterInjector<T> {
@LazyInit private MethodHandle handle;

SingleParameterInjector(Dependency<T> dependency, BindingImpl<? extends T> binding) {
this(dependency, binding.getInternalFactory());
}

SingleParameterInjector(
Dependency<T> dependency, InternalFactory<? extends T> factory) {
this.dependency = dependency;
this.factory = binding.getInternalFactory();
this.factory = factory;
}

T inject(InternalContext context) throws InternalProvisionException {
Expand Down
Loading
Loading