Skip to content

Commit

Permalink
[BLZT-47] Fixed inject non-config bean into config bean (#85)
Browse files Browse the repository at this point in the history
* [BLZT-47] Fixed inject non-config bean into config bean

* [BLZT-47] Fixed inject non-config bean into config bean

* [BLZT-47] Fixed unit tests
  • Loading branch information
mvorotniak authored Nov 29, 2023
1 parent 0409960 commit 0d18198
Show file tree
Hide file tree
Showing 16 changed files with 124 additions and 123 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,10 @@ public interface AnnotationResolver {
* @return a string representing resolved information from the annotations
*/
String resolve(Class<?> clazz);

default String getSimpleName(Class<?> clazz) {
String simpleName = clazz.getSimpleName();

return simpleName.substring(0, 1).toLowerCase() + simpleName.substring(1);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,12 @@ public boolean isSupported(Class<?> clazz) {
@Override
public String resolve(Class<?> clazz) {
String value = clazz.getAnnotation(Component.class).value();
String componentName = value.isEmpty() ? getSimpleName(clazz) : value;

String qualifier = getQualifier(clazz);

// qualifier -> Component.value -> className
return qualifier != null ? qualifier : value.isEmpty() ? clazz.getSimpleName() : value;
return qualifier != null ? qualifier : componentName;
}

private String getQualifier (Class<?> clazz) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public boolean isSupported(Class<?> clazz) {
*/
@Override
public String resolve(Class<?> clazz) {
return clazz.getSimpleName();
return getSimpleName(clazz);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,6 @@ public boolean isSupported(Class<?> clazz) {
*/
@Override
public String resolve(Class<?> clazz) {
return clazz.getSimpleName();
return getSimpleName(clazz);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public boolean isSupported(Class<?> clazz) {
@Override
public String resolve(Class<?> clazz) {
String value = clazz.getAnnotation(Service.class).value();
return value.isEmpty() ? clazz.getSimpleName() : value;
return value.isEmpty() ? getSimpleName(clazz) : value;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ public class AnnotationBringBeanRegistry extends DefaultBringBeanFactory impleme

@Getter
protected ClassPathScannerFactory classPathScannerFactory;
private final ConfigurationBeanRegistry configurationBeanRegistry;
private final BeanCreator beanCreator;
private final Set<String> currentlyCreatingBeans = new HashSet<>();
@Getter
Expand All @@ -28,7 +27,6 @@ public class AnnotationBringBeanRegistry extends DefaultBringBeanFactory impleme
public AnnotationBringBeanRegistry(Reflections reflections) {
this.reflections = reflections;
this.classPathScannerFactory = new ClassPathScannerFactory(reflections);
this.configurationBeanRegistry = new ConfigurationBeanRegistry(this);
this.beanCreator = new BeanCreator(this, classPathScannerFactory);
}

Expand All @@ -50,7 +48,7 @@ public void registerBean(String beanName, BeanDefinition beanDefinition) {
currentlyCreatingBeans.add(beanName);

if (beanDefinition.isConfigurationBean()) {
configurationBeanRegistry.registerConfigurationBean(beanName, beanDefinition);
beanCreator.registerConfigurationBean(beanName, beanDefinition);
} else {
beanCreator.create(clazz, beanName, beanDefinition);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@

import com.bobocode.bring.core.context.scaner.ClassPathScannerFactory;
import com.bobocode.bring.core.domain.BeanDefinition;
import com.bobocode.bring.core.exception.NoSuchBeanException;
import com.bobocode.bring.core.utils.ReflectionUtils;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;

/**
* Responsible for creating beans and injecting dependencies into these beans based on provided definitions.
*
Expand Down Expand Up @@ -35,21 +43,78 @@ public BeanCreator(AnnotationBringBeanRegistry beanRegistry, ClassPathScannerFac
*
* @param clazz The class of the bean to be created.
* @param beanName The name of the bean being created.
* @param beanDefinition The definition of the bean being created.
* @param beanDefinition The definition of the bean being created.
* @return The created bean object.
*/
public void create(Class<?> clazz, String beanName, BeanDefinition beanDefinition) {
createBeanUsingConstructor.create(clazz, beanName, beanDefinition);
injectDependencies(clazz, beanName);
public Object create(Class<?> clazz, String beanName, BeanDefinition beanDefinition) {
Object bean = createBeanUsingConstructor.create(clazz, beanName, beanDefinition);
injectDependencies(clazz, bean);

return bean;
}

/**
* Registers a configuration bean in the context based on the provided bean name and definition.
* Resolves dependencies and instantiates the configuration bean within the context.
*
* @param beanName The name of the configuration bean to be registered.
* @param beanDefinition The definition of the configuration bean.
* @return The registered configuration bean.
*/
public Object registerConfigurationBean(String beanName, BeanDefinition beanDefinition) {
Object configObj = Optional.ofNullable(beanRegistry.getSingletonObjects().get(beanDefinition.getFactoryBeanName()))
.orElseThrow(() -> {
log.info("Unable to register Bean from Configuration class [{}]: " +
"Configuration class not annotated or is not of Singleton scope.", beanName);
return new NoSuchBeanException(beanDefinition.getBeanClass());
});

List<String> methodParamNames = ReflectionUtils.getParameterNames(beanDefinition.getMethod());

List<Object> methodObjs = new ArrayList<>();
methodParamNames.forEach(
paramName -> {
Object object = beanRegistry.getBeanByName(paramName);

if (Objects.nonNull(object)) {
methodObjs.add(object);
} else {
BeanDefinition bd = Optional.ofNullable(beanRegistry.getBeanDefinitionMap().get(paramName))
.orElseThrow(() -> {
if (beanDefinition.getBeanClass().isInterface()) {
return new NoSuchBeanException(String.format("No such bean that implements this %s",
beanDefinition.getBeanClass()));
}
return new NoSuchBeanException(beanDefinition.getBeanClass());
});

Object newObj = bd.isConfigurationBean()
? registerConfigurationBean(beanName, bd)
: create(bd.getBeanClass(), beanName, bd);
methodObjs.add(newObj);
}
});

Supplier<Object> supplier = ReflectionUtils.invokeBeanMethod(beanDefinition.getMethod(),
configObj, methodObjs.toArray());
Object bean = supplier.get();

if (beanDefinition.isPrototype()) {
beanRegistry.addPrototypeBean(beanName, supplier);
} else {
beanRegistry.addSingletonBean(beanName, bean);
}

return bean;
}

/**
* Injects dependencies into the bean based on its class and name.
* Injects dependencies into the passed bean based on its class.
*
* @param clazz The class of the bean.
* @param beanName The name of the bean.
* @param bean The bean.
*/
private void injectDependencies(Class<?> clazz, String beanName) {
Object bean = beanRegistry.getSingletonObjects().get(beanName);
private void injectDependencies(Class<?> clazz, Object bean) {
fieldBeanInjection.injectViaFields(clazz, bean);
setterBeanInjection.injectViaSetter(clazz, bean);
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,13 @@ public class ConstructorBeanInjection {
* @param clazz The class for which the bean is to be created.
* @param beanName The name of the bean.
* @param beanDefinition The definition of the bean.
* @return The created bean object.
* @throws NoConstructorWithAutowiredAnnotationBeanException If no constructor is annotated with @Autowired.
* @throws NoSuchBeanException If a required dependency bean is not found.
*/
public void create(Class<?> clazz, String beanName, BeanDefinition beanDefinition) {
public Object create(Class<?> clazz, String beanName, BeanDefinition beanDefinition) {
var constructor = findAutowiredConstructor(clazz);
createBeanUsingConstructor(constructor, beanName, beanDefinition);
return createBeanUsingConstructor(constructor, beanName, beanDefinition);
}

/**
Expand Down Expand Up @@ -77,9 +78,10 @@ private Constructor<?> findAutowiredConstructor(Class<?> clazz) {
* @param constructor The constructor to use for bean instantiation.
* @param beanName The name of the bean to be created.
* @param beanDefinition The definition of the bean being created.
* @return The created bean object using constructor.
* @throws NoSuchBeanException If a required dependency bean is not found.
*/
private void createBeanUsingConstructor(Constructor<?> constructor, String beanName,
private Object createBeanUsingConstructor(Constructor<?> constructor, String beanName,
BeanDefinition beanDefinition) {
var createdBeanAnnotations = classPathScannerFactory.getCreatedBeanAnnotations();
Parameter[] parameters = constructor.getParameters();
Expand Down Expand Up @@ -113,6 +115,8 @@ private void createBeanUsingConstructor(Constructor<?> constructor, String beanN
} else {
beanRegistry.addSingletonBean(beanName, bean);
}

return bean;
}

/**
Expand Down Expand Up @@ -164,38 +168,18 @@ private String findBeanNameForArgumentInConstructor(Parameter parameter, List<St
private String findPrimaryBeanNameOrByQualifierOrbBParameter(List<String> beanNames, String paramName,
Parameter parameter) {
Class<?> parameterType = parameter.getType();
String qualifier = getQualifier(parameter);
var primaryNames = beanNames.stream()
.filter(beanName -> beanRegistry.getBeanDefinitionByName(beanName).isPrimary())
.toList();
if(primaryNames.size() == 1) {
return primaryNames.get(0);
} else if (primaryNames.size() > 1) {
throw new NoUniqueBeanException(parameterType);
} else if (qualifier != null) {
return beanNames.stream().filter(name -> name.equals(qualifier))
.findFirst().orElseThrow(() -> {
if (parameterType.isInterface()) {
return new NoSuchBeanException(String.format("No such bean that implements this %s ", parameterType));
}
return new NoSuchBeanException(parameterType);
});
}
else {
} else {
return beanNames.stream()
.filter(name -> name.equalsIgnoreCase(paramName))
.findFirst()
.orElseThrow(() -> new NoUniqueBeanException(parameterType, beanNames));
}
}

/**
* Retrieves the qualifier value from the parameter's annotation, if present.
*
* @param parameter The parameter for which the qualifier value is to be retrieved.
* @return The qualifier value if present, otherwise null.
*/
private String getQualifier(Parameter parameter) {
return parameter.isAnnotationPresent(Qualifier.class) ? parameter.getAnnotation(Qualifier.class).value() : null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ void shouldCreateAndInjectDifferentBeans() {

// when
var useCase = bringApplicationContext.getBean(GetInfoFromExternalServicesUseCase.class);
var restClient3 = bringApplicationContext.getBean(testdata.di.positive.fullinjection.RestClient.class, "restClient3");

// then
assertThat(useCase).isNotNull();
Expand All @@ -136,6 +137,8 @@ void shouldCreateAndInjectDifferentBeans() {
"restClient=RestClient{url='https://exterl.service', username='user100'}}, " +
"externalService2=ExternalService2{" +
"restClient2=RestClient{url='https://exterl.service2', username='user200'}}}");
assertThat(restClient3.getUrl()).isEqualTo("url");
assertThat(restClient3.getUsername()).isEqualTo("username");
}

@DisplayName("Should inject implementations of Interface to Field")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ void shouldThrowExceptionWhenMethodNotAnnotated() {
void shouldThrowExceptionWhenWeHaveTwoDependenciesForOneInterface() {
//given
var expectedMessage = "No qualifying bean of type 'interface testdata.di.negative.oneinterfacetwodependency.Drink'" +
" available: expected single matching bean but found 2: [Espresso,Latte]";
" available: expected single matching bean but found 2: [espresso,latte]";
//when
Executable executable = () -> {
var bringApplicationContext = BringApplication.run(DEMO_PACKAGE + ".oneinterfacetwodependency");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class BringApplicationContextCyclicDependencyTest {
@Test
void shouldThrowExceptionWhenWeHaveCyclicDependencyViaConstructor() {
//given
var expectedMessage = "Looks like you have cyclic dependency between those beans [A -> B -> C -> A]";
var expectedMessage = "Looks like you have cyclic dependency between those beans [a -> b -> c -> a]";


// when
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ public String url(String urlValue) {
}

@Bean
public BringService bringService3(@Qualifier("bringRestClient") RestClient сlient, String url) {
RestClient restClient = сlient.toBuilder().url(url).build();
public BringService bringService3(@Qualifier("bringRestClient") RestClient client, String url) {
RestClient restClient = client.toBuilder().url(url).build();

return new BringService(restClient);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,13 @@ public RestClient restClient2() {
.username("user200")
.build();
}

@Bean
public RestClient restClient3(DefaultRestClient defaultRestClient) {
return RestClient.builder()
.url(defaultRestClient.getUrl())
.username(defaultRestClient.getUsername())
.build();
}

}
Loading

0 comments on commit 0d18198

Please sign in to comment.