@ComponentScan
📌 Spring이 특정 패키지 내에서 @Component, @Service, @Repository, @Controller 같은 Annotation이 붙은 클래스를 자동으로 검색하고, 이를 Bean으로 등록하는 기능이다. 개발자가 Bean을 직접 등록하지 않고도 Spring이 자동으로 관리할 객체들을 찾는다.
ComponentScan의 역할
- Chef가 요리할 재료를 자동으로 식료품 저장고에서 찾아오는 과정, Chef는 스스로 필요한 재료를 찾아 요리에 사용한다.
- 요리사(개발자)가 직접 재료(Bean)를 찾아서 가져올 필요가 없다.
@ComponentScan
- 특정 패키지 내에 @Component Annotation이 붙은 클래스를 자동으로 찾아서 Spring Bean으로 등록한다.
- Annotation을 이용해 Bean을 등록할 수 있어 코드가 간결해지고 유지보수가 쉬워진다.
- 스캐닝 범위는 주로 애플리케이션의 루트(최상위) 패키지에서 시작된다.
- @SpringBootApplication
- 스프링 부트 애플리케이션의 시작점을 정의하기 위해 사용하는 애너테이션입니다. 이 애너테이션은 여러 기능을 결합한 복합 애너테이션으로, 스프링 부트 애플리케이션을 간단히 설정하고 실행할 수 있도록 돕습니다.
- SpringBoot로 프로젝트를 생성하면 main() 메서드가 있는 클래스 상단에 @SpringBootApplication Annotation 이 존재한다.
|
@ComponentScan의 동작 순서
1. 스프링 컨테이너가 빈 등록
- Spring Application이 실행되면 @ComponentScan이 지정된 패키지를 탐색한다.
- @ComponentScan은 @Component, @Service, @Repository, @Controller 등의 애너테이션이 붙은 클래스를 탐색합니다.
- 해당 클래스들을 스프링 빈으로 등록합니다.
- 구현 클래스뿐만 아니라, 인터페이스와 그 구현체도 함께 빈으로 등록됩니다.
2. 의존성 정의 (인터페이스 기반 설계)
- 일반적으로 **인터페이스(추상화)**를 의존성으로 정의합니다.
구현체는 스프링이 자동으로 선택하고 주입합니다.
3. 구현체 자동 연결
- 스프링은 등록된 빈 중에서 의존성으로 선언된 인터페이스에 맞는 구현체를 자동으로 찾아 주입합니다.
- 이 과정은 타입 매칭을 통해 이루어집니다.
@Configuration, @Bean
📌 Spring Bean을 등록하는 방법에는 수동, 자동 두가지가 존재한다.
Spring Bean 등록 방법
- Spring Bean은 Bean의 이름으로 등록된다.
- 1. 자동 Bean 등록(@ComponentScan, @Component)
- @Component 이 있는 클래스의 앞글자만 소문자로 변경하여 Bean 이름으로 등록한다.
- 1. 자동 Bean 등록(@ComponentScan, @Component)
// myService 라는 이름의 Spring Bean
@Component
public class MyService {
public void doSomething() {
System.out.println("Spring Bean 으로 동작");
}
}
- @ComponentScan 을 통해 @Component로 설정된 클래스를 찾는다.
- 2. 수동 Bean 등록(@Configuration, @Bean)
- @Configuration 이 있는 클래스를 Bean으로 등록하고 해당 클래스를 파싱해서 @Bean 이 있는 메서드를 찾아 Bean을 생성한다. 이때 해당 메서드의 이름으로 Bean의 이름이 설정된다.
// 인터페이스
public interface TestService {
void doSomething();
}
// 인터페이스 구현체
public class TestServiceImpl implements TestService {
@Override
public void doSomething() {
System.out.println("Test Service 메서드 호출");
}
}
// 수동으로 빈 등록
@Configuration
public class AppConfig {
// TestService 타입의 Spring Bean 등록
@Bean
public TestService testService() {
// TestServiceImpl을 Bean으로 등록
return new TestServiceImpl();
}
}
// Spring Bean으로 등록이 되었는지 확인
public class MainApp {
public static void main(String[] args) {
// Spring ApplicationContext 생성 및 설정 클래스(AppConfig) 등록
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// 등록된 TestService 빈 가져오기
TestService service = context.getBean(TestService.class);
// 빈 메서드 호출
service.doSomething();
}
}
수동으로 Bean을 등록할 때는 항상 @Configuration과 함께 사용해야 Bean이 싱글톤으로 관리된다. CGLIB 라이브러리와 연관이 있다. |
더보기
@Configuration과 싱글톤 관리
- **@Configuration**은 스프링에서 Java Config 클래스를 정의할 때 사용하는 애너테이션입니다. 이 애너테이션을 붙인 클래스는 스프링 컨테이너가 관리하는 설정 클래스로 동작하며, 해당 클래스에 정의된 Bean은 싱글톤으로 관리됩니다.
- 싱글톤 관리:
- @Configuration이 붙은 클래스는 내부적으로 CGLIB 동적 프록시 객체로 변환됩니다.
- 이 프록시 객체는 @Bean 메서드 호출 시, 이미 생성된 Bean이 있으면 이를 반환하고, 없으면 새로운 Bean을 생성합니다.
- 이를 통해 동일한 Bean이 여러 번 생성되지 않도록 보장합니다.
Bean 충돌
📌 Bean 등록 방법에는 수동, 자동 두가지가 존재하고 Bean은 각각의 이름으로 생성된다. 이때 이름이 같은 Bean이 설정되고자 한다면 충돌이 발생한다.
같은 이름의 Bean 등록
- 자동 Bean 등록 VS 자동 Bean 등록
public interface ConflictService {
void test();
}
// Bean의 이름을 service로 설정
@Component("service")
public class ConflictServiceV1 implements ConflictService {
@Override
public void test() {
System.out.println("Conflict V1");
}
}
// Bean의 이름을 service로 설정
@Component("service")
public class ConflictServiceV2 implements ConflictService {
@Override
public void test() {
System.out.println("Conflict V2");
}
}
// componentScan의 범위를 conflict 패키지 하위로 설정
@ComponentScan(basePackages = "com.example.springconcept.conflict")
public class ConflictApp {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(ConflictApp.class);
// Service 빈을 가져와서 실행
ConflictService service = context.getBean(ConflictService.class);
service.test();
}
}
- ConflictingBeanDefinitionException 발생
수동 Bean 등록 VS 자동 Bean 등록
// conflictService 이름으로 Bean 생성
@Component
public class ConflictService implements MyService {
@Override
public void doSomething() {
System.out.println("ConflictService 메서드 호출");
}
}
public class ConflictServiceV2 implements MyService {
@Override
public void doSomething() {
System.out.println("ConflictServiceV2 메서드 호출");
}
}
// 수동으로 Bean 등록
@Configuration
public class ConflictAppConfig {
// conflictService 이름으로 Bean 생성
@Bean(name = "conflictService")
MyService myService() {
return new ConflictServiceV2();
}
}
@ComponentScan(basePackages = "com.example.springconcept.conflict2")
public class ConflictApp2 {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(ConflictApp2.class);
// Service 빈을 가져와서 실행
MyService service = context.getBean(MyService.class);
service.doSomething();
}
}
- 수동 Bean 등록이 자동 Bean 등록을 오버라이딩해서 우선권을 가진다.
- 의도한 결과라면 다행이지만, 아닌 경우(실수)가 대부분이다. → 버그 발생
- Spring Boot에서는 수동과 자동 Bean등록의 충돌이 발생하면 오류가 발생한다.
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true |
설정 변경(application.properties)
// 수동, 자동 Bean을 동시에 등록할 때 이름이 같으면 수동 Bean이 오버라이딩
spring.main.allow-bean-definition-overriding=true
// 기본값
spring.main.allow-bean-definition-overriding=false
@Qualifier, @Primary
📌 같은 타입의 Bean이 중복된 경우 해결하기 위해 사용하는 Annotation
- 같은 타입의 Bean 충돌 해결 방법
- @Autowired + 필드명 사용
- @Autowired 는 타입으로 먼저 주입을 시도하고 같은 타입의 Bean이 여러개라면 필드 이름 혹은 파라미터 이름으로 매칭한다.
- @Autowired + 필드명 사용
public interface MyService { ... }
@Component
public class MyServiceImplV1 implements MyService { ... }
@Component
public class MyServiceImplV2 implements MyService { ... }
@Component
public class ConflictApp {
// 필드명을 Bean 이름으로 설정
@Autowired
private MyService myServiceImplV2;
...
}
- @Qualifier 사용
- Bean 등록 시 추가 구분자를 붙여 준다.
- 생성자 주입, setter 주입 사용 가능
@Component
@Qualifier("firstService")
public class MyServiceImplV1 implements MyService { ... }
@Component
@Qualifier("secondService")
public class MyServiceImplV2 implements MyService { ... }
@Component
public class ConflictApp {
private MyService myService;
// 생성자 주입에 구분자 추가
@Autowired
public ConflictApp(@Qualifier("firstService") MyService myService) {
this.myService = myService;
}
// setter 주입에 구분자 추가
@Autowired
public void setMyService(@Qualifier("firstService") MyService myService) {
this.myService = myService;
}
...
}
- @Primary 사용
- @Primary로 지정된 Bean이 우선 순위를 가진다.
@Component
public class MyServiceImplV1 implements MyService { ... }
@Component
@Primary
public class MyServiceImplV2 implements MyService { ... }
@Component
public class ConflictApp {
private MyService myService;
@Autowired
public ConflictApp(MyService myService) {
this.myService = myService;
}
...
}
- 실제 적용 사례
- Database가 (메인 MySQL, 보조 Oracle) 두개 존재하는 경우
- 기본적으로 MySQL을 사용할 때 @Primary를 사용하면 된다.
- 필요할 때 @Qualifier로 Oracle을 사용하도록 만들 수 있다.
- 동시에 사용되는 경우 @Qualifier 의 우선순위가 높다.
- Database가 (메인 MySQL, 보조 Oracle) 두개 존재하는 경우
같은 타입의 Bean이 여러개 조회되었지만 모든 Bean이 필요하다면, Java의 자료구조 Map, List를 사용하는 방법도 있다. |
수동 VS 자동
📌 Annotation 기반의 Spring에서는 자동 Bean 등록과 의존관계 주입을 사용하는 경우를 주로 사용한다. @Component 뿐만 아니라 @Controller, @Service, @Repository 등 자동으로 쉽게 등록할 수 있는 Annotation들을 지원하고 Spring Boot는 ComponentScan 방식을 기본으로 사용한다.
- 자동 Bean 등록을 사용하는 이유
- 다양한 Annotation으로 편리하게 등록할 수 있다.
- Spring Boot는 ComponentScan 방식을 기본으로 사용한다.
- 간단하지만 OCP, DIP를 준수하며 개발할 수 있다.
- 수동 Bean 등록을 사용하는 경우
- 외부 라이브러리나 객체를 Spring Bean으로 등록할 때
- 외부 라이브러리에서 제공하는 클래스는 자동 등록이 불가능하다.
- 데이터베이스 연결과 같이 비지니스 로직을 지원하는 기술들에 사용한다.
- 비지니스 로직보다 그 수가 아주 적지만 Application에 광범위하게 적용된다.
- 설정 정보에 명시되어 있어서 유지보수성이 증가한다.
- 같은 타입의 Bean 여러개 중 하나를 명시적으로 선택해야 할 때
- 외부 라이브러리나 객체를 Spring Bean으로 등록할 때
꼭 필요한 경우가 아니라면 자동 Bean 등록을 사용하면 된다.
'Back-End (Web) > Spring' 카테고리의 다른 글
[Spring] 의존관계 주입 (0) | 2024.12.26 |
---|---|
[Spring] Spring의 핵심 개념 (0) | 2024.12.24 |
[Spring] Layered Architecture (2) | 2024.12.20 |
[Spring] Server에서 Client로 Data를 전달하는 방법 (0) | 2024.12.19 |
[Spring] HTTP Message Body & TEXT (0) | 2024.12.18 |