[Spring Boot] Spring Bean

2023. 1. 6. 23:09기술 창고/Spring

728x90
SMALL

Bean이라는 것은 무엇을 말하는 것일까?

Spring 프로젝트를 진행하면서 기본적인 개념이라고 알려져있고, 검색만 해도 많은 분들께서 자세하게 알려주시지만 나는 잘 이해가 되지 않았다.

Spring Bean이란 무엇이고, 생성 주기까지 한번 정리해볼 필요가 있을 것 같다.

 


Spring Bean

Bean이란 스프링 IoC 컨테이너가 관리하는 자바 객체를 빈(bean)이라고 한다.

우리가 new 연산자로 어떤 객체를 생성했을 때 그 객체는 빈이 아니다.

ApplicationContext.getBean()으로 얻어질 수 있는 객체는 빈이다.

즉 Spring에서의 빈은 ApplicationContext가 알고있는 객체, 즉 ApplicationContext가 만들어서 그 안에 담고있는 객체를 의미한다.

 

 

Bean 등록 방법

1. Component Scan

@ComponentScan 어노테이션과 @Component 어노테이션을 사용해서 빈을 등록하도록 하는 방법이다.

간단히 말하면 @ComponentScan 어노테이션은 어느 지점부터 컴포넌트를 찾으라고 알려주는 역할을 하고 @Component는 실제로 찾아서 빈으로 등록할 클래스를 의미한다.

 

Spring IoC 컨테이너가 IoC 컨테이너를 만들고 그 안에 빈을 등록할때 사용하는 인터페이스들을 라이프 사이클 콜백이라고 부른다.

라이프 사이클 콜백 중에는 @Component 어노테이션을 찾아서 이 어노테이션이 붙어있는 모든 클래스의 인스턴스를 생성해 빈으로 등록하는 작업을 수행하는 어노테이션 프로세서가 등록되어 있다고 한다.

 

Spring Boot 프로젝트에서 @ComponentScan 어노테이션이 붙어있는 클래스가 이에 해당한다.

Spring 프로젝트를 진행하게 되면 application 실행 클래스에 @SpringBootApplication 어노테이션이 붙어있는 것을 볼 수 있는데

Ctrl + 마우슨 왼쪽 클릭해서 진입을 해보면,

위와 같이 @ComponentScan 어노테이션이 포함이 되어있는 것을 확인할 수 있다.

 

 @ComponentScan 어노테이션은 어디서부터 컴포넌트를 찾아볼 것인지 알려주는 역할을 한다.

@ComponentScan이 붙어있는 클래스가 있는 패키지에서부터 모든 하위 패키지의 모든 클래스를 훑어보며 @Component 어노테이션(또는 @Component 어노테이션을 사용하는 다른 어노테이션)이 붙은 클래스를 찾는다.

Spring이 IoC 컨테이너를 만들때 위와 같은 과정을 거쳐 빈으로 등록해주는 것이다.

 

따라서,

# 만든 프로젝트로 예시

FinalprojectApplication 밑의 하위 패키지들 중에서 @Component 가 적용된 객체들은 전부 Bean으로 등록해준다.

 

Controller 패키지에 있는 Controller 객체를 예로 확인해보자.

위와 같이 Controller 객체에는 컨트롤러임을 명시하는 @Controller 어노테이션이 붙어있다.

이 @Controller에 진입을 해보면,

이렇듯 @Component 어노테이션이 붙어있는 것을 알 수 있다.

따라서, @Controller로 붙은 클래스의 객체들을 Bean으로 등록이 되는 것이다.

 

+ @Component 포함 어노테이션 : @Controller, @Service, @Repository ... etc

 

 

 

2. Bean 설정파일에 직접 Bean 등록

Bean 설정파일은 XML과 자바 설정파일로 작성할 수 있는데 최근 추세는 자바 설정파일을 좀 더 많이 사용한다고 한다.

자바 설정파일은 자바 클래스를 생성해서 작성할 수 있으며 일반적으로 xxxxConfiguration와 같이 명명한다.

그리고 클래스에 @Configuration 어노테이션을 붙인다.

그 안에 @Bean 어노테이션을 사용해 직접 빈을 정의한다.

JPAQueryFactory가 IoC 컨테이너 안에서 Bean으로 등록된다.

이렇게 빈을 직접 정의해서 등록하면 @Component 어노테이션을 붙이지 않아도 된다.

 

@Configuration 어노테이션을 보면 이 어노테이션도 @Component를 사용하기 때문에 @ComponentScan의 스캔 대상이 되고 그에 따라 빈 설정파일이 읽힐때 그 안에 정의한 빈들이 IoC 컨테이너에 등록되는 것이다.

 

 

 

Spring Bean LifeCycle (빈 생명주기 사이클)

Bean으로 등록된 객체들을 사용하려면 앞서 초기화를 진행해야 하는데, 생성자 주입을 하든, 수정자 주입을 하든, 필드 주입을 하든 의존성이 주입되기 전에 아무 시점에나 초기화를 진행하면 에러가 발생한다.

따라서, 의존성 주입이 완료된 후에 초기화가 진행되어야 한다.

 

[Spring Bean LifeCycle]

(1) Spring IoC 컨테이너 생성 -> 
(2) Spring Bean 생성(등록) -> 
(3) 의존성 주입(생성자 주입 권장) -> 
(4) 초기화 콜백 -> 
(5) 사용 -> 
(6) 소멸 전 콜백 -> 
(7) Spring 종료

