All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.micronaut.spring.context.factory.MicronautBeanFactory Maven / Gradle / Ivy

The newest version!
/*
 * 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.spring.context.factory;

import io.micronaut.context.BeanContext;
import io.micronaut.context.BeanRegistration;
import io.micronaut.context.RuntimeBeanDefinition;
import io.micronaut.context.annotation.*;
import io.micronaut.context.exceptions.NoSuchBeanException;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.AnnotationUtil;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.naming.NameResolver;
import io.micronaut.core.reflect.InstantiationUtils;
import io.micronaut.core.util.ArgumentUtils;
import io.micronaut.core.util.ArrayUtils;
import io.micronaut.core.util.StringUtils;
import io.micronaut.inject.BeanDefinition;
import io.micronaut.inject.BeanDefinitionReference;
import io.micronaut.inject.DisposableBeanDefinition;
import io.micronaut.inject.InstantiatableBeanDefinition;
import io.micronaut.inject.ParametrizedInstantiatableBeanDefinition;
import io.micronaut.inject.qualifiers.Qualifiers;
import io.micronaut.spring.beans.MicronautContextInternal;
import io.micronaut.spring.context.aware.SpringAwareListener;
import jakarta.inject.Singleton;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.AbstractBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.Role;
import org.springframework.core.OrderComparator;
import org.springframework.core.ResolvableType;
import org.springframework.stereotype.Component;
import org.springframework.util.ConcurrentReferenceHashMap;

import java.io.Closeable;
import java.lang.annotation.Annotation;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Stream;

/**
 * Implementation of the {@link ListableBeanFactory} interface for Micronaut.
 *
 * @author graemerocher
 * @since 1.0
 */
@Singleton
@Internal
public class MicronautBeanFactory extends DefaultListableBeanFactory implements ListableBeanFactory, AutowireCapableBeanFactory, HierarchicalBeanFactory, ConfigurableListableBeanFactory, MicronautContextInternal {

    private final BeanContext beanContext;
    private final Map> beanDefinitionMap = new LinkedHashMap<>(200);
    // only used for by name lookups
    private final Map> beanDefinitionsByName = new LinkedHashMap<>(200);
    private final SpringAwareListener springAwareListener;
    private final Map>> beanTypeCache = new ConcurrentReferenceHashMap<>();
    private final Map beanNamesForTypeCache = new ConcurrentReferenceHashMap<>();
    private final MicronautBeanFactoryConfiguration configuration;
    private final List> beanExcludes;

    /**
     * The default constructor.
     * @param beanContext The target Micronaut context
     * @param awareListener The spring aware listener
     * @param configuration Configuration
     */
    public MicronautBeanFactory(
            BeanContext beanContext,
            SpringAwareListener awareListener,
            MicronautBeanFactoryConfiguration configuration) {
        this.beanContext = beanContext;
        this.springAwareListener = awareListener;
        this.configuration = configuration;
        this.beanExcludes = configuration.getBeanExcludes();
        final Collection> references = beanContext.getBeanDefinitionReferences();

        for (BeanDefinitionReference reference : references) {
            final BeanDefinition definition = reference.load(beanContext);
            if (definition instanceof ParametrizedInstantiatableBeanDefinition || (!(definition instanceof InstantiatableBeanDefinition))) {
                // Spring doesn't have a similar concept. Consider these internal / non-public beans.
                continue;
            }

            if (beanExcludes.contains(definition.getBeanType())) {
                continue;
            }

            if (definition.isEnabled(beanContext)) {
                if (definition.isIterable()) {
                    Collection> beanDefinitions = beanContext.getBeanDefinitions(definition.getBeanType());
                    for (BeanDefinition beanDefinition : beanDefinitions) {
                        String beanName = computeBeanName(beanDefinition);
                        beanDefinitionMap.put(beanName, beanDefinition);
                    }
                } else {
                    String beanName = computeBeanName(definition);
                    beanDefinitionMap.put(beanName, definition);
                }

                // handle component differently so that the value is a unique bean name
                if (definition.isAnnotationPresent(Component.class)) {
                    // explicit handling of named beans
                    final Optional v = definition.getValue(Component.class, String.class);
                    v.ifPresent(s -> beanDefinitionsByName.put(s, definition));
                }

                // handle Spring's @Bean differently so that the value is a unique bean name
                if (definition.isAnnotationPresent(org.springframework.context.annotation.Bean.class)) {
                    // explicit handling of named beans
                    final Optional v = definition.getValue(org.springframework.context.annotation.Bean.class, String.class);
                    v.ifPresent(s -> beanDefinitionsByName.put(s, definition));
                }
            }
        }
    }

