Spring Boot Bean 등록 - Spring Boot Bean deunglog

빈 객체를 등록하는 방법을  설명하기에 앞서 빈(Bean)이 무엇인지 알아보겠습니다.

[ 스프링에서의 빈이란? ]

우선 스프링은 경량 컨테이너로서 객체 생성, 소멸과 같은 Life Cycle을 관리하며 스프링 컨테이너로부터 필요한 객체를 얻을 수 있다. 

스프링 컨테이너에 의해서 자바 객체가 만들어지게 되면 이 객체를 스프링은 스프링 빈(Bean)이라고 부른다.

스프링 빈과 자바 일반 객체와의 차이점은 없고 스프링 컨테이너에 의해 만들어진 객체를 스프링 빈이라고 부를 뿐이다.

Spring Boot Bean 등록 - Spring Boot Bean deunglog
스프링 레퍼런스 메뉴얼에 나와 있는 부분(출처 - https://endorphin0710.tistory.com/93)

[ 스프링 빈의 어노테이션 종류 ]

스프링부트의 경우 @Component, @Service, @Controller, @Repository, @Bean, @Configuration 등으로 필요한 빈들을 등록하고 필요한 곳에서 @Autowired를 통해 주입받아 사용하는 것이 일반적이다.

다음 그림과 같이 @Service, @Controller, @Repository는 모두 @Component를 상속받고 있으며 해당 어노테이션으로 등록된 클래스들은 스프링 컨테이너에 의해 자동으로 생성되어 스프링 빈으로 등록된다.

Spring Boot Bean 등록 - Spring Boot Bean deunglog
출처: dinfree/blog/스프링프레임워크

[ @Component & @Autowired ]

스프링부트에서 사용자 클래스를 스프링 빈으로 등록하는 가장 쉬운 방법은 클래스 선언부 위에 Component Annotation을 사용하는 것이다.  @Component가 붙은 클래스는 스프링 빈 객체로 등록이 되어 객체 생성/삭제를 스프링에서 관리할 수 있다.

이해하기 쉽도록 예시를 하나 들어보자.

다음과 같이 Weapon 인터페이스와 AK47 클래스, AK47_Black 클래스를 만들자.

두 개의 클래스는 모두 Weapon 인터페이스를 implements한 상태이다.

package com.example.review1;

public interface Weapon {

    public void fire();
    
    public void reload();
}
package com.example.review1;

import org.springframework.stereotype.Component;

@Component
public class AK47 implements Weapon {

    @Override
    public void fire() {
        System.out.println("탕 X 10");
    }

    @Override
    public void reload() {
        System.out.println("10발 장전 완료!");
    }

}
package com.example.review1;

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

@Component
public class AK47_Black implements Weapon {

    @Override
    public void fire() {
        System.out.println("탕 X 20");
    }

    @Override
    public void reload() {
        System.out.println("20발 장전 완료!");
    }

}
  • Weapon 인터페이스는 기본 무기를 정의
  • AK47 클래스는 10발을 쏘고 10발을 장전 
  • AK47_Black 클래스는 20발을 쏘고 20발을 장전

오토와이어링은 자동으로 스프링 빈 객체를 특정 참조변수에 매핑해주는 것을 뜻하며 @Autowired라는 어노테이션을 사용한다.

 오토와이어링 예제 실행을 위해 스프링 부트의 메인 어플리케이션인 Review7WeekApplication.java 파일을 다음과 같이 CommandLineRunner 인터페이스를 구현하는 것으로 작성한다.(파일명이 Review7 어쩌고인 이유는 복습용으로 포스팅 중이라 그렇습니다.)

CommandLineRunner는 스프링 부트 어플리케이션이 시작되고 Command Line 인자를 받아 실행되는 코드를 구현하기 위해 사용한다. 반드시 run() 메서드를 오버라이딩 해야한다.

package com.example.review1;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Review7WeekApplication implements CommandLineRunner {

    @Autowired
    Weapon weapon;
	
    public static void main(String[] args) {
        SpringApplication.run(Review7WeekApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        weapon.fire();
    }
}
  • @Autowired를 사용해 AK47, AK47_Black 인스턴스를 스프링 컨테이너로부터 받아온다.
  • run() 메서드에서 weapon.fire() 메서드 호출한다.

위처럼 소스코드를 작성하고 실행을 하면 다음과 같은 에러가 난다.

Spring Boot Bean 등록 - Spring Boot Bean deunglog

Field weapon in ... 부분을 해석해보면 '한 개의 빈만 필요로 하는데 두 개가 발견돼서 어떤 것을 필요로 하는지 나는 모르겠는데?' 라는 의미같다.

사실 스프링은 싱글톤이기 때문에 AK47과 AK47_Black 인스턴스를 동시에 불러올 수 없다. 그래서 어떤 것을 선택해야할지 지정을 해줘야 하는데 지정을 하는 방법은 두 가지가 있다. 조금 있다가 살펴보자.


[ @Bean & @Configuration ]

스프링 빈을 생성하는 또 다른 방법으로는 자바 설정 클래스를 이용하는 것이다. 초기 스프링 기반 개발에서 빈 생성은 xml로 된 스프링 설정파일을 통해 이루어졌으며 지금 자바 클래스에서 관련 설정을  대신하는 방법을 주로 사용한다. 물론 필요에 따라 xml 설정은 아직도 사용이 가능하다.

설정 클래스는 @Configuration 어노테이션을 클래스 선언부 앞에 추가를 하면 된다. 또한 특정 타입을 리턴하는 메서드를 만들고 @Bean 어노테이션을 붙여주면 자동으로 해당 타입의 빈 객체가 생성된다.

@Bean 어노테이션의 주요 내용은 다음과 같다.

  • @Configuration 설정된 클래스의 메서드에서 사용가능.
  • 메서드의 리턴 객체가 스프링 빈 객체임을 선언함.
  • 빈의 이름은 기본적으로 메서드 이름이 됨.
  • @Bean(name=”name”) 으로 이름 변경 가능.
  • @Scope 을 통해 객체 생성을 조정할 수 있음.
  • @Component 애너테이션을 통해 @Configuration 없이도 빈 객체를 생성할수도 있음.
  • 빈 객체에 init(), destroy() 등 라이프사이클 메서드를 추가한 다음 @Bean 에서 지정할 수 있음.

이해를 돕기 위해 예시를 들어보겠습니다.

package com.example.review1;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ConfigurationExample {

    @Bean
    public Weapon superAK47() {
        AK47 ak47 = new AK47();
        return ak47;
    }

    @Bean
    public Weapon superAK47_Black() {
        AK47_Black ak47_black = new AK47_Black();
        return ak47_black;
    }
}
  • 새로운 ak47객체와 ak47_black 객체를 스프링 Bean으로 생성

위와 같이 소스코드를 작성하고 다시 실행을 시키면 위와 비슷한 에러가 난다.

Spring Boot Bean 등록 - Spring Boot Bean deunglog

이번에는 객체를 한 개만 필요로 하는데 4개를 찾았으니 난 모른다. 라는 에러를 내고 있다. 

에러를 없애는 방법을 살펴보자.


[ @Primary & @Qualifier ]

  • @Primary: @Bean 혹은 @Component 에 함께 사용하며 객체 생성의 우선권을 부여.
  • @Qualifier: @Autowired에 함께 사용하며 Bean 의 이름이 같은 객체를 찾음.

Bean에 이름을 지정하는 방법은 다음과 같다.

1) 이름을 명시하지 않는경우

  • @Component: 소문자로 시작하는 클래스이름이 자동으로 사용됨.
  • @Bean: 소문자로 시작하는 메서드이름이 자동으로 사용됨.

2) 이름을 명시하는 경우

  • @Component: @Component(“이름”)과 같이 사용.
  • @Bean: @Bean(name=”이름”)과 같이 사용.

