728x90
반응형

spring

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에 의해 생성되고 관리되는 객체이다.

IoCDI에 대한 개념은 👉여기에서 확인

Life Cycle

BeanSpring Container에 의해 생성되고 관리되기 때문에 Java객체인 Bean의 생명주기(Life Cycle)도
객체생성 -> 의존설정 -> 초기화 -> 사용 -> 소멸
형태로 Spring Container에 의해 관리된다.

Bean 사용 이유

Bean은 위와 같은 Life Cycle로 Spring Container에 의해 관리된다.
그래서 해당 application으로 요청이 올 때마다 객체를 생성하는 것이 아닌,
Spring Container에 의해 초기화 과정에서 생성된 Bean객체를 활용하기 때문에 성능상 이점이 있다.

또한, 의존성 관리와 java 객체의 life cycle관리에도 용이하게 때문에 Bean을 활용한다.

Bean의 생성

Bean은 다음과 같이 두 가지 방법으로 생성될 수 있다.

  1. @Component어노테이션을 활용한 자동등록 방식
  2. @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의 구조와 동작 방식도 파악해보면 좋을 것 같다.

반응형

+ Recent posts