    /**
     * Shared logic for Micronaut singleton rules.
     * @param annotationMetadata The metadata
     * @return True if is singleton
     */
    public static boolean isSingleton(AnnotationMetadata annotationMetadata) {
        if (annotationMetadata.hasDeclaredStereotype(org.springframework.context.annotation.Bean.class)) {
            final String scope = annotationMetadata.stringValue(org.springframework.context.annotation.Scope.class).orElse(null);
            return scope == null || "singleton".equalsIgnoreCase(scope);
        }
        if (annotationMetadata.hasDeclaredStereotype(AnnotationUtil.SINGLETON)) {
            return true;
        } else {
            if (!annotationMetadata.hasDeclaredStereotype(AnnotationUtil.SCOPE) &&
                    annotationMetadata.hasDeclaredStereotype(DefaultScope.class)) {
                return annotationMetadata.stringValue(DefaultScope.class)
                        .map(t -> t.equals(Singleton.class.getName()) || t.equals(AnnotationUtil.SINGLETON))
                        .orElse(false);
            } else {
                return false;
            }
        }
    }

    private String computeBeanName(BeanDefinition definition) {
        String name;
        if (definition instanceof NameResolver) {
            name = ((NameResolver) definition).resolveName().orElse(Primary.class.getSimpleName());
        } else {
            name = definition.getValue(AnnotationUtil.NAMED, String.class).orElseGet(() ->
                    definition.getAnnotationTypeByStereotype(AnnotationUtil.QUALIFIER).map(Class::getSimpleName).orElse(definition.getClass().getSimpleName())
            );
        }
        return definition.getBeanType().getName() + "(" + name + ")";
    }

    @Override
    public @NonNull
    Object getBean(@NonNull String name) throws BeansException {
        if (super.isAlias(name)) {
            final String[] aliases = super.getAliases(name);
            for (String alias : aliases) {
                if (containsBean(alias)) {
                    return getBean(alias);
                }
            }
        }
        if (super.containsSingleton(name)) {
            final Object singleton = super.getSingleton(name, true);
            if (singleton == null) {
                throw new NoSuchBeanDefinitionException(name);
            }
            return singleton;
        } else {
            Class type = getType(name);
            BeanDefinition definition;
            if (type != null) {
                definition = beanDefinitionMap.get(name);
            } else {
                definition = beanDefinitionsByName.get(name);
                if (definition != null) {
                    type = definition.getBeanType();
                }
            }

            if (definition != null && type != null) {
                return beanContext.getBean(definition);
            }
            throw new NoSuchBeanDefinitionException(name);
        }
    }

    @Override
    public @NonNull
     T getBean(@NonNull String name, @NonNull Class requiredType) throws BeansException {
        ArgumentUtils.requireNonNull("requiredType", requiredType);
        if (beanExcludes.contains(requiredType)) {
            throw new NoSuchBeanDefinitionException(requiredType);
        }

        try {
            if (containsBeanDefinition(name)) {
                @SuppressWarnings("unchecked")
                BeanDefinition beanDefinition = (BeanDefinition) beanDefinitionMap.get(name);
                if (requiredType.isAssignableFrom(requiredType)) {
                    return beanContext.getBean(beanDefinition);
                }
            }
            if (isAlias(name)) {
                final String[] aliases = getAliases(name);
                for (String alias : aliases) {
                    if (super.containsSingleton(alias)) {
                        return super.getBean(alias, requiredType);
                    }
                }
            }
            if (super.containsSingleton(name)) {
                final Object o = super.getBean(name);
                if (requiredType.isInstance(o)) {
                    return (T) o;
                }
            }
            return beanContext.getBean(requiredType, Qualifiers.byName(name));
        } catch (NoSuchBeanException e) {
            throw new NoSuchBeanDefinitionException(requiredType, e.getMessage());
        } catch (Exception e) {
            throw new BeanCreationException(name, e.getMessage(), e);
        }
    }