의존성 주입이 완료되고 나면 콜백 메소드를 통해 초기화 시점을 알려주는 초기화 콜백을 사용한다.

또한 스프링이 종료되기 이전에 소멸 콜백을 사용하여 소멸 시점을 알려준다.

 

Spring Bean LifeCycle을 압축시키기 위해 의존성 주입, 즉 권장되는 의존성 주입 방법인 생성자 주입을 통해 Bean 생성/등록 및 초기화를 동시에 수행하게 하면 좋지 않을까? 하는 생각이 들 수도 있을 것이다.

 

생성자는 파라미터를 받고, 메모리를 할당해서 객체를 생성하는 역할을 하고,

초기화는 생성자로 인해 생성된 값들을 통해 외부 자원과 연결하거나 말 그대로 초기 세팅과 같은 무거운 동작을 수행한다.

그래서 동시에 생성과 초기화를 수행하는 것보다는 두 부분은 현재처럼 나누어서 진행하는 것이 유지보수 측면에서도 좋다.

 

 

Bean LifeCycle CallBack 3가지

빈 라이프 사이클을 초기화 콜백과 소멸 콜백을 사용하여 관리하는 3가지 방법이 있다.

 

1. 인터페이스( InitializingBean, DisposableBean )

public class ExampleBean implements InitializingBean, DisposableBean {
 
    @Override
    public void afterPropertiesSet() throws Exception {
        // 초기화 콜백 (의존관계 주입이 끝나면 호출)
    }
 
    @Override
    public void destroy() throws Exception {
        // 소멸 전 콜백 (메모리 반납, 연결 종료와 같은 과정)
    }
}
  • InitalizingBean은 afterPropertiesSet() 메소드로 초기화를 지원한다. (의존관계 주입이 끝난 후에 초기화 진행)
  • DisposableBean은 destory() 메소드로 소멸을 지원한다. (Bean 종료 전에 마무리 작업, 예를 들면 자원 해제(close() 등))

 

이 방식의 단점

  • InitalizingBean, DisposableBean 인터페이스는 스프링 전용 인터페이스이다. 해당 코드가 인터페이스에 의존한다.
  • 초기화, 소멸 메소드를 오버라이드 하기 때문에 메소드명을 변경할 수 없다.
  • 코드를 커스터마이징 할 수 없는 외부 라이브러리에 적용 불가능하다.

인터페이스를 사용하는 초기화 및 종료 방법은 스프링 초창기에 나온 방법들이며, 지금은 거의 사용하지 않는다.

 

 

2. 설정 정보에서 초기화 메소드, 종료 메소드 지정

public class ExampleBean {
 
    public void initialize() throws Exception {
        // 초기화 콜백 (의존관계 주입이 끝나면 호출)
    }
 
    public void close() throws Exception {
        // 소멸 전 콜백 (메모리 반납, 연결 종료와 같은 과정)
    }
}
 
@Configuration
class LifeCycleConfig {
 
    @Bean(initMethod = "initialize", destroyMethod = "close")
    public ExampleBean exampleBean() {
        // 생략
    }
}

이 방식의 장점

  • 메소드명을 자유롭게 부여 가능하다.
  • 스프링 코드에 의존하지 않는다.
  • 설정 정보를 사용하기 때문에 코드를 커스터마이징 할 수 없는 외부라이브러리에서도 적용 가능하다.

 

이 방식의 단점

  • Bean 지정시 initMethod와 destoryMethod를 직접 지정해야 하기에 번거롭다.

@Bean의 destoryMethod 속성의 특징

 

라이브러리는 대부분 종료 메소드명이 close 혹은 shutdown이다.

@Bean의 destoryMethod는 기본값이 inferred(추론)으로 등록 즉, close, shutdown이라는 이름의 메소드가 종료 메소드라고 추론하고 자동으로 호출해준다. 즉, 종료 메소드를 따로 부여하지 않더라도 잘 작동한다.

추론 기능을 사용하기 싫다면 명시적으로 destroyMethod=""으로 지정해줘야 한다.

 

 

 

3. @PostConstruct, @PreDestory 어노테이션

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
 
public class ExampleBean {
 
    @PostConstruct
    public void initialize() throws Exception {
        // 초기화 콜백 (의존관계 주입이 끝나면 호출)
    }
 
    @PreDestroy
    public void close() throws Exception {
        // 소멸 전 콜백 (메모리 반납, 연결 종료와 같은 과정)
    }
}

이 방식의 장점

  • 최신 스프링에서 가장 권장하는 방법이다.
  • 어노테이션 하나만 붙이면 되므로 매우 편리하다.
  • 패키지가 javax.annotation.xxx 이다. 스프링에 종속적인 기술이 아닌 JSR-250이라는 자바 표준이다. 따라서 스프링이 아닌 다른 컨테이너에서도 동작한다.
  • 컴포넌트 스캔과 잘어울린다.

 

이 방식의 단점

  • 커스터마이징이 불가능한 외부 라이브러리에서 적용이 불가능하다.
    • 외부 라이브러리에서 초기화, 종료를 해야 할 경우 두 번째 방법 즉, @Bean의 initMethod와 destoryMethod 속성을 사용하자.
728x90
반응형
LIST