스프링 빈 (Bean) 이란?
POJO(Plain Old Java Object)로써 스프링 컨테이너에 의해 관리되는 객체이다.
- 주요 구성 요소
- 클래스(class) : 빈은 일반적으로 자바 클래스로 표현된다.
- id, name : 빈을 식별하는데 사용됨
- scope : 스프링 빈의 생존 기간 ( bean 생성 방법이기도 함, singleton, prototype 등)
- constructor-arg : 빈 생성시 생성자에 전달할 파라미터
- property : 빈 생성 시 setter 메서드를 통해 전달할 인수
- Lifecycle Callback : 빈의 생명 주기 관리를 위해 초기화와 소멸 단계에서 실행 되는 메서드를 정의함
- 초기화 콜백 - 빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출
- 소멸전 콜백 - 빈이 소멸되기 직전 호출
스프링에서는 기본적으로 스프링 컨테이너에 등록되어 있는 빈(Bean)을 싱글톤 객체로 생성하여 관리하는데, 이 때 빈을 등록하기 위한 방법들에 대해 정리해 본다.
스프링 빈 등록 방법
대표적으로 3가지가 있다.
- XML기반 설정 방식
- 자바 기반 설정 방식(@Configuration, @Bean)
- 애너테이션 기반 설정 방식(@Component)
기존 SpringMVC에서는 XML기반 설정 방식을 사용했었는데, 프로젝트의 규모가 커짐에 따라 XML에 등록 하는 것이 번거로워져 애너테이션을 활용한 방법이 나오게 되었다. 하나씩 살펴보자.
1. XML 기반 설정 방식
XML파일을 사용하는 방법으로 <bean> 요소의 class 속성에 FQCN(Fully-Qualified Class Name)을 기술하면 빈이 정의된다. <constructor-arg> 태그나 <property>요소를 사용해 의존성을 주입한다.
2. 자바 기반 설정 방식 (@Configuration & @Bean)
DI 컨테이너에 관리할 빈을 빈 설정 파일에 정의하는 방법으로 자바 클래스(설정 클래스)에 @Configuration 애너테이션을, 메서드에 @Bean 애너테이션을 사용해 빈을 정의하는 방법.
수동으로 등록하는 방법이며 메서드명이 빈의 이름이 되며 다르게 이름을 정의 하려면 @Bean(name="userRepo")와 같이 name 속성에 빈의 이름을 재정의하면 된다.
@Configuration // 설정 클래스 선언
public class AppConfig {
@Bean
public UserRepository userRepository() {
return new UserRepositoryImpl();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserService userService() {
return new UserServiceImpl(userRepository(), passwordEncoder()); //의존성 주입이 일어나는 부분
}
}
3.애너테이션 기반 설정 방식 (@Component)
@Component와 같은 마커 애너테이션이 부여된 클래스를 탐색해서 DI 컨테이너에 빈을 자동으로 등록하는 방법이다.
이러한 과정을 컴포넌트 스캔(Component Scan)이라고 한다. (대표적인 애너테이션은 @Component, @Controller, @Service, @Repository, @Configuration, @RestController 가 있다.)
의존성 주입 또한 자바 코드로 명시적으로 하는 것이 아니라 DI 컨테이너가 자동으로 필요로 하는 의존 컴포넌트를 주입하게 하는데 이런 과정을 오토와이어링(Auto Wiring)이라고 한다.
@Component
public class UserRepositoryImpl implements UserRepository {
...
}
@Component
public class BCryptPasswordEncoder implements PasswordEncoder {
....
}
위의 두개의 빈을 DI컨테이너에서 찾아 오토와이어링 대상에 주입한다.
@Component
public class UserServiceImpl implements UserService {
@Autowired //생성자에 애너테이션 부여
public UserServiceImpl(UserRepository userRepository, PasswordEncoder passwordEncoder) {
....
}
}
수동등록과 자동등록
수동으로 직접 빈을 등록하는 작업은 클래스가 많아질수록 작업할 것이 많아지기 때문에 생산력이 떨어질 수 밖에 없다.
(@Configuration 설정 클래스에 가서 @Bean 애너테이션을 달고, 객체를 생성하고, 주입할 대상을 적어야 한다.)
그래서 자동으로 설정하는 방법이 있고 스프링에서도 이 방법을 권장하고 있지만 수동으로 등록해야 하는 경우가 있다.
업무와 관련된 로직이 있는 곳은 웹 통신을 지원하는 컨트롤러, 비즈니스 로직이 있는 서비스, 데이터 계층 로직을 처리하는 레포지토리 등이 있는데 비슷한 패턴이 보이고 양도 많으며 수정이 많기 때문에 자동 기능을 사용하는 것이 좋다.
반대로 아래와 같은 경우는 수동 등록을 해야하거나 고려해봐야 하는 상황이다.
- 외부 라이브러리 사용과 같이 직접 제어할 수 없는 경우
- 애플리케이션 전반에 걸쳐 영향을 주는 경우
- 다형성으로 인한 여러 구현체를 등록해야 할 때
외부 라이브러리를 가져가 쓸 때, 빈으로 등록해주어야 1개의 객체만 생성되어 여러 클래스에서 사용할 때 메모리적으로 이득을 볼 수 있다. 해당 클래스를 프로젝트 내에서 생성한 것이 아니기 때문에 필수적으로 등록해야 한다.
데이터베이스 연결이나 공통 로그 처리와 같이 기술적인 문제나 공통 관심사를 처리해야 할 경우에는 로직이 잘 적용되고 있는지 명확하게 파악하기 위함이다.
다형성을 활용하는 경우도 한눈에 알아보기 쉽게 수동 등록을 고려해 볼 수 있다.
public class DiscountService {
private final Map<String,DiscountPolicy> policyMap;
public DiscountService(Map<String, DiscountPolicy> policyMap) {
this.policyMap = policyMap;
}
}
위 코드만 보면 DiscountPolicy가 어떤 것들이 있는지 파악하기 힘들다. 직접 DiscountPolicy와 관련된 코드들을 봐야 알 수 있기 때문에 다형성을 활용한 비즈니스 로직은 수동 빈 주입을 사용하면 아래와 같이 한번에 알아볼 수 있다.
@Configuration
public class DiscountConfig {
@Bean
public DiscountPolicy rateDiscountPolicy() {
return new RateDiscountPolicy();
}
@Bean
public DiscountPolicy fixDiscountPolicy() {
return new FixDiscountPolicy();
}
}
스프링 빈이 싱글톤으로 생성되는 것이 보장되는 이유
(@Configuration - CGLIB)
이전 포스팅에서(싱글톤 레지스트리) 스프링 빈이 싱글톤으로 생성되는것이 보장되는 이유를 궁금해 했었다.
@Component 애너테이션을 이용해 자동으로 빈을 등록한다면 스프링이 해당 클래스의 객체의 생성을 제어하게 되기 때문에 1개의 객체만 생성되도록 할 수 있다. 하지만 @Bean 애너테이션으로 수동 등록을 해 줄 경우, 아래와 같이 빈 등록 메서드를 중복으로 호출하게 된다. (memberRepository() 2번 호출)
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository()); //중복
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy()); //중복
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
}
}
하지만 스프링에서는 이런 문제를 방지하려고 @Configuration이 있는 클래스를 객체로 생성할 때 바이트코드를 조작하는 라이브러리를 사용해 프록시 패턴을 적용한다. 상속을 통해 임의의 다른 클래스를 만들고, 그 클래스를 대신 스프링 빈으로 등록한다.
아래와 같이 구현 되어 있을 것이다. (이해를 위한 예시)
@Bean
public MemberRepository memberRepository() {
if (memoryMemberRepository가 이미 스프링 컨테이너에 등록되어 있으면?) {
return 스프링 컨테이너에서 찾아서 반환;
} else { //스프링 컨테이너에 없으면
기존 로직을 호출해서 MemoryMemberRepository를 생성하고 스프링 컨테이너에 등록
return 반환
}
}
그리고 만약 의도적으로 매번 다른 객체를 생성하기 원할 경우에는 @Configuration 애너테이션의 proxyBeanMethods 에 false를 설정하면 된다.
@Configuration(proxyBeanMethods = false)
수동 방식(자바 기반 설정 방식)의 경우에는 @Bean만 사용해도 스프링 빈으로 등록 되지만, @Configuration을 명시해야 싱글톤을 보장받을 수 있다는 것을 명심하자.
'Backend > Spring' 카테고리의 다른 글
@Autowired 빈 설정 방식 (+ @Qualifier, @Primary) (0) | 2023.12.22 |
---|---|
다양한 의존관계 주입 방법 (생성자 주입을 선택해야 하는 이유) (0) | 2023.12.18 |
스프링의 싱글톤 (+ 싱글톤 레지스트리란?) (0) | 2023.12.11 |
DI (Dependency Injection, 의존관계 주입) 이란? (+ 필요성) (0) | 2023.12.10 |
IoC(제어의 역전)/ DI(의존관계 주입) 용어 정리 (0) | 2023.11.29 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!