    @Override
    public @NonNull
    Object getBean(@NonNull String name, @NonNull Object... args) throws BeansException {
        final Class type = getType(name);
        if (type != null) {
            return beanContext.createBean(type, args);
        }
        throw new NoSuchBeanDefinitionException(name);
    }

    @Override
    public @NonNull
     T getBean(@NonNull Class requiredType) throws BeansException {
        ArgumentUtils.requireNonNull("requiredType", requiredType);
        if (beanExcludes.contains(requiredType)) {
            throw new NoSuchBeanDefinitionException(requiredType);
        }
        // unfortunate hack
        try {
            final String[] beanNamesForType = super.getBeanNamesForType(requiredType, false, false);
            if (ArrayUtils.isNotEmpty(beanNamesForType)) {
                return getBean(beanNamesForType[0], requiredType);
            } else {
                return beanContext.getBean(requiredType);
            }
        } catch (NoSuchBeanException e) {
            throw new NoSuchBeanDefinitionException(requiredType, e.getMessage());
        }
    }

    @Override
    public @NonNull
     T getBean(@NonNull Class requiredType, @NonNull Object... args) throws BeansException {
        try {
            return beanContext.createBean(requiredType, args);
        } catch (NoSuchBeanException e) {
            throw new NoSuchBeanDefinitionException(requiredType, e.getMessage());
        }
    }

    @Override
    public @NonNull
     ObjectProvider getBeanProvider(@NonNull Class requiredType) {
        return new ObjectProvider() {
            @Override
            public T getObject(Object... args) throws BeansException {
                return beanContext.createBean(requiredType, args);
            }

            @Override
            public T getIfAvailable() throws BeansException {
                if (beanContext.containsBean(requiredType)) {
                    return beanContext.getBean(requiredType);
                }
                return null;
            }

            @Override
            public T getIfUnique() throws BeansException {
                final Collection beansOfType = beanContext.getBeansOfType(requiredType);
                if (beansOfType.size() == 1) {
                    return beansOfType.stream().findFirst().orElse(null);
                }
                return null;
            }

            @Override
            public T getObject() throws BeansException {
                return beanContext.getBean(requiredType);
            }

            @Override
            public Stream stream() {
                return Arrays.stream(getBeanNamesForType(requiredType)).map(name -> (T) getBean(name));
            }

            @Override
            public Stream orderedStream() {
                return stream().sorted(OrderComparator.INSTANCE);
            }
        };
    }

    @Override
    public @NonNull
     ObjectProvider getBeanProvider(@NonNull ResolvableType requiredType) {
        final Class resolved = (Class) requiredType.resolve();
        return getBeanProvider(resolved);
    }

    @Override
    public boolean containsBean(@NonNull String name) {
        return super.containsSingleton(name) ||
                beanDefinitionMap.containsKey(name) ||
                beanDefinitionsByName.containsKey(name) ||
                isAlias(name);
    }

    @Override
    public boolean isSingleton(@NonNull String name) throws NoSuchBeanDefinitionException {
        if (super.containsSingleton(name)) {
            return true;
        } else {
            final BeanDefinition definition = beanDefinitionMap.get(name);
            if (definition != null) {
                return isSingleton(definition);
            }
            return false;
        }
    }

    /**
     * Is the definition singleton.
     * @param definition The definition
     * @return True if it is
     */
    protected boolean isSingleton(@NonNull BeanDefinitionReference definition) {
        final AnnotationMetadata annotationMetadata = definition.getAnnotationMetadata();
        return isSingleton(annotationMetadata);
    }

    @Override
    public boolean isPrototype(@NonNull String name) throws NoSuchBeanDefinitionException {
        if (super.containsSingleton(name)) {
            return false;
        }

        final BeanDefinition definition = beanDefinitionMap.get(name);
        if (definition != null) {
            final AnnotationMetadata annotationMetadata = definition.getAnnotationMetadata();
            if (annotationMetadata.hasDeclaredStereotype(Prototype.class)) {
                return true;
            } else {
                final boolean hasScope = annotationMetadata.getAnnotationNamesByStereotype(AnnotationUtil.SCOPE).isEmpty();
                return !hasScope;
            }
        }
        return false;
    }

