Skip to content

Commit

Permalink
fix(spring): allow bean injection by constructor
Browse files Browse the repository at this point in the history
Allow to instanciate this type of bean https://github.com/gravitee-io/gravitee-federation-agent/blob/2.7.1/gravitee-federation-agent-standalone/gravitee-federation-agent-standalone-container/src/main/java/com/graviteesource/integration/agent/standalone/probes/StatusProbe.java

- fully rely on Spring to instanciate beans (by constructors, fields or any else)
- spring already inject ApplicationContext if need
- should allow usage of @PostConstruct
- use more simple function to get all beans for a type

APIM-7022
  • Loading branch information
michel-barret committed Oct 1, 2024
1 parent c5f409f commit a7ac22b
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 40 deletions.
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
<freemarker.version>2.3.31</freemarker.version>
<gravitee-gateway-api.version>3.5.0</gravitee-gateway-api.version>
<awaitability.version>4.2.1</awaitability.version>
<javax.annotation-api.version>1.3.2</javax.annotation-api.version>
</properties>

<dependencyManagement>
Expand Down Expand Up @@ -173,5 +174,11 @@
<version>${awaitability.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>${javax.annotation-api.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,13 @@
*/
package io.gravitee.common.spring.factory;

import java.lang.reflect.Constructor;
import java.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

/**
* @author David BRASSELY (david.brassely at graviteesource.com)
Expand All @@ -53,49 +48,18 @@ public void setApplicationContext(ApplicationContext applicationContext) throws
protected Collection<? extends T> getFactoriesInstances() {
if (factories == null) {
logger.debug("Loading instances for type {}", getObjectType().getName());
factories = getSpringFactoriesInstances(getObjectType(), new Class<?>[] {});
factories = createSpringFactoriesInstances(applicationContext, getObjectType());
} else {
logger.debug("Instances for type {} already loaded. Skipping...", getObjectType().getName());
}

return factories;
}

private Collection<? extends T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(
org.springframework.core.io.support.SpringFactoriesLoader.loadFactoryNames(type, classLoader)
);
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
// @VisibleForTesting
static <U> List<U> createSpringFactoriesInstances(ApplicationContext applicationContext, Class<U> type) {
var instances = new ArrayList<>(applicationContext.getBeansOfType(type).values());
AnnotationAwareOrderComparator.sort(instances);
return instances;
}

@SuppressWarnings("unchecked")
private <T> List<T> createSpringFactoriesInstances(
Class<T> type,
Class<?>[] parameterTypes,
ClassLoader classLoader,
Object[] args,
Set<String> names
) {
List<T> instances = new ArrayList<>(names.size());
for (String name : names) {
try {
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
T instance = (T) BeanUtils.instantiateClass(constructor, args);
((AbstractApplicationContext) applicationContext).getBeanFactory().autowireBean(instance);
if (instance instanceof ApplicationContextAware) {
((ApplicationContextAware) instance).setApplicationContext(applicationContext);
}
instances.add(instance);
} catch (Throwable ex) {
throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Copyright © 2015 The Gravitee team (http://gravitee.io)
*
* 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
*
* http://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.gravitee.common.spring.factory;

import static org.assertj.core.api.Assertions.assertThat;

import javax.annotation.PostConstruct;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;

@Configuration
class SpringFactoriesLoaderTest {

@Service
static class MyServiceDeps {}

interface AService {
ApplicationContext getApplicationContext();

MyServiceDeps getMyServiceDeps();
}

@RequiredArgsConstructor
@Setter
@Getter
@Order(12)
@Service
static class MyService implements ApplicationContextAware, AService {

private final MyServiceDeps myServiceDeps;
private ApplicationContext applicationContext;
}

@RequiredArgsConstructor
@Setter
@Getter
@Order(1)
@Service
static class MyAnotherService implements ApplicationContextAware, AService {

private final MyServiceDeps myServiceDeps;
private ApplicationContext applicationContext;
}

@RequiredArgsConstructor
@Getter
@Order(3)
@Service
static class MyYetAnotherService implements AService, InitializingBean {

private MyServiceDeps myServiceDeps;
private final ApplicationContext applicationContext;

@Override
public void afterPropertiesSet() {
myServiceDeps = new MyServiceDeps();
}
}

@RequiredArgsConstructor
@Getter
@Order(5)
@Service
static class MyYetYetAnotherService implements AService {

private MyServiceDeps myServiceDeps;
private final ApplicationContext applicationContext;

@PostConstruct
public void setup() {
myServiceDeps = new MyServiceDeps();
}
}

@Test
void createSpringFactoriesInstances() {
// Given
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringFactoriesLoaderTest.class);

// When
var svc = SpringFactoriesLoader.createSpringFactoriesInstances(ctx, AService.class);

// Then
assertThat(svc).hasSize(4);
for (AService s : svc) {
assertThat(s.getApplicationContext()).isNotNull();
assertThat(s.getMyServiceDeps()).isNotNull();
}
assertThat(svc.stream().mapToInt(o -> o.getClass().getAnnotation(Order.class).value())).isSorted();
}
}

0 comments on commit a7ac22b

Please sign in to comment.