io.micronaut.spring.boot.starter.MicronautImportRegistrar Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of micronaut-spring-boot-starter Show documentation
Show all versions of micronaut-spring-boot-starter Show documentation
Extensions to integrate Micronaut and Spring
/*
* Copyright 2017-2022 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.boot.starter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.UnaryOperator;
import java.util.stream.Stream;
import javax.sql.DataSource;
import io.micronaut.context.ApplicationContext;
import io.micronaut.context.ApplicationContextBuilder;
import io.micronaut.context.Qualifier;
import io.micronaut.context.RuntimeBeanDefinition;
import io.micronaut.context.annotation.Context;
import io.micronaut.context.annotation.Infrastructure;
import io.micronaut.context.annotation.Primary;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.naming.Named;
import io.micronaut.core.reflect.InstantiationUtils;
import io.micronaut.core.util.ArrayUtils;
import io.micronaut.core.util.StringUtils;
import io.micronaut.inject.BeanDefinition;
import io.micronaut.inject.annotation.MutableAnnotationMetadata;
import io.micronaut.inject.qualifiers.Qualifiers;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.type.AnnotationMetadata;
/**
* An {@link ImportBeanDefinitionRegistrar} that exposes all Micronaut beans as Spring beans using {@link EnableMicronaut}.
*
* The beans to be exposed can be limited by a {@link MicronautBeanFilter} configured via {@link EnableMicronaut#filter()}.
*
* @author graemerocher
* @since 4.3.0
*/
@Internal
@AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
public final class MicronautImportRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware, BeanFactoryAware {
private Environment environment;
private BeanFactory beanFactory;
private final List exposedBeans = new ArrayList<>();
public MicronautImportRegistrar() {
exposedBeans.add(new ExposedBeanData(DataSource.class, null, null, (name) -> {
if ("dataSource".equals(name)) {
return "default";
}
return name;
}));
}
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {
if (registry.containsBeanDefinition("micronautApplicationContext")) {
// already registered
return;
}
GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
String[] activeProfiles = environment != null ? environment.getActiveProfiles() : StringUtils.EMPTY_STRING_ARRAY;
ApplicationContextBuilder builder = ApplicationContext.builder(activeProfiles);
if (beanFactory != null) {
ObjectProvider beanProvider = beanFactory.getBeanProvider(ApplicationArguments.class);
beanProvider.ifAvailable(args ->
builder.args(args.getSourceArgs())
);
}
if (environment instanceof ConfigurableEnvironment) {
ConfigurableEnvironment ce = (ConfigurableEnvironment) environment;
List cePropertySources = propertySourcesForConfigurableEnvironment(ce);
builder.propertySources(cePropertySources.toArray(new io.micronaut.context.env.PropertySource[0]));
}
builder.singletons(
environment,
beanFactory
);
ApplicationContext context = builder
.banner(false)
.deduceEnvironment(false)
.build();
context.start();
GenericBeanDefinition ppd = new GenericBeanDefinition();
ppd.setBeanClass(MicronautPostProcess.class);
ppd.setInstanceSupplier(() -> new MicronautPostProcess(context));
registry.registerBeanDefinition("micronautPostProcess", ppd);
genericBeanDefinition.setInstanceSupplier(() -> context);
genericBeanDefinition.setDestroyMethodName("stop");
registry.registerBeanDefinition(
"micronautApplicationContext",
genericBeanDefinition
);
MergedAnnotation enableMicronautAnn = importingClassMetadata.getAnnotations().get(EnableMicronaut.class);
MicronautBeanFilter beanFilter = new MicronautBeanFilter() {
@Override
public boolean excludes(@NonNull BeanDefinition> definition) {
return definition.isAbstract() ||
Stream.of(
org.springframework.context.ApplicationContext.class,
ConversionService.class,
Environment.class,
ApplicationEventPublisher.class,
BeanFactory.class
).anyMatch(t -> t.isAssignableFrom(definition.getBeanType()));
}
};
if (enableMicronautAnn.isPresent()) {
if (enableMicronautAnn.hasNonDefaultValue("filter")) {
Class> filter = enableMicronautAnn.getClass("filter");
Object filterObject = InstantiationUtils.tryInstantiate(filter).orElse(null);
if (filterObject instanceof MicronautBeanFilter) {
MicronautBeanFilter specificFilter = (MicronautBeanFilter) filterObject;
MicronautBeanFilter currentFilter = beanFilter;
beanFilter = new MicronautBeanFilter() {
@Override
public boolean includes(@NonNull BeanDefinition> definition) {
return currentFilter.includes(definition) && specificFilter.includes(definition);
}
@Override
public boolean excludes(@NonNull BeanDefinition> definition) {
return currentFilter.excludes(definition) || specificFilter.excludes(definition);
}
};
}
}
EnableMicronaut enableMicronaut = enableMicronautAnn.synthesize();
EnableMicronaut.ExposedBean[] exposedBeans = enableMicronaut.exposeToMicronaut();
if (ArrayUtils.isNotEmpty(exposedBeans)) {
for (EnableMicronaut.ExposedBean exposedBean : exposedBeans) {
ExposedBeanData exposedBeanData = new ExposedBeanData(
exposedBean.beanType(),
StringUtils.isNotEmpty(exposedBean.name()) ? exposedBean.name() : null,
StringUtils.isNotEmpty(exposedBean.qualifier()) ? exposedBean.qualifier() : null,
null);
this.exposedBeans.remove(exposedBeanData);
this.exposedBeans.add(exposedBeanData);
}
}
}
Collection> allBeanDefinitions = context.getAllBeanDefinitions();
for (BeanDefinition> definition : allBeanDefinitions) {
if (beanFilter.includes(definition) &&
!beanFilter.excludes(definition)) {
Class> beanType = definition.getBeanType();
if (definition.isEnabled(context)) {
if (definition.isIterable()) {
Collection extends BeanDefinition>> beanDefinitions = context.getBeanDefinitions(beanType);
for (BeanDefinition> beanDefinition : beanDefinitions) {
registerBeanWithContext(
registry,
context,
beanDefinition,
beanType
);
}
} else {
registerBeanWithContext(registry, context, definition, beanType);
}
}
}
}
}
private static void registerBeanWithContext(BeanDefinitionRegistry registry, ApplicationContext context, BeanDefinition> definition, Class> beanType) {
String scope = definition.getScopeName().orElse(null);
GenericBeanDefinition gbd = new GenericBeanDefinition();
boolean isContextScope = Context.class.getName().equals(scope);
gbd.setPrimary(definition.isPrimary());
gbd.setLazyInit(!isContextScope);
int role = definition.hasDeclaredAnnotation(Infrastructure.class) ? org.springframework.beans.factory.config.BeanDefinition.ROLE_INFRASTRUCTURE : org.springframework.beans.factory.config.BeanDefinition.ROLE_APPLICATION;
gbd.setRole(role);
if (definition.isSingleton() || isContextScope || definition.isIterable()) {
gbd.setScope("singleton");
} else {
// perhaps support other scopes in the future
gbd.setScope("prototype");
}
gbd.setBeanClass(beanType);
gbd.setInstanceSupplier(() ->
context.getBean(definition)
);
Qualifier> qualifier = definition.getDeclaredQualifier();
String beanName = computeBeanName(registry, definition, gbd, qualifier);
gbd.setDescription("Bean named [" + beanName + "] of type [" + beanType.getName() + "] (Imported from Micronaut)");
if (!registry.containsBeanDefinition(beanName)) {
registry.registerBeanDefinition(
beanName,
gbd
);
}
}
private static String computeBeanName(BeanDefinitionRegistry registry, BeanDefinition> definition, GenericBeanDefinition gbd, Qualifier> qualifier) {
String beanName;
if (qualifier != null) {
if (qualifier instanceof Named) {
beanName = ((Named) qualifier).getName();
if ("Primary".equals(beanName)) {
beanName = AnnotationBeanNameGenerator.INSTANCE.generateBeanName(
gbd,
registry
);
} else {
beanName = definition.getBeanType().getName() + "(" + beanName + ")";
}
} else {
beanName = AnnotationBeanNameGenerator.INSTANCE.generateBeanName(
gbd,
registry
);
}
} else {
beanName = AnnotationBeanNameGenerator.INSTANCE.generateBeanName(
gbd,
registry
);
}
return beanName;
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@NonNull
private List propertySourcesForConfigurableEnvironment(@NonNull ConfigurableEnvironment ce) {
List result = new ArrayList<>();
MutablePropertySources propertySources = ce.getPropertySources();
for (PropertySource> propertySource : propertySources) {
if (propertySource instanceof MapPropertySource) {
MapPropertySource mps = (MapPropertySource) propertySource;
Map source = mps.getSource();
result.add(io.micronaut.context.env.PropertySource.of(
mps.getName(),
source
));
}
}
return result;
}
private final class MicronautPostProcess implements BeanFactoryPostProcessor {
private final ApplicationContext context;
public MicronautPostProcess(ApplicationContext context) {
this.context = context;
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
for (ExposedBeanData exposedBean : exposedBeans) {
String[] beanNames = beanFactory.getBeanNamesForType(exposedBean.beanType);
for (String beanName : beanNames) {
org.springframework.beans.factory.config.BeanDefinition beanDefinition =
beanFactory.getBeanDefinition(beanName);
String qualifier = exposedBean.nameTransformer.apply(beanName);
RuntimeBeanDefinition.Builder builder = RuntimeBeanDefinition.builder(DataSource.class, () ->
beanFactory.getBean(beanName, DataSource.class)
).qualifier(Qualifiers.byName(qualifier))
.singleton(beanDefinition.isSingleton());
if (beanDefinition.isPrimary()) {
MutableAnnotationMetadata metadata = new MutableAnnotationMetadata();
metadata.addDeclaredAnnotation(Primary.class.getName(), Collections.emptyMap());
builder.annotationMetadata(metadata);
}
context.registerBeanDefinition(
builder.build()
);
}
}
}
}
private static final class ExposedBeanData {
final Class> beanType;
@Nullable
final String beanName;
@Nullable
final String qualifier;
@NonNull
final UnaryOperator nameTransformer;
private ExposedBeanData(
Class> beanType,
@Nullable String beanName,
@Nullable String qualifier,
@Nullable UnaryOperator nameTransformer) {
this.beanType = beanType;
this.beanName = beanName;
this.qualifier = qualifier;
this.nameTransformer = nameTransformer != null ? nameTransformer : UnaryOperator.identity();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ExposedBeanData that = (ExposedBeanData) o;
return beanType.equals(that.beanType) && Objects.equals(beanName, that.beanName) && Objects.equals(qualifier, that.qualifier);
}
@Override
public int hashCode() {
return Objects.hash(beanType, beanName, qualifier);
}
}
}