Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.plugin.core.OrderAwarePluginRegistry;
import org.springframework.plugin.core.Plugin;
import org.springframework.plugin.core.PluginRegistry;
import org.springframework.plugin.core.support.PluginRegistryFactoryBean;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
Expand All @@ -39,6 +40,7 @@
* to the bean definition for the factory.
*
* @author Oliver Gierke
* @author Gabriel Hall
*/
public class PluginRegistriesBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

Expand Down Expand Up @@ -67,36 +69,46 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, B
}

for (Class<?> type : types) {
registerPluginRegistry(type, registry);
}
}

RootBeanDefinition beanDefinition = new RootBeanDefinition(PluginRegistryFactoryBean.class);
beanDefinition.setTargetType(getTargetType(type, OrderAwarePluginRegistry.class));
beanDefinition.getPropertyValues().addPropertyValue("type", type);

Qualifier annotation = type.getAnnotation(Qualifier.class);
/**
* Registers a {@link PluginRegistryFactoryBean} for the given plugin type.
*
* @param type the plugin type to register.
* @param registry the bean definition registry.
*/
public static void registerPluginRegistry(Class<?> type, BeanDefinitionRegistry registry) {

// If the plugin interface has a Qualifier annotation, propagate that to the bean definition of the registry
if (annotation != null) {
AutowireCandidateQualifier qualifierMetadata = new AutowireCandidateQualifier(Qualifier.class);
qualifierMetadata.setAttribute(AutowireCandidateQualifier.VALUE_KEY, annotation.value());
beanDefinition.addQualifier(qualifierMetadata);
}
RootBeanDefinition beanDefinition = new RootBeanDefinition(PluginRegistryFactoryBean.class);
beanDefinition.setTargetType(getTargetType(type, OrderAwarePluginRegistry.class));
beanDefinition.getPropertyValues().addPropertyValue("type", type);

// Default
String beanName = annotation == null //
? StringUtils.uncapitalize(type.getSimpleName() + "Registry") //
: annotation.value();
Qualifier annotation = type.getAnnotation(Qualifier.class);

registry.registerBeanDefinition(beanName, beanDefinition);
// If the plugin interface has a Qualifier annotation, propagate that to the bean definition of the registry
if (annotation != null) {
AutowireCandidateQualifier qualifierMetadata = new AutowireCandidateQualifier(Qualifier.class);
qualifierMetadata.setAttribute(AutowireCandidateQualifier.VALUE_KEY, annotation.value());
beanDefinition.addQualifier(qualifierMetadata);
}

String beanName = annotation == null //
? StringUtils.uncapitalize(type.getSimpleName() + "Registry") //
: annotation.value();

registry.registerBeanDefinition(beanName, beanDefinition);
}

/**
* Returns the target type of the {@link PluginRegistry} for the given plugin type.
*
* @param pluginType must not be {@literal null}.
* @return
* @param pluginClass must not be {@literal null}.
* @param wrapper the registry implementation type.
* @return the registry target type.
*/
private static ResolvableType getTargetType(Class<?> pluginClass, Class<?> wrapper) {
public static ResolvableType getTargetType(Class<?> pluginClass, Class<?> wrapper) {

Assert.notNull(pluginClass, "Plugin type must not be null!");

Expand Down
2 changes: 2 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@

<modules>
<module>core</module>
<module>spring-plugin-autoconfigure</module>
</modules>

<properties>
Expand All @@ -56,6 +57,7 @@
<slf4j.version>2.0.18</slf4j.version>

<artifactory-maven-plugin.version>3.7.0</artifactory-maven-plugin.version>
<spring-boot.version>4.2.0-M1</spring-boot.version>

<java-module-name>spring.plugin</java-module-name>
</properties>
Expand Down
57 changes: 57 additions & 0 deletions spring-plugin-autoconfigure/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>

<artifactId>spring-plugin-autoconfigure</artifactId>

<name>Spring Plugin - Autoconfigure</name>
<description>Spring Boot auto-configuration for Spring Plugin</description>

<parent>
<groupId>org.springframework.plugin</groupId>
<artifactId>spring-plugin</artifactId>
<version>4.2.0-SNAPSHOT</version>
</parent>

<properties>
<java-module-name>spring.plugin.autoconfigure</java-module-name>
</properties>

<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
<resource>
<directory>../src/main/resources</directory>
<filtering>true</filtering>
<targetPath>META-INF</targetPath>
</resource>
</resources>
</build>

<dependencies>

<dependency>
<groupId>org.springframework.plugin</groupId>
<artifactId>spring-plugin-core</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>${spring-boot.version}</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<version>${spring-boot.version}</version>
<scope>test</scope>
</dependency>

</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* Copyright 2026 the original author or 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 org.springframework.plugin.autoconfigure;

import java.util.LinkedHashSet;
import java.util.Set;

import org.jspecify.annotations.Nullable;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.core.ResolvableType;
import org.springframework.plugin.core.Plugin;
import org.springframework.plugin.core.PluginRegistry;
import org.springframework.plugin.core.config.PluginRegistriesBeanDefinitionRegistrar;

/**
* Registers plugin registries for direct plugin interfaces implemented by Spring beans.
*
* @author Gabriel Hall
*/
@AutoConfiguration
public final class PluginRegistriesAutoConfiguration {

@Bean
static PluginRegistryBeanDefinitionRegistrar pluginRegistryBeanDefinitionRegistrar() {
return new PluginRegistryBeanDefinitionRegistrar();
}

static final class PluginRegistryBeanDefinitionRegistrar
implements BeanDefinitionRegistryPostProcessor, BeanFactoryAware {

private @Nullable ListableBeanFactory beanFactory;

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {

ListableBeanFactory beanFactory = getBeanFactory();

for (Class<?> pluginType : getPluginTypes(beanFactory)) {
ResolvableType registryType = PluginRegistriesBeanDefinitionRegistrar.getTargetType(pluginType,
PluginRegistry.class);

if (beanFactory.getBeanNamesForType(registryType, true, false).length == 0) {
PluginRegistriesBeanDefinitionRegistrar.registerPluginRegistry(pluginType, registry);
}
}
}

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}

@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (ListableBeanFactory) beanFactory;
}

private static Set<Class<?>> getPluginTypes(ListableBeanFactory beanFactory) {

Set<Class<?>> result = new LinkedHashSet<>();

for (String beanName : beanFactory.getBeanNamesForType(Plugin.class, true, false)) {

Class<?> beanType = beanFactory.getType(beanName, false);

if (beanType != null) {
collectPluginTypes(beanType, result);
}
}

return result;
}

private ListableBeanFactory getBeanFactory() {

if (this.beanFactory == null) {
throw new IllegalStateException("No ListableBeanFactory configured!");
}

return this.beanFactory;
}

private static void collectPluginTypes(Class<?> type, Set<Class<?>> result) {

if (type.isInterface() && type != Plugin.class && Plugin.class.isAssignableFrom(type)) {
result.add(type);
return;
}

for (Class<?> candidate : type.getInterfaces()) {

if (candidate != Plugin.class && Plugin.class.isAssignableFrom(candidate)) {
result.add(candidate);
}
}

Class<?> superclass = type.getSuperclass();

if (superclass != null && superclass != Object.class) {
collectPluginTypes(superclass, result);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* Spring Boot auto-configuration for plugin registries.
*/
@org.jspecify.annotations.NullMarked
package org.springframework.plugin.autoconfigure;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.springframework.plugin.autoconfigure.PluginRegistriesAutoConfiguration
Loading