오토와이어링시 이름 사용 @Autowired 에서 특정 이름의 Bean 을 가지고 오려면 @Qualifier 어노테이션을 사용해야 한다.

여기서는 @Bean에 이름을 부여하고 찾는 것으로 다음과 같이 소스코드를 수정한다.

// ConfigurationExample.java
@Bean(name="ak47")
...

@Bean(name="ak47_b")
...


// Review7WeekApplication.java
@Autowired
@Qualifier("ak47_b")
Weapon weapon
...

위의 소스코드를 실행하면 콘솔창에 "탕 X 20"이 올바르게 출력이 된다.

@Qualifier말고도 @Primary를 사용해도 객체 생성의 우선권이 부여된다.

// ConfigurationExample.java
@Bean(name="ak47")
@Primary
...

@Bean(name="ak47_b")
...


// Review7WeekApplication.java
@Autowired
Weapon weapon
...

위의 소스코드를 실행하면 이번에는 "탕 X 10"이 올바르게 출력이 된다.

@Primary 어노테이션은 자바 설정 클래스에서도 사용이 가능하고 스프링 어노테이션(@Component) 밑에 붙여서도 사용이 가능하다.

그렇다면 만약에 @Qualifier와 @Primary 중에 어떤 것에 객체 생성의 우선권이 부여될까?

// ConfigurationExample.java
@Bean(name="ak47")
@Primary
...

@Bean(name="ak47_b")
...


// Review7WeekApplication.java
@Autowired
@Qualifier("ak47_b")
Weapon weapon
...
Spring Boot Bean 등록 - Spring Boot Bean deunglog
"탕 X 10" 출력

위의 소스코드를 실행하면 "탕 X 20"이 출력되는 것을 볼 수 있다.

이로써 @Qualifier가 @Primary보다 강력하다는 것을 알 수 있다.

- gbridge 수업 중 황희정 교수님 블로그(dinfree.com) 참고