스프링 빈 등록의 다양한 방법들
개요
스프링은 빈을 직접 관리하며 필요 시 빈을 주입해주는 DI방식으로 동작한다. 스프링이 빈을 등록하는 방식은 어노테이션, xml등 여러가지 방법을 사용해서 할 수 있다. 하지만 지금은 대부분 어노테이션을 사용하는 추세이고 방식도 이 방식이 훨씬 편하기 때문에 어노테이션 방식으로 빈을 등록하는 방법을 알아볼 것이다.
어노테이션을 이용해서 빈을 등록할 때 여러가지 어노테이션들이 사용된다.
- @Bean : 메소드 레벨에서 빈으로 등록한다.
- @Configuration : 내부에 생성된 빈들이 싱글톤이 유지되도록 해준다.
- @Component : 클래스 레벨에서 빈으로 등록한다.
- @ComponentScan : 특정 지정한 경로 하위에 있는 @Component가 선언된 클래스들을 빈으로 등록해준다.
(1) @Bean
메소드 레벨에서 빈으로 등록할 때 사용한다. 클래스 내부에 메소드를 만들고 @Bean을 입력하면 스프링 실행 시 자동으로 실행되어 빈으로 등록된다.
public class AppConfig {
@Bean
public MemberRepository memberRepository() {
return new MemberRepository();
}
}
무분별하게 사용하면 어디서 어떻게 관리가 되는지 알 수 없기 떄문에 보통 빈들을 따로 관리하기 위해서 만드는 클래스에서 사용한다.
(2) @Configuration
@Configuration은 빈들을 관리할 때 사용한다.
@Configuration
public class AppConfig {
@Bean
public MemberServiceImpl memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
return new MemberRepository();
}
}
애플리케이션을 관리하기 위한 AppConfig라는 파일을 만들어서 빈을 등록해볼 때 클래스 어노테이션으로 @Configutation을 사용하고 메소드 클래스에 @Bean을 사용했다.
@Configutation이 설정된 클래스에서 생성된 빈들은 스프링이 내부적으로 싱글톤을 확실하게 보장해준다. 위와 같은 코드가 있을 때 memberRepository()가 실행되면서 인스턴스가 생성되는데 memberService()에서 memberRepository()를 또 실행시키고 있다. 이렇게 되면 MemberRepository인스턴스가 2개가 생성되면서 싱글톤이 깨지게 된다. 이런 경우에서도 싱글톤을 유지해주는 방식이 바로 @Configuration어노테이션이다.
싱글톤을 유지하는 자세한 방식은 https://solidbasics.tistory.com/37에서 확인 할 수 있다.
(3) @Component
@ComponentScan에 의해서 자동으로 빈으로 등록할 수 있도록 한다. 클래스 범위에 사용할 수 있다.
@Component
public class MemberRepository {
}
@ComponentScan의 범위 밖에 있으면 @Component가 선언되어도 빈으로 등록되지 않는다.
(4) @ComponentScan
특정 범위 안에서 @Component가 사용된 모든 클래스들을 빈으로 등록해주기 위해 사용한다. @Component를 스캔할 범위를 지정해야한다. 범위를 지정하지 않으면 모든 클래스, 라이브러리들을 스캔해야 하기 때문에 너무 광범위하게 동작하게 된다.
스캔 범위를 지정하는 방법은 다음과 같다.
- 지정한 패키지와 하위에 있는 패키지를 스캔한다.
- 지정한 클래스가 속한 패키지와 하위에 있는 패키지를 스캔한다.
// 지정한 패키지와 하위에 있는 패키지를 스캔한다.
@Configuration
@ComponentScan(
basePackages = "com.example.singleton.component"
)
public class AutoAppConfig {
}
basePackages를 사용해서 패키지 경로를 입력해주면 된다.
// 지정한 클래스가 속한 패키지와 하위에 있는 패키지를 스캔한다.
package com.example.singleton.component;
@Configuration
@ComponentScan(
basePackageClasses = AutoAppConfig.class
)
public class AutoAppConfig {
}
클래스를 현재 클래스로 지정했으므로 현재 클래스가 속해있는 패키지를 기준으로 스캔한다.
스프링부트에서는 아무 설정 없이 어노테이션만 사용한다면 어노테이션을 사용한 클래스가 속한 패키지를 디폴트범위로 지정한다.
@ComponentScan이 스캔하는 대상은 @Component외에도 다양하게 있다.
- @Controller : 스프링 MVC 컨트롤러에서 사용 / 스프링 MVC컨트롤러로 인식
- @Service : 스프링 비즈니스 로직에서 사용 / 특별한 처리는 없지만 비즈니스 계층을 인식하는데 사용한다.
- @Repository : 스프링 데이터 접근 계층에서 사용 / 스프링 데이터 접근 계층으로 인식하고, 데이터 계층의 예외를 스프링 예외로 변환시켜준다.
- @Configuration : 스프링 설정 정보에서 사용 / 스프링 설정정보로 인식하고 싱글톤을 유지하도록 추가
스프링을 사용해본 사람이라면 익숙한 어노테이션 들일텐데 '각 어노테이션을 언제 사용하는지 / 스프링에서 어떻게 동작시키는지'를 알 수 있다.
사실 위의 어노테이션들이 컴포넌트 스캔에 의해서 빈으로 등록되는 것은 각 어노테이션 내부에 @Component가 들어있기 때문이다.
@Component
public @interface Controller
...
@Component
public @interface Service
...
@Component
public @interface Repository
...
@Component
public @interface Configuration
...
@Component외에 다른 어노테이션들도 존재하지만 @Component가 들어있기 때문에 컴포넌트 스캔에 의해서 빈으로 등록될 수 있다.
@ComponentScan이 없으면 @Component가 선언된 클래스라도 빈으로 등록되지 않는다. @Configuration도 결국 내부에 @Component때문에 빈으로 등록이 가능한 것이기 때문에 @ComponentScan이 필요하다.
평소 스프링을 실행시켰을 때 특별하게 @ComponentScan을 설정하지도 않았는데 @Controller나 @Service등이 빈으로 등록되는 이유는 @SpringBootApplication덕분이다.
@SpringBootApplication은 스프링부트를 생성하면 가장 상위 패키지에 존재하는 클래스에 설정되어있다. 그리고 이 어노테이션 내부에 @ComponentScan이 들어있다.
결국 스프링을 동작시킬 때 최상위에 있는 클래스 내부에 @ComponentScan이 있기 때문에 해당 클래스가 있는 패키지와 하위에 있는 패키지들은 모두 스캔대상이 되면서 빈으로 등록되는 것이다.
자동으로 등록되는 빈의 이름
빈을 수동으로 등록해줄 때는 빈의 이름을 직접 지정할 수 있다.
@Configuration
public class AppConfig {
@Bean
public MemberRepository memberRepository() {
return new MemberRepository();
}
}
이와 같이 빈을 등록하면 메소드의 이름이 빈의 이름이 되는 것이다. 이 빈의 이름은 memberRepository이다.
그렇다면 자동으로 등록되는 빈은 이름이 어떻게 지정될 것인가?
먼저 아무 설정도 하지 않는다면 클래스 이름의 맨 앞자리를 소문자로 바꾼 이름이 빈의 이름으로 등록된다.
@Component
public class MemberRepository {
}
이와 같이 설정하면 등록되는 빈의 이름은 memberRepository가 되는 것이다.
이름을 직접 지정할 수도 있다.
@Component("repository")
public class MemberRepository {
}
@Component옆에 직접 이름을 지정할 수도 있다. 이때 빈의 이름은 repository가 되는 것이다.
중복등록과 충돌
빈을 등록하다보면 빈의 이름이 중복되는 경우가 생길 수 있는데 크게 2가지의 상황이 생길 수 있다.
- 자동 빈 등록 vs 자동 빈 등록
- 수동 빈 등록 vs 자동 빈 등록
(1) 자동 빈 등록 vs 자동 빈 등록
자동으로 등록되는 경우인데 이름이 중복되면 스프링은 ConflictingBeanDefinitionException예외를 발생시킨다.
(2) 수동 빈 등록 vs 자동 빈 등록
보통 포괄적인 것과 상세한 것이 있다면 상세한 것이 우선순위를 가진다. 그러므로 자동과 수동이 중복이 되면 수동으로 등록한 빈이 우선순위를 가지고 등록된다.
그런데 사실 빈의 이름이 중복되는 경우는 보통 대부분의 경우는 실수로 인해 생기는 현상일 것이다. 그런데 이런 상황에서 그냥 수동 등록이 우선권을 가져가서 등록되버리면 문제가 터졌을 때 찾아내기가 쉽지 않다.
그래서 스프링부트는 기본설정으로 수동등록과 자동등록이 중복되면 에러를 발생시키게 된다.
memberRepository를 수동으로 등록하고 자동등록에서도 이름을 memberRepository로 지정한 뒤 스프링을 실행한 결과이다.
memberRepository가 이미 등록되어 있다는 에러메세지와 함께 어떤 식으로 설정을 바꾸면 사용할 수 있는지도 알려주고 있다.
spring.main.allow-bean-definition-overriding=true 라는 설정을 application.properties에 추가해주면 중복 등록되었을 때 수동을 우선순위로 등록하게 될 것이다. 스프링부트에서는 이 설정이 기본값으로 false로 설정되어 있기 때문에 실행 시 에러를 띄우게 된다.
가장 좋은 에러는 컴파일 에러라고 한다. 스프링이 이러한 부분에서 섬세하게 에러를 잡아주고 있다.
About
해당 게시글은 김영한님의 '스프링 핵심 원리 - 기본편'을 공부하고 작성한 내용입니다.
스프링 핵심 원리 - 기본편 - 인프런 | 강의
스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런...
www.inflearn.com
해당 게시글의 소스코드는 https://github.com/ssung0810/spring_study/tree/main/singleton에서 볼 수 있습니다.