Skip to content

Commit

Permalink
fix #1827 ComponentScan with SPEL will not parse (#48)
Browse files Browse the repository at this point in the history
Fixes langchain4j/langchain4j#1827

fix ComponentScan basePackages with placeholders will not be parsed. 

Changes Made:
- Support for parsing @componentscan with placeholders
- Simplify the getBasePackages()
  • Loading branch information
qing-wq authored Oct 28, 2024
1 parent 6311727 commit 3fbf707
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Profile;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import java.util.*;

Expand All @@ -24,49 +25,46 @@ public class AiServiceScannerProcessor implements BeanDefinitionRegistryPostProc

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
ClassPathAiServiceScanner classPathAiServiceScanner = new ClassPathAiServiceScanner(registry, false);
ClassPathAiServiceScanner scanner = new ClassPathAiServiceScanner(registry, false);
Set<String> basePackages = getBasePackages((ConfigurableListableBeanFactory) registry);
for (String basePackage : basePackages) {
classPathAiServiceScanner.scan(basePackage);
}
scanner.scan(StringUtils.toStringArray(basePackages));

removeAiServicesWithInactiveProfiles(registry);
}

private Set<String> getBasePackages(ConfigurableListableBeanFactory beanFactory) {
Set<String> basePackages = new LinkedHashSet<>();

// AutoConfiguration
List<String> autoConfigPackages = AutoConfigurationPackages.get(beanFactory);
basePackages.addAll(autoConfigPackages);

String[] beanNames = beanFactory.getBeanNamesForAnnotation(ComponentScan.class);
for (String beanName : beanNames) {
Class<?> beanClass = beanFactory.getType(beanName);
if (beanClass != null) {
ComponentScan componentScan = beanClass.getAnnotation(ComponentScan.class);
if (componentScan != null) {
Collections.addAll(basePackages, componentScan.value());
Collections.addAll(basePackages, componentScan.basePackages());
for (Class<?> basePackageClass : componentScan.basePackageClasses()) {
basePackages.add(basePackageClass.getPackage().getName());
}
}
}
}
// ComponentScan
addComponentScanPackages(beanFactory, basePackages);

return basePackages;
}

String[] applicationBeans = beanFactory.getBeanNamesForAnnotation(SpringBootApplication.class);
if (applicationBeans.length > 0) {
Class<?> applicationBeanClass = beanFactory.getType(applicationBeans[0]);
SpringBootApplication springBootApplication = AnnotationUtils.findAnnotation(applicationBeanClass, SpringBootApplication.class);
if (springBootApplication != null) {
Collections.addAll(basePackages, springBootApplication.scanBasePackages());
for (Class<?> aClass : springBootApplication.scanBasePackageClasses()) {
basePackages.add(ClassUtils.getPackageName(aClass));
private void addComponentScanPackages(ConfigurableListableBeanFactory beanFactory, Set<String> collectedBasePackages) {
beanFactory.getBeansWithAnnotation(ComponentScan.class).forEach((beanName, instance) -> {
Set<ComponentScan> componentScans = AnnotatedElementUtils.getMergedRepeatableAnnotations(instance.getClass(), ComponentScan.class);
for (ComponentScan componentScan : componentScans) {
Set<String> basePackages = new LinkedHashSet<>();
String[] basePackagesArray = componentScan.basePackages();
for (String pkg : basePackagesArray) {
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
Collections.addAll(basePackages, tokenized);
}
for (Class<?> clazz : componentScan.basePackageClasses()) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(instance.getClass()));
}
collectedBasePackages.addAll(basePackages);
}
}

return basePackages;
});
}

private void removeAiServicesWithInactiveProfiles(BeanDefinitionRegistry registry) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ static class ComponentScanWithBasePackages {
static class ComponentScanWithBasePackageClasses {
}

@ComponentScan(basePackages = "${test.basePackages}")
static class ComponentScanWithPlaceholder {
}

@Test
void should_create_AI_service_that_use_componentScan_value() {

Expand Down Expand Up @@ -98,4 +102,29 @@ void should_create_AI_service_that_use_componentScan_basePackageClasses() {
assertThat(answer).containsIgnoringCase("Berlin");
});
}

@Test
void should_create_AI_service_that_use_componentScan_with_placeholder() {

contextRunner
.withPropertyValues(
"langchain4j.open-ai.chat-model.api-key=" + OPENAI_API_KEY,
"langchain4j.open-ai.chat-model.max-tokens=20",
"langchain4j.open-ai.chat-model.temperature=0.0",
"test.basePackages=dev.langchain4j.service.spring.mode.automatic.differentPackage.package2"
)
.withUserConfiguration(DifferentPackageAiServiceApplication.class)
.withUserConfiguration(ComponentScanWithPlaceholder.class)
.run(context -> {

// given
DifferentPackageAiService aiService = context.getBean(DifferentPackageAiService.class);

// when
String answer = aiService.chat("What is the capital of Germany?");

// then
assertThat(answer).containsIgnoringCase("Berlin");
});
}
}

0 comments on commit 3fbf707

Please sign in to comment.