Skip to content

Commit 15f4c95

Browse files
authored
Fix #2133 Spring boot starter breaks configuration classes (#79)
Fixes langchain4j/langchain4j#2133 The problem is `beanfactory.getBeanswithanNotation()`. It creates an instance of the Bean in advance. https://github.com/langchain4j/langchain4j-spring/blob/405fddafbcf8c3463c7211dc82b7c5887f9531c6/langchain4j-spring-boot-starter/src/main/java/dev/langchain4j/service/spring/AiServiceScannerProcessor.java#L49
1 parent b145129 commit 15f4c95

File tree

4 files changed

+82
-17
lines changed

4 files changed

+82
-17
lines changed

langchain4j-spring-boot-starter/src/main/java/dev/langchain4j/service/spring/AiServiceScannerProcessor.java

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -46,25 +46,27 @@ private Set<String> getBasePackages(ConfigurableListableBeanFactory beanFactory)
4646
}
4747

4848
private void addComponentScanPackages(ConfigurableListableBeanFactory beanFactory, Set<String> collectedBasePackages) {
49-
beanFactory.getBeansWithAnnotation(ComponentScan.class).forEach((beanName, instance) -> {
50-
Set<ComponentScan> componentScans = AnnotatedElementUtils.getMergedRepeatableAnnotations(instance.getClass(), ComponentScan.class);
51-
for (ComponentScan componentScan : componentScans) {
52-
Set<String> basePackages = new LinkedHashSet<>();
53-
String[] basePackagesArray = componentScan.basePackages();
54-
for (String pkg : basePackagesArray) {
55-
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
56-
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
57-
Collections.addAll(basePackages, tokenized);
58-
}
59-
for (Class<?> clazz : componentScan.basePackageClasses()) {
60-
basePackages.add(ClassUtils.getPackageName(clazz));
61-
}
62-
if (basePackages.isEmpty()) {
63-
basePackages.add(ClassUtils.getPackageName(instance.getClass()));
49+
for (String beanName : beanFactory.getBeanNamesForAnnotation(ComponentScan.class)) {
50+
Class<?> beanClass = beanFactory.getType(beanName);
51+
if (beanClass != null) {
52+
Set<ComponentScan> componentScans = AnnotatedElementUtils.getMergedRepeatableAnnotations(beanClass, ComponentScan.class);
53+
for (ComponentScan componentScan : componentScans) {
54+
Set<String> basePackages = new LinkedHashSet<>();
55+
for (String pkg : componentScan.basePackages()) {
56+
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
57+
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
58+
Collections.addAll(basePackages, tokenized);
59+
}
60+
for (Class<?> clazz : componentScan.basePackageClasses()) {
61+
basePackages.add(ClassUtils.getPackageName(clazz));
62+
}
63+
if (basePackages.isEmpty()) {
64+
basePackages.add(ClassUtils.getPackageName(beanClass));
65+
}
66+
collectedBasePackages.addAll(basePackages);
6467
}
65-
collectedBasePackages.addAll(basePackages);
6668
}
67-
});
69+
}
6870
}
6971

7072
private void removeAiServicesWithInactiveProfiles(BeanDefinitionRegistry registry) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package dev.langchain4j.service.spring.mode.automatic.Issue2133;
2+
3+
import org.springframework.beans.factory.annotation.Autowired;
4+
import org.springframework.boot.SpringApplication;
5+
import org.springframework.boot.autoconfigure.SpringBootApplication;
6+
7+
/**
8+
* @author: qing
9+
* @Date: 2024/11/20
10+
*/
11+
@SpringBootApplication
12+
public class TestAutowireAiServiceApplication {
13+
14+
@Autowired
15+
TestAutowireConfiguration testAutowireConfiguration;
16+
17+
public static void main(String[] args) {
18+
SpringApplication.run(TestAutowireAiServiceApplication.class, args);
19+
}
20+
21+
TestAutowireConfiguration getConfiguration() {
22+
return testAutowireConfiguration;
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package dev.langchain4j.service.spring.mode.automatic.Issue2133;
2+
3+
import dev.langchain4j.service.spring.AiServicesAutoConfig;
4+
import org.junit.jupiter.api.Test;
5+
import org.springframework.boot.autoconfigure.AutoConfigurations;
6+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
7+
8+
import static org.junit.jupiter.api.Assertions.assertNotNull;
9+
10+
class TestAutowireClassAiServiceIT {
11+
12+
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
13+
.withConfiguration(AutoConfigurations.of(AiServicesAutoConfig.class));
14+
15+
@Test
16+
void should_get_configuration_class() {
17+
contextRunner
18+
.withUserConfiguration(TestAutowireAiServiceApplication.class)
19+
.withBean(TestAutowireConfiguration.class)
20+
.run(context -> {
21+
// given
22+
TestAutowireAiServiceApplication application = context.getBean(TestAutowireAiServiceApplication.class);
23+
24+
// should get the configuration class
25+
assertNotNull(application.getConfiguration(), "TestConfiguration class should be not null");
26+
});
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package dev.langchain4j.service.spring.mode.automatic.Issue2133;
2+
3+
import org.springframework.context.annotation.Configuration;
4+
5+
/**
6+
* @author: qing
7+
* @Date: 2024/11/20
8+
*/
9+
@Configuration
10+
class TestAutowireConfiguration {
11+
}

0 commit comments

Comments
 (0)