When

아래와 같은 구성의 패키지 구조 일때 @Service 어노테이션으로 선언후 빌드 하면

me
└── chulgil
    ├── v1
    │   └── HelloService.java
    └── v2
        └── HelloService.java

아래와 같은 에러가 발생한다.
Caused by:org.springframework.context.annotation.ConflictingBeanDefinitionException

Why

스프링 프레임워크는 빈의 이름을 기반으로 ID를 결정하기 때문에 다른 패키지에 있더라도
같은 이름의 클래스는 빈이 충돌한다는 메시지를 보게 된다.

What

이 문제를 해결하려면

  1. 빈 등록시에 고유 이름을 부여하는 방법
  2. 빈 이름을 생성하는 커스텀생성기를 작성하는 방법
    이 있다.

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);
    }
}