    @Override
    public boolean isTypeMatch(@NonNull String name, @NonNull ResolvableType typeToMatch) throws NoSuchBeanDefinitionException {
        final Class resolved = typeToMatch.resolve();
        if (resolved != null) {
            return isTypeMatch(name, resolved);
        }
        return false;
    }

    @Override
    public boolean isTypeMatch(@NonNull String name, @NonNull Class typeToMatch) throws NoSuchBeanDefinitionException {
        final Class beanType = getType(name);
        if (beanType != null) {
            return typeToMatch.isAssignableFrom(beanType);
        }
        return false;
    }

    @Override
    public Class getType(@NonNull String beanName) throws NoSuchBeanDefinitionException {
        Optional> opt = beanTypeCache.get(beanName);
        //noinspection OptionalAssignedToNull
        if (opt == null) {
            final BeanDefinition definition = beanDefinitionMap.get(beanName);
            if (definition != null) {
                opt = Optional.of(definition.getBeanType());
            } else {

                beanName = transformedBeanName(beanName);
                // Check manually registered singletons.
                Object beanInstance = super.getSingleton(beanName, false);
                if (beanInstance != null && !beanInstance.getClass().getSimpleName().equals("NullBean")) {
                    if (beanInstance instanceof FactoryBean && !BeanFactoryUtils.isFactoryDereference(beanName)) {
                        return getTypeForFactoryBean((FactoryBean) beanInstance);
                    } else {
                        return beanInstance.getClass();
                    }
                }
                // No singleton instance found -> check bean definition.
                org.springframework.beans.factory.BeanFactory parentBeanFactory = getParentBeanFactory();
                if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
                    // No bean definition found in this factory -> delegate to parent.
                    return parentBeanFactory.getType(originalBeanName(beanName));
                }

                final org.springframework.beans.factory.config.BeanDefinition parentDef;
                try {
                    parentDef = super.getBeanDefinition(beanName);
                } catch (NoSuchBeanDefinitionException e) {
                    beanTypeCache.put(beanName, Optional.empty());
                    return null;
                }
                if (parentDef instanceof RootBeanDefinition) {

                    RootBeanDefinition mbd = (RootBeanDefinition) parentDef;

                    // Check decorated bean definition, if any: We assume it'll be easier
                    // to determine the decorated bean's type than the proxy's type.
                    BeanDefinitionHolder dbd = mbd.getDecoratedDefinition();
                    if (dbd != null && !BeanFactoryUtils.isFactoryDereference(beanName)) {
                        RootBeanDefinition tbd = super.getMergedBeanDefinition(dbd.getBeanName(), dbd.getBeanDefinition(), mbd);
                        Class targetClass = predictBeanType(dbd.getBeanName(), tbd);
                        if (targetClass != null && !FactoryBean.class.isAssignableFrom(targetClass)) {
                            return targetClass;
                        }
                    }

                    Class beanClass = predictBeanType(beanName, mbd);

                    // Check bean class whether we're dealing with a FactoryBean.
                    if (beanClass != null && FactoryBean.class.isAssignableFrom(beanClass)) {
                        if (!BeanFactoryUtils.isFactoryDereference(beanName)) {
                            // If it's a FactoryBean, we want to look at what it creates, not at the factory class.
                            boolean allowInit = true; // if allowInit is true a full creation of the FactoryBean is used as fallback (through delegation to the superclass's implementation).
                            ResolvableType resolvableType = super.getTypeForFactoryBean(beanName, mbd, allowInit);
                            return resolvableType.getRawClass();
                        } else {
                            return beanClass;
                        }
                    } else {
                        return (!BeanFactoryUtils.isFactoryDereference(beanName) ? beanClass : null);
                    }
                }
            }
            beanTypeCache.put(beanName, opt);
        }

