SingletonScope.java
/*
* Copyright 2017-2020 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.context;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.type.Argument;
import io.micronaut.core.util.ArgumentUtils;
import io.micronaut.core.util.ObjectUtils;
import io.micronaut.inject.BeanDefinition;
import io.micronaut.inject.BeanIdentifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
/**
* The singleton scope implementation.
*
* @author Denis Stepanov
* @since 3.5.0
*/
@Internal
final class SingletonScope {
/**
* The locks used to prevent re-creating of the same singleton.
*/
private final Map<BeanDefinitionIdentity, Object> singletonsInCreationLocks = new ConcurrentHashMap<>(5, 1);
/**
* The main collection storing registrations for {@link BeanDefinition}.
*/
private final Map<BeanDefinitionIdentity, BeanRegistration> singletonByBeanDefinition = new ConcurrentHashMap<>(100);
/**
* Index collection to retrieve a registration by {@link Argument} and {@link Qualifier}.
*/
private final Map<DefaultBeanContext.BeanKey, BeanRegistration> singletonByArgumentAndQualifier = new ConcurrentHashMap<>(100);
@NonNull
<T> BeanRegistration<T> getOrCreate(@NonNull DefaultBeanContext beanContext,
@Nullable BeanResolutionContext resolutionContext,
@NonNull BeanDefinition<T> definition,
@NonNull Argument<T> beanType,
@Nullable Qualifier<T> qualifier) {
BeanRegistration<T> beanRegistration = findBeanRegistration(definition, beanType, qualifier);
if (beanRegistration != null) {
return beanRegistration;
}
BeanDefinitionIdentity identity = BeanDefinitionIdentity.of(definition);
BeanRegistration<T> existingRegistration = singletonByBeanDefinition.get(identity);
if (existingRegistration != null) {
return existingRegistration;
}
Object lock = singletonsInCreationLocks.computeIfAbsent(identity, beanDefinitionIdentity -> new Object());
synchronized (lock) {
try {
existingRegistration = singletonByBeanDefinition.get(identity);
if (existingRegistration != null) {
return existingRegistration;
}
BeanRegistration<T> newRegistration = beanContext.createRegistration(resolutionContext, beanType, qualifier, definition, false);
registerSingletonBean(newRegistration, qualifier);
return newRegistration;
} finally {
singletonsInCreationLocks.remove(identity);
}
}
}
/**
* Register singleton.
*
* @param registration The bean registration
* @param qualifier The qualifier
* @param <T> The singleton type
* @return The new registration
*/
@NonNull
<T> BeanRegistration<T> registerSingletonBean(@NonNull BeanRegistration<T> registration, Qualifier qualifier) {
BeanDefinition<T> beanDefinition = registration.beanDefinition;
singletonByBeanDefinition.put(BeanDefinitionIdentity.of(beanDefinition), registration);
if (!beanDefinition.isSingleton()) {
// In some cases you can register an instance of non-singleton bean and expect it act as a singleton
// Current test `RegisterSingletonSpec "test register singleton method"` does it because of the present @Inject annotation
// This might be something to remove in 4.0
DefaultBeanContext.BeanKey<T> beanKey = new DefaultBeanContext.BeanKey<>(beanDefinition, qualifier);
singletonByArgumentAndQualifier.put(beanKey, registration);
}
if (beanDefinition instanceof BeanDefinitionDelegate || beanDefinition instanceof RuntimeBeanDefinition<T>) {
// Special cases when custom bean definitions need to be indexed:
// BeanDefinitionDelegate - doesn't really exist with a custom qualifier
DefaultBeanContext.BeanKey<T> beanKey = new DefaultBeanContext.BeanKey<>(beanDefinition, beanDefinition.getDeclaredQualifier());
singletonByArgumentAndQualifier.put(beanKey, registration);
}
return registration;
}
/**
* Fast check if singleton is present.
*
* @param beanType The bean type
* @param qualifier The qualifier
* @param <T> The singleton type
* @return true if contains, false doesn't mean the singleton is not present.
*/
<T> boolean containsBean(Argument<T> beanType, Qualifier<T> qualifier) {
ArgumentUtils.requireNonNull("beanType", beanType);
DefaultBeanContext.BeanKey<T> beanKey = new DefaultBeanContext.BeanKey<>(beanType, qualifier);
return singletonByArgumentAndQualifier.containsKey(beanKey);
}
/**
* @return Active singleton registrations
*/
@NonNull
Collection<BeanRegistration> getBeanRegistrations() {
return singletonByBeanDefinition.values();
}
/**
* @param qualifier The qualifier
* @return Active singleton registrations by qualifier
*/
@NonNull
Collection<BeanRegistration<?>> getBeanRegistrations(@NonNull Qualifier<?> qualifier) {
List<BeanRegistration<?>> beanRegistrations = new ArrayList<>();
for (BeanRegistration<?> beanRegistration : singletonByBeanDefinition.values()) {
BeanDefinition<Object> beanDefinition = (BeanDefinition<Object>) beanRegistration.beanDefinition;
if (((Qualifier<Object>) qualifier).doesQualify(beanDefinition.getBeanType(), beanDefinition)) {
beanRegistrations.add(beanRegistration);
}
}
return beanRegistrations;
}
/**
* @param beanType The beanType
* @param <T> The bean type
* @return Active singleton registrations by beanType
*/
@SuppressWarnings("unchecked")
@NonNull
<T> Collection<BeanRegistration<T>> getBeanRegistrations(@NonNull Class<T> beanType) {
List<BeanRegistration<T>> beanRegistrations = new ArrayList<>();
for (BeanRegistration<?> beanRegistration : singletonByBeanDefinition.values()) {
BeanDefinition beanDefinition = beanRegistration.beanDefinition;
if (beanType.isAssignableFrom(beanDefinition.getBeanType())) {
beanRegistrations.add((BeanRegistration<T>) beanRegistration);
}
}
return beanRegistrations;
}
/**
* Find active bean registration by provided bean definition and qualifier.
*
* @param beanDefinition The beanDefinition
* @param qualifier The qualifier
* @param <T> The bean type
* @return found registration or null
*/
@Nullable
<T> BeanRegistration<T> findBeanRegistration(@NonNull BeanDefinition<T> beanDefinition, @Nullable Qualifier<T> qualifier) {
return findBeanRegistration(beanDefinition, beanDefinition.asArgument(), qualifier);
}
/**
* Find active bean registration by provided identifier.
*
* @param identifier The identifier
* @param <T> The bean type
* @return found registration or null
*/
@Nullable
<T> BeanRegistration<T> findBeanRegistration(@NonNull BeanIdentifier identifier) {
for (BeanRegistration registration : singletonByBeanDefinition.values()) {
if (registration.identifier.equals(identifier)) {
return registration;
}
}
return null;
}
/**
* Find active bean registration by provided bean instance.
*
* @param bean The bean
* @param <T> The bean type
* @return found registration or null
*/
@Nullable
<T> BeanRegistration<T> findBeanRegistration(@Nullable T bean) {
if (bean == null) {
return null;
}
for (BeanRegistration beanRegistration : singletonByBeanDefinition.values()) {
if (bean == beanRegistration.getBean()) {
return beanRegistration;
}
}
return null;
}
/**
* Find active bean registration by provided bean definition.
*
* @param definition The
* @param <T> The bean type
* @return found registration or null
*/
@Nullable
<T> BeanRegistration<T> findBeanRegistration(@NonNull BeanDefinition<T> definition) {
return singletonByBeanDefinition.get(BeanDefinitionIdentity.of(definition));
}
/**
* Find active bean registration by provided bean definition, beanType and qualifier.
*
* @param beanDefinition The beanDefinition
* @param beanType The beanType
* @param qualifier The qualifier
* @param <T> The bean type
* @return found registration or null
*/
@Nullable
<T> BeanRegistration<T> findBeanRegistration(@NonNull BeanDefinition<T> beanDefinition,
@NonNull Argument<T> beanType,
@Nullable Qualifier<T> qualifier) {
BeanRegistration<T> beanRegistration = singletonByBeanDefinition.get(BeanDefinitionIdentity.of(beanDefinition));
if (beanRegistration == null) {
return findCachedSingletonBeanRegistration(beanType, qualifier);
}
return beanRegistration;
}
/**
* Find cached singleton registration by beanType and qualifier.
* <p>
* If the result is null it doesn't mean singleton is not present.
*
* @param beanType The bean type
* @param qualifier The qualifier
* @param <T> The type
* @return found registration.
*/
@Nullable
<T> BeanRegistration<T> findCachedSingletonBeanRegistration(@NonNull Argument<T> beanType, @Nullable Qualifier<T> qualifier) {
DefaultBeanContext.BeanKey<T> beanKey = new DefaultBeanContext.BeanKey<>(beanType, qualifier);
BeanRegistration<T> beanRegistration = singletonByArgumentAndQualifier.get(beanKey);
if (beanRegistration != null && beanRegistration.bean != null) {
return beanRegistration;
}
return null;
}
/**
* Find cached singleton registration's bean definition by beanType and qualifier.
* <p>
* If the result is null it doesn't mean singleton is not present.
*
* @param beanType The bean type
* @param qualifier The qualifier
* @param <T> The type
* @return found registration's bean definition.
*/
@Nullable
<T> BeanDefinition<T> findCachedSingletonBeanDefinition(@NonNull Argument<T> beanType, @Nullable Qualifier<T> qualifier) {
BeanRegistration<T> reg = findCachedSingletonBeanRegistration(beanType, qualifier);
if (reg != null) {
return reg.getBeanDefinition();
}
return null;
}
/**
* Purge cached instances by {@link BeanDefinition} and an instance.
*
* @param beanDefinition The bean definition
* @param bean The bean
* @param <T> The bean type
*/
synchronized <T> void purgeCacheForBeanInstance(BeanDefinition<T> beanDefinition, T bean) {
singletonByBeanDefinition.remove(BeanDefinitionIdentity.of(beanDefinition));
singletonByArgumentAndQualifier.entrySet().removeIf(entry -> entry.getKey().beanType.isInstance(bean));
}
/**
* Cleanup the scope.
*/
void clear() {
singletonByBeanDefinition.clear();
singletonByArgumentAndQualifier.clear();
}
/**
* The bean definition identity implementation.
*
* @author Denis Stepanov
* @since 3.5.0
*/
interface BeanDefinitionIdentity {
static BeanDefinitionIdentity of(BeanDefinition<?> beanDefinition) {
if (beanDefinition instanceof BeanDefinitionDelegate<?> definitionDelegate) {
return new BeanDefinitionDelegatedIdentity(definitionDelegate);
} else if (beanDefinition instanceof RuntimeBeanDefinition<?> runtimeBeanDefinition) {
return new RuntimeBeanDefinitionIdentity(runtimeBeanDefinition);
}
return new SimpleBeanDefinitionIdentity(beanDefinition);
}
}
/**
* Simple key object that compares {@link BeanDefinitionDelegate}s by its class name and attributes.
*
* @since 3.5.0
*/
static final class BeanDefinitionDelegatedIdentity implements BeanDefinitionIdentity {
private final BeanDefinitionDelegate<?> beanDefinitionDelegate;
BeanDefinitionDelegatedIdentity(BeanDefinitionDelegate<?> beanDefinitionDelegate) {
this.beanDefinitionDelegate = beanDefinitionDelegate;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
BeanDefinitionDelegatedIdentity that = (BeanDefinitionDelegatedIdentity) o;
if (beanDefinitionDelegate.definition.getClass() != that.beanDefinitionDelegate.definition.getClass()) {
return false;
}
return Objects.equals(beanDefinitionDelegate.getDeclaredQualifier(), that.beanDefinitionDelegate.getDeclaredQualifier());
}
@Override
public int hashCode() {
return ObjectUtils.hash(beanDefinitionDelegate.getBeanType(), beanDefinitionDelegate.getDeclaredQualifier());
}
}
/**
* Simple key object that compares {@link BeanDefinitionDelegate}s by its class name and attributes.
*
* @since 3.5.0
*/
static final class RuntimeBeanDefinitionIdentity implements BeanDefinitionIdentity {
private final RuntimeBeanDefinition<?> beanDefinition;
RuntimeBeanDefinitionIdentity(RuntimeBeanDefinition<?> beanDefinition) {
this.beanDefinition = beanDefinition;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
RuntimeBeanDefinitionIdentity that = (RuntimeBeanDefinitionIdentity) o;
if (beanDefinition.getBeanType() != that.beanDefinition.getBeanType()) {
return false;
}
return beanDefinition.getBeanDefinitionName().equals(that.beanDefinition.getBeanDefinitionName());
}
@Override
public int hashCode() {
return beanDefinition.getBeanDefinitionName().hashCode();
}
}
/**
* Simple key object that compares {@link BeanDefinition}s by its class name.
* It's possible we have multiple instances of the same bean definition.
*
* @since 3.5.0
*/
static final class SimpleBeanDefinitionIdentity implements BeanDefinitionIdentity {
private final Class<?> beanDefinitionClass;
SimpleBeanDefinitionIdentity(BeanDefinition<?> beanDefinition) {
this.beanDefinitionClass = beanDefinition.getClass();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
SimpleBeanDefinitionIdentity that = (SimpleBeanDefinitionIdentity) o;
return beanDefinitionClass == that.beanDefinitionClass;
}
@Override
public int hashCode() {
return beanDefinitionClass.hashCode();
}
}
}