0. 서론
회사에서 프로젝트를 진행하면서 최근에 코드 합칠 일이 많았다.
프로젝트 초기에는 형상관리를 위한 SVN 저장소가 없는 상태였고, 자동 빌드배포를 위한 CM서버도 구축되어 있지 않았기 때문에
각자 맡은 모듈을 각자의 프로젝트로서 생성하여 구현하였다.
그 후, SVN 저장소가 생성되고 CM서버 구축도 막바지였기 때문에 각자 작성한 코드를 합쳐서 하나의 프로젝트로 SVN에 올려야 했다.
코드 합치는 과정은 순탄치가 않았다.
서비스 백엔드 서버에 필요한 로그인-인증 코드, 연동 서버에 필요한 SOAP연동-배치 코드로 총 두 번의 합치는 과정이 있었다.
합치는 두 번의 과정에서 공통적으로 bean
생성 오류가 있었고, bean
생성 순서가 꼬였던 것이 원인이었다.
한 번은 main에서의 어노테이션 한 개를 지워서 해결했고(어떤 어노테이션인지는 회사pc로 확인 후 작성할 에정),
다른 한 번은 @Configuration
어노테이션이 선언된 config 파일의 위치를 바꿔서 해결하였다. => 프로젝트(디렉토리) 구조에 따라 bean생성 순서가 달라진다.
두 번의 삽질을 경험하니... Bean
이란 놈이 어떻게 생성되는지가 궁금해진다.
1. Bean 이란?
Bean이란?
Spring의 IoC(제어의 역전)특성을 활용하는 Spring Container에 의해 관리되는 Java 객체.
개발자가 작성한new
연산자에 의해 생성되는 것이 아닌, Application이 실행될 때 ApplicationContext에 의해 생성되고 관리되는 객체이다.
IoC
와 DI
에 대한 개념은 👉여기에서 확인
Life Cycle
Bean
은 Spring Container
에 의해 생성되고 관리되기 때문에 Java객체인 Bean
의 생명주기(Life Cycle)도
객체생성 -> 의존설정 -> 초기화 -> 사용 -> 소멸
형태로 Spring Container
에 의해 관리된다.
Bean 사용 이유
Bean
은 위와 같은 Life Cycle로 Spring Container
에 의해 관리된다.
그래서 해당 application으로 요청이 올 때마다 객체를 생성하는 것이 아닌,Spring Container
에 의해 초기화 과정에서 생성된 Bean
객체를 활용하기 때문에 성능상 이점이 있다.
또한, 의존성 관리와 java 객체의 life cycle관리에도 용이하게 때문에 Bean
을 활용한다.
Bean의 생성
Bean
은 다음과 같이 두 가지 방법으로 생성될 수 있다.
@Component
어노테이션을 활용한 자동등록 방식@Configuration
+@Bean
어노테이션을 활용한 수동등록 방식
2. Bean 생성 방식
@Component 활용 자동 방식
Bean
으로 등록하고자 하는 클래스에 @Component
어노테이션을 붙이는 방식이다.
@Component
public class testClass {
public void testMethod() {
...
}
}
위와 같이 클래스이름 상단에 @Component
어노테이션을 붙이면 Bean
으로 등록된다.
뿐만 아니라 @Component
어노테이션을 활용하는 @Controller
, @Service
, @Repository
어노테이션을 붙인 클래스도 Bean
으로 등록된다.
@Comfiguration + @Bean 활용 수동 방식
아래 코드와 같이 @Configuration
어노테이션을 붙여도 Bean
으로 등록된다.@Configuration
어노테이션 역시 하위 어노테이션으로 @Component
어노테이션이 있기 때문이다.
@Configuration
public class Config {
@Bean
public SimpleBean simpleBean() {
return new SimpleBean();
}
@Bean
public SimpleBeanConsumer simpleBeanConsumer() {
return new SimpleBeanConsumer(simpleBean());
}
}
@Configuration
어노테이션을 붙이는 방식은 클래스 내부 메소드에 @Bean
어노테이션을 붙이는 방식이 수반된다.
여기서 궁금증이 생길 것이다.
@Configuration + @Bean 수동 방식은 왜 활용하는 것인가?
클래스에 @Component
어노테이션 하나만 붙여도 Bean
으로 등록되어 Spring Container
가 관리할 수 있도록 IoC(제어의 역전)
형태가 만들어진다. 그런데 굳이 @Configuration
어노테이션을 붙이고 내부 메소드에도 @Bean
어노테이션을 붙여서 Bean
으로 등록해야 할까?
@Component
를 이용한 자동방식과 @Configuration
+ @Bean
을 이용한 수동방식의 차이를 이해하면 궁금증이 해결될 것이다.
아래 예제를 보자.
=== 1 ===
@Component
public class Config {
@Bean
public SimpleBean simpleBean() {
return new SimpleBean();
}
@Bean
public SimpleBeanConsumer simpleBeanConsumer() {
return new SimpleBeanConsumer(simpleBean());
}
}
=== 2 ===
@Configuration
public class Config {
@Bean
public SimpleBean simpleBean() {
return new SimpleBean();
}
@Bean
public SimpleBeanConsumer simpleBeanConsumer() {
return new SimpleBeanConsumer(simpleBean());
}
}
1번 코드와 2번 코드는 클래스에 붙은 어노테이션을 제외하면 차이가 없다. 하지만 동작 상에는 큰 차이가 있다.
@Component
가 붙은 1번 코드에서 simpleBeanConsumer
메소드가 실행되면 의존관계에 있는 simpleBean
메소드가 실행되어 새로운 SimpleBean
객체가 생성된다.
하지만,@Configuration
이 붙은 2번 코드에서 simpleBeanConsumer
메소드가 실행되면 의존관계에 있는 simpleBean
메소드가 실행되는데, 새로운 SimpleBean
객체가 생성되진 않고 Bean
등록 과정에서 Spring Context
에 등록된 SimpleBean
Bean이 불러와진다.
1번 코드에서는 객체를 새로 생성하지만, 2번 코드에서는 기존에 생성된 객체를 불러온다는 큰 차이가 있다는 것이다.
2번 코드를 @Component
어노테이션을 활용하여 아래 코드와 같이 재구성해보면 이해가 쉬울 것이다.
@Component
public class Config {
@Autowired
SimpleBean simpleBean;
@Bean
public SimpleBean simpleBean() {
return new SimpleBean();
}
@Bean
public SimpleBeanConsumer simpleBeanConsumer() {
return new SimpleBeanConsumer(simpleBean);
}
}
이러한 두 방식의 차이는 CGLIB(Code Generation Library)
가 두 어노테이션에 동작하는 방식이 다르기 때문에 발생한다고 한다.@Configuration
어노테이션 내부에서 호출된 메소드가 @Bean
어노테이션이 붙은 메소드라면 Spring Context
에 등록된 Bean
을 반환하도록 되어있다고 한다. (자세한건 CGLIB
에 대해 알아보면서 파악하면 될 것 같다.)
중간 정리
@Component
를 활용한 자동방식과 @Configuration
+ @Bean
을 활용한 수동방식의 차이는 Spring Context
에 등록된 Bean을 반환하냐 안하냐의 차이이다. 이러한 차이가 발생한 이유는 두 어노테이션에 CGLIB
가 작동하는 방식이 다르기 때문이다.
이러한 특징때문에 자동방식은 보통 비즈니스 로직을 구현할 때 활용된다.
그리고 수동방식은 config class와 같은 기술 지원 로직에 활용된다. 해당 방식은 context 내부에 등록된 Bean
을 적극 활용하기 때문에 application 전반에 영향을 미쳐야 하는 기술지원 로직에 적합하기 때문이다.
3. Bean 생성 과정
Bean
은 자동/수동방식 공통적으로 @ComponentScan
어노테이션에 의해 등록된다.
그리고 @ComponentScan
어노테이션은 @SpringBootApplication
어노테이션의 하위 어노테이션이기 때문에 일반적으로 루트 클래스에서 동작하게 된다.
@ComponentScan
어노테이션은 지정된 클래스부터 @Component
어노테이션이 붙은 하위 클래스를 스캔하기 시작한다.@Configuration
, @Controller
, @Service
, @Repository
어노테이션도 역시 포함이다.
그리고 스캔된 클래스들은 위 Bean의 Life Cycle
섹션에서 언급했던 것처럼 객체생성 -> 의존설정 -> 초기화
과정을 거쳐 Bean
으로 등록된다.
마무리
사실 고백하자면... 일주일이 넘도록 이 글 하나만 썼다. Bean
에 대해 파도파도 끝이 없어서 어디서부터 어떻게 써야할지 고민을 많이 했다.
결국 타협해서 Bean
개념에 대해 간단히 짚고 생성방식과 과정에 대해서만 작성했다.
관련하여 다룰 개념들이 아직도 많은데, 추후에는 프록시패턴, 데코레이터패턴 등과 같은 디자인 패턴들을 심도있게 다뤄볼까 한다.
또한, 위에서 언급했던 CGLIB
에 대해 다뤄봐도 좋을 것 같고,
시간이 좀 더 지나면 코드를 보면서 Spring Context
의 구조와 동작 방식도 파악해보면 좋을 것 같다.
'개발 > Spring Framework' 카테고리의 다른 글
[Spring] Spring Batch - File to DB 개발 (1) (0) | 2023.04.19 |
---|---|
[Spring] Spring Batch란? (0) | 2023.04.09 |
[Spring] Spring Security - 사전 학습 (0) | 2023.02.05 |
[Spring] Spring Framework란? - (2) Spring의 특징 (AOP) (0) | 2023.01.22 |
[Spring] Spring Framework란? - (2) Spring의 특징 (역사, IoC/DI) (0) | 2023.01.15 |