        return opt.orElse(null);
    }

    @Override
    public boolean containsBeanDefinition(@NonNull String beanName) {
        return beanDefinitionMap.containsKey(beanName);
    }

    @Override
    public int getBeanDefinitionCount() {
        return beanDefinitionMap.size();
    }

    @Override
    public @NonNull
    String[] getBeanDefinitionNames() {
        return beanDefinitionMap.keySet().toArray(new String[0]);
    }

    @Override
    public @NonNull
    String[] getBeanNamesForType(@NonNull ResolvableType type) {
        final Class resolved = type.resolve();
        if (resolved != null) {
            return getBeanNamesForType(resolved);
        }
        return StringUtils.EMPTY_STRING_ARRAY;
    }

    @Override
    public @NonNull
    String[] getBeanNamesForType(Class type) {
        return getBeanNamesForType(type, true, true);
    }

    @Override
    public @NonNull
    String[] getBeanNamesForType(Class type, boolean includeNonSingletons, boolean allowEagerInit) {
        // ignore certain common cases
        if (type == null || Object.class == type || List.class == type || beanExcludes.contains(type)) {
            return StringUtils.EMPTY_STRING_ARRAY;
        }
        String[] names = beanNamesForTypeCache.get(type);
        if (names == null) {
            final String[] superResult = MicronautBeanFactory.super.getBeanNamesForType(type, includeNonSingletons, allowEagerInit);
            if (ArrayUtils.isNotEmpty(superResult)) {
                names = superResult;
            } else {
                final Collection> beanDefinitions = beanContext.getBeanDefinitions(type);
                names = beansToNames(beanDefinitions);
            }
            beanNamesForTypeCache.put(type, names);
        }
        return names;
    }

    @Override
    public @NonNull
     Map getBeansOfType(Class type, boolean includeNonSingletons, boolean allowEagerInit) throws BeansException {
        if (type == null || beanExcludes.contains(type)) {
            return Collections.emptyMap();
        }
        final Map springSingletons = super.getBeansOfType(type, false, false);
        final Collection beansOfType = beanContext.getBeansOfType(type);
        Map beans = new HashMap<>(beansOfType.size());
        beans.putAll(springSingletons);
        for (T bean : beansOfType) {
            if (!springSingletons.containsValue(bean)) {

                final Optional> reg = beanContext.findBeanRegistration(bean);
                reg.ifPresent(registration -> beans.put(registration.getBeanDefinition().getClass().getName(), bean));
            }
        }
        return beans;
    }

    @Override
    public @NonNull
    String[] getBeanNamesForAnnotation(@NonNull Class annotationType) {
        final String[] beanNamesForAnnotation = super.getBeanNamesForAnnotation(annotationType);
        final Collection> beanDefinitions = beanContext.getBeanDefinitions(Qualifiers.byStereotype(annotationType));
        return ArrayUtils.concat(beansToNames(beanDefinitions), beanNamesForAnnotation);
    }

    @Override
    public  A findAnnotationOnBean(@NonNull String beanName, @NonNull Class annotationType) throws NoSuchBeanDefinitionException {
        if (super.containsSingleton(beanName)) {
            return super.findAnnotationOnBean(beanName, annotationType);
        } else {
            final BeanDefinition ref = beanDefinitionMap.get(beanName);
            if (ref != null) {
                return ref.getAnnotationMetadata().synthesize(annotationType);
            }
            return null;
        }
    }

    private String[] beansToNames(Collection> beanDefinitions) {
        return beanDefinitions.stream()
                .filter(bd -> !(bd instanceof ParametrizedInstantiatableBeanDefinition))
                .map(this::computeBeanName).toArray(String[]::new);
    }

    @Override
    public @NonNull
     T createBean(@NonNull Class beanClass) throws BeansException {
        return beanContext.createBean(beanClass);
    }

    @SuppressWarnings("ConstantConditions")
    @Override
    public void autowireBean(@NonNull Object existingBean) throws BeansException {
        if (existingBean != null) {
            beanContext.inject(existingBean);
        }
    }

    @Override
    public Object configureBean(Object existingBean, String beanName) throws BeansException {
        final Object injected = beanContext.inject(existingBean);
        return initializeBean(injected, beanName);
    }

    @Override
    public Object createBean(Class beanClass, int autowireMode, boolean dependencyCheck) throws BeansException {
        return createBean(beanClass);
    }

    @Override
    public Object autowire(Class beanClass, int autowireMode, boolean dependencyCheck) throws BeansException {
        return beanContext.getBean(beanClass);
    }

    @Override
    public void autowireBeanProperties(Object existingBean, int autowireMode, boolean dependencyCheck) throws BeansException {
        autowireBean(existingBean);
    }

    @Override
    public void applyBeanPropertyValues(Object existingBean, String beanName) throws BeansException {
        autowireBean(existingBean);
    }

    @Override
    public Object initializeBean(Object existingBean, String beanName) throws BeansException {
        return springAwareListener.onBeanCreated(null, existingBean, beanName);
    }

    @Override
    public void destroyBean(Object existingBean) {
        if (existingBean instanceof Closeable) {
            try {
                ((Closeable) existingBean).close();
            } catch (Exception e) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Invocation of destroy method failed for bean [" + existingBean + "]: " + e.getMessage(), e);
                }
            }
        } else if (existingBean instanceof DisposableBean) {
            try {
                ((DisposableBean) existingBean).destroy();
            } catch (Exception e) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Invocation of destroy method failed for bean [" + existingBean + "]: " + e.getMessage(), e);
                }
            }
        } else if (existingBean instanceof AutoCloseable) {
            try {
                ((AutoCloseable) existingBean).close();
            } catch (Exception e) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Invocation of destroy method failed for bean [" + existingBean + "]: " + e.getMessage(), e);
                }
            }
        }
    }

    @Override
    public boolean containsLocalBean(String name) {
        return super.containsLocalBean(name) || beanDefinitionMap.containsKey(name) || beanDefinitionsByName.containsKey(name);
    }

    /**
     * @return The backing Micronaut bean context.
     */
    public BeanContext getBeanContext() {
        return beanContext;
    }

    @Override
    public boolean isAutowireCandidate(String beanName, DependencyDescriptor descriptor) throws NoSuchBeanDefinitionException {
        return false;
    }

    @Override
    public org.springframework.beans.factory.config.BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException {
        final BeanDefinition definition = beanDefinitionMap.get(beanName);
        if (definition != null && definition.isEnabled(beanContext)) {
            final GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
            genericBeanDefinition.setBeanClass(definition.getBeanType());
            int role = definition.intValue(Role.class).orElse(org.springframework.beans.factory.config.BeanDefinition.ROLE_APPLICATION);
            genericBeanDefinition.setRole(role);
            return genericBeanDefinition;
        }
        throw new NoSuchBeanDefinitionException(beanName);
    }

    @SuppressWarnings("unchecked")
    @Override
    protected  T doGetBean(String name, Class requiredType, Object[] args, boolean typeCheckOnly) throws BeansException {

        if (beanExcludes.contains(requiredType)) {
            throw new NoSuchBeanDefinitionException(name);
        }

        if (super.containsSingleton(name)) {
            final Object o = super.getSingleton(name);
            if (requiredType == null || requiredType.isInstance(o)) {
                return (T) o;
            }
        }

        BeanDefinition definition = beanDefinitionMap.get(name);
        if (definition == null) {
            // by name, with no type lookups
            final BeanDefinition def = beanDefinitionsByName.get(name);
            if (def != null) {
                if (requiredType != null) {
                    if (requiredType.isAssignableFrom(def.getBeanType())) {
                        definition = def;
                    }
                } else {
                    definition = def;
                }
            }
        }

        if (definition != null && definition.isEnabled(beanContext)) {
            if (requiredType == null) {
                requiredType = (Class) definition.getBeanType();
            }

            BeanDefinition finalDefinition = definition;
            io.micronaut.context.Qualifier q = (io.micronaut.context.Qualifier) definition.getValue(AnnotationUtil.NAMED, String.class)
                    .map((String n) -> {
                        if (Primary.class.getName().equals(n)) {
                            return n;
                        }
                        return Qualifiers.byName(n);
                    })
                    .orElseGet(() -> {
                                if (finalDefinition.hasDeclaredStereotype(Primary.class)) {
                                    return null;
                                }
                                final String annotationType = finalDefinition.getAnnotationNameByStereotype(AnnotationUtil.QUALIFIER).orElse(null);
                                if (annotationType != null) {
                                    return Qualifiers.byAnnotation(finalDefinition, annotationType);
                                } else if (finalDefinition instanceof NameResolver) {
                                    return ((NameResolver) finalDefinition).resolveName().map(Qualifiers::byName).orElse(null);
                                }
                        return null;
                            }
                    );
            if (q != null) {
                return beanContext.getBean(requiredType, q);
            } else {
                return beanContext.getBean(requiredType);
            }
        }

        final org.springframework.beans.factory.BeanFactory parentBeanFactory = getParentBeanFactory();
        if (parentBeanFactory != null) {
            if (parentBeanFactory instanceof AbstractBeanFactory) {
                return ((AbstractBeanFactory) parentBeanFactory).getBean(
                        name, requiredType, args);
            } else if (args != null) {
                // Delegation to parent with explicit args.
                return (T) parentBeanFactory.getBean(name, args);
            } else if (requiredType != null) {
                // No args -> delegate to standard getBean method.
                return parentBeanFactory.getBean(name, requiredType);
            } else {
                return (T) parentBeanFactory.getBean(name);
            }
        }

        if (requiredType != null) {
            try {
                return beanContext.getBean(requiredType, Qualifiers.byName(name));
            } catch (NoSuchBeanException e) {
                throw new NoSuchBeanDefinitionException(name);
            }
        } else {
            throw new NoSuchBeanDefinitionException(name);
        }
    }

    @Override
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        if (super.containsSingleton(beanName)) {
            return super.getSingleton(beanName, allowEarlyReference);
        } else {
            return getBean(beanName);
        }
    }

    @Override
    public Object getSingleton(String beanName, ObjectFactory singletonFactory) {
        if (super.containsSingleton(beanName)) {
            return super.getSingleton(beanName, singletonFactory);
        } else {
            return getBean(beanName);
        }
    }

    @Override
    public Iterator getBeanNamesIterator() {
        return Arrays.asList(getBeanDefinitionNames()).iterator();
    }

    @Override
    public boolean isFactoryBean(String name) throws NoSuchBeanDefinitionException {
        return false;
    }

    @Override
    public void destroyBean(String beanName, Object beanInstance) {
        final BeanDefinition definition = beanDefinitionMap.get(beanName);
        if (definition instanceof DisposableBeanDefinition) {
            ((DisposableBeanDefinition) definition).dispose(beanContext, beanInstance);
        }
    }

    @Override
    public void registerSingleton(String beanName, Object singletonObject) {
        final Class type = singletonObject.getClass();
        beanContext.registerSingleton(
                type,
                singletonObject,
                Qualifiers.byName(beanName)
        );
        super.registerSingleton(beanName, singletonObject);
    }

    @Override
    public boolean containsSingleton(String beanName) {
        return isSingleton(beanName);
    }

    @Override
    public void registerBeanDefinition(String beanName, org.springframework.beans.factory.config.BeanDefinition beanDefinition) throws BeanDefinitionStoreException {
        if (beanDefinition instanceof org.springframework.beans.factory.support.AbstractBeanDefinition) {
            org.springframework.beans.factory.support.AbstractBeanDefinition abstractBeanDefinition = (org.springframework.beans.factory.support.AbstractBeanDefinition) beanDefinition;
            final Supplier instanceSupplier = (Supplier) abstractBeanDefinition.getInstanceSupplier();
            final Class beanClass = (Class) abstractBeanDefinition.getBeanClass();

            RuntimeBeanDefinition.Builder builder;
            if (instanceSupplier != null) {
                builder = RuntimeBeanDefinition.builder(
                    beanClass,
                    instanceSupplier
                );
            } else {
                builder = RuntimeBeanDefinition.builder(
                    beanClass,
                    () -> InstantiationUtils.instantiate(beanClass)
                );
            }
            String scope = abstractBeanDefinition.getScope();
            if (scope != null) {
                if ("prototype".equals(scope)) {
                    builder.scope(Prototype.class);
                } else {
                    builder.scope(Singleton.class);
                }
            } else {
                builder.scope(Singleton.class);
            }
            builder.qualifier(Qualifiers.byName(beanName));
            beanContext.registerBeanDefinition(builder.build());
            if (BeanPostProcessor.class.isAssignableFrom(beanClass)) {
                beanContext.getBean(SpringAwareListener.class)
                    .resetPostProcessors();
            }
        }
    }

    @Override
    protected boolean isPrimary(String beanName, Object beanInstance) {
        BeanDefinition definition = beanDefinitionMap.get(beanName);
        if (definition == null) {
            definition = beanDefinitionsByName.get(beanName);
        }

        if (definition != null) {
            return definition.hasDeclaredStereotype(Primary.class) || definition.getValue(AnnotationUtil.NAMED, String.class).map((String n) -> Primary.class.getName().equals(n)).orElse(false);
        } else {
            return false;
        }
    }

}