When
아래와 같은 구성의 패키지 구조 일때 @Service 어노테이션으로 선언후 빌드 하면
me
└── chulgil
├── v1
│ └── HelloService.java
└── v2
└── HelloService.java
아래와 같은 에러가 발생한다.
Caused by:org.springframework.context.annotation.ConflictingBeanDefinitionException
Why
스프링 프레임워크는 빈의 이름을 기반으로 ID를 결정하기 때문에 다른 패키지에 있더라도
같은 이름의 클래스는 빈이 충돌한다는 메시지를 보게 된다.
What
이 문제를 해결하려면
- 빈 등록시에 고유 이름을 부여하는 방법
- 빈 이름을 생성하는 커스텀생성기를 작성하는 방법
이 있다.
How
1. 어노테이션에 식별자 부여 방법
package me.chulgil.v1
@Service("v1.HelloService")
public class HelloService {
// ...생략
}
@RestController("v1.HelloController")
public class HelloController {
// ...생략
}
package me.chulgil.v2
@Service("v2.HelloService")
public class HelloService {
// ...생략
}
@RestController("v2.HelloController")
public class HelloController {
// ...생략
}
2. BeanNameGenerator 구현 방법
스프링에서는
BeanNameGenerator구현체를 통해 빈 이름 생성 및 등록을 하기 때문에
이 구현체를 아래와 같이 패키지 경로까지 포함된 빈 이름으로 생성하여 등록하면 해결된다.
@SpringBootApplication
public class SampleApplication {
public static void main(String[] args) {
CustomBeanNameGen generator = new CustomBeanNameGen();
generator.addBasePackages("me.chulgil");
new SpringApplicationBuilder(SampleApplication.class)
.beanNameGenerator(generator)
.run(args);
}
}
public class CustomBeanNameGen implements BeanNameGenerator {
private final BeanNameGenerator generator = new AnnotationBeanNameGenerator();
private List<String> basePackages = new ArrayList<>();
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
return isTargetPackageBean(definition) ? getBeanName(definition) : generator.generateBeanName(definition, registry);
}
private boolean isTargetPackageBean(BeanDefinition definition) {
String beanClassName = getBeanName(definition);
return basePackages.stream().anyMatch(beanClassName::startsWith);
}
private String getBeanName(BeanDefinition definition) {
return definition.getBeanClassName();
}
public boolean addBasePackages(String path) {
return this.basePackages.add(path);
}
}