spring / / 2022. 12. 23. 07:31

스프링 부트 빈(Bean) 어노테이션

스프링 부트 빈 어노테이션

https://javatechonline.com/spring-boot-bean-annotations-with-examples/

위의 문서를 번역한 내용이다.

빈 프로퍼티 어노테이션

@Lazy @Profile @Scope @Order @DependsOn @Primary @Conditional

빈 주입 어노테이션

@Autowired @Resource @Inject @Qualifier @Primary vs @Qualifier

빈 상태 어노테이션

@PostConstruct @PreDestroy

자바 빈은 무엇인가?

자바 빈은 클래스 혹은 애플리케이션 간에 데이터를 교환하도록 하는 자바 헬퍼 클래스이다. 일반적으로 메인 클래스는 아니지만 두 클래스 간에 전송역할을 한다. 어떤 로직도 포함하지 않는다.

자바 빈 클래스를 개발하는 몇 가지 표준 가이드가 있다:

  1. 우리는 자바 빈을 public 클래스로 선언해야 한다.
  2. java.io.Serializable(네트워크를 통해 데이터를 쉽게 직렬화하는데 사용되는)을 구현하도록 권고한다. 네트워크로 데이터를 전송할 필요가 없다면 선택적으로 사용할 수 있다.
  3. 모든 멤버 변수(빈 프로퍼티)는 private & non-static이어야 한다 (만일 20개의 employees를 전송한다면, 20개의 값이 전송될 것이다. 만일 static 인 하나의 값만 전송될 것이라면, non-static으로 선언하는 것이 자바 빈의 장점을 얻기 위한 필수사항이 된다)
  4. 또한 모든 빈 프로퍼티는 하나의 setter와 하나의 getter 메소드를 가져야 한다.

스프링 빈 혹은 컴포넌트는 무엇인가?

애플리케이션이 시작되는 동안, 스프링은 객체를 초기화하고 그것을 애플리케이션 컨텍스트에 추가한다. 애플리케이션 컨텍스트에서 이 객체를 '스프링 빈' 혹은 '스프링 컴포넌트'라고 부른다. 그들은 스프링에서 관리되기 때문에 우리는 스프링 관리 빈(Spring-managed Bean) 혹은 스프링 관리 컴포넌트(Spring-managed Component)라고 부른다.

스프링/스프링 부트에서 @Bean 어노테이션은 무엇인가?

여러분이 스프링의 xml 구성을 기억하고 있다면, XML <bean/> 구성 요소와 유사하다. 이것은 스프링 빈을 만들고 일반적으로 @Configuration과 함께 사용된다. 우리는 메소드 수준에서 *@Bean을 사용한다. 앞서 언급한 @Configuration이 있는 클래스(구성 클래스라고 부른다)는 객체를 초기화하는 메소드를 가지고 의존 관계를 구성한다. 그런 메소드는 *@Bean 어노테이션을 가지게 될 것이다. 기본적으로, 빈 이름은 메소드 이름과 동일하다. 초기화한 다음 실제 빈을 리턴한다. 그 어노테이션이 있는 메소드는 스프링 IoC 컨테이너에서 관리되는 빈을 만들게 된다.

@Configuration
public class AppConfig {
  @Bean
  public Employee employee() {
    return new Employee();
  }
  @Bean
  public Address address() {
    return new Address();
  }
}

비교를 위해서, 위의 구성은 다음 스프링 XML 방식과 동일한 방식이다.

<beans>
    <bean name="employee" class="com.dev.Employee" />
      <bean name="address" class="com.dev.Address" />
</beans>

어노테이션은 init-method, destroy-method, autowiring, lazy-init, dependency-check, depends-on과 scope과 같은 <bean/>으로 제공되는 대부분의 속성을 제공한다.

빈(Bean) 프로퍼티 어노테이션

@Lazy

기본적으로, 스프링은 애플리케이션 컨텍스트가 시작될 때 모든 싱글톤 빈을 만든다. 하지만, 어떤 경우에는 빈을 만들 때, 애플리케이션 시작할 때가 아닌 요청할 때 만들 필요가 있다. 그런 경우에 @Lazy*를 적용한다. 우리는 *@Configuration 클래스에 @Lazy 어노테이션을 넣고, @Bean 어노테이션이 늦게(lazily) 로딩되어야 하는 것을 의미한다. 게다가, 이것은 XML 기반의 구성인 default-lazy-init="true"와 동일하다. @Lazy 어노테이션이 없는 빈은 초기에 초기화된다. 예를 들면 아래 코드가 @Lazy 어노테이션의 사용을 나타낸다.

@Configuration
public class AppConfig {
  @Lazy
  @Bean
  public Employee getEmployee() {
    return new Employee();
  }
}

위의 예제에 나타난 것처럼, 특정 빈의 느린 로딩을 위해서, 특정 메소드에 @Bean 어노테이션과 함께 @Lazy 어노테이션을 사용해라.

@Profile

@Profile을 알아보기 전에, 스프링 부트의 프로파일(profile) 개념을 이해해 보자. 스프링 프로파일은 애플리케이션의 구성을 구분할 수 있는 방법을 제공하고 특정 환경에서만 동작할 수 있게 한다. 개발, 테스트, UAT, 운영 등 프로젝트 개발 시 다양한 환경이 있다. 사실, 호스트명, 포트 등 특정 환경에만 사용하는 특정 구성정보를 가지고 있다. 게다가, 특정 환경에서만 초기화가 필요한 빈(bean)도 있다. 이제 특정 프로파일에 빈(bean)을 어떻게 생성할 수 있는지 알아보자.

우리는 빈을 특정 프로파일에 매핑하고 있는 것을 나타내는 @Profile 어노테이션을 사용한다. 이것은 정의된 프로파일에서만 활성화 되어 있는 경우에만 빈이 초기화가 된다는 것을 나타낸다. 어노테이션은 단순히 하나 혹은 여러 개의 프로파일 이름을 가진다. 예를 들어, 개발 시에만 활성화되고 운영시에서 비활성화되는 빈을 가지고 있다고 생각해보자. 우리는 개발 프로파일을 가진 빈에 어노테이션을 붙이면 해당 빈은 개발 시에만 컨테이너에 존재하게 될 것이다. 하지만 운영 시에는 해당 빈은 활성화가 되지 않을 것이다.

@Component
@Profile("development")
public class MyConfigClass {}

반면에, 프로파일에서 제외하기 위해서 NOT 연산자를 사용하여 예) !development을 프로파일 이름 앞에 붙일 수 있다, 예를 들어, 개발 프로파일이 비활성화 된다면 아래 컴포넌트가 초기화 될 수 있다.

@Component
@Profile("!development")
public class MyConfigClass {}

@Scope

빈의 스코프(scope)는 사용되는 컨텍스트에서의 생애 주기(life cycle)와 빈의 가시성(visibility)을 나타낸다. 스프링 프레임워크는 최신 버전 기준으로 6가지 유형의 스코프를 정의한다.

singleton
prototype
request
session
application
websocket

앞서 얘기한 6가지 스코프에서 4개는 web ApplicationContext에서 사용할 수 있다. 하지만 싱글톤(singleton)과 프로토타입(prototype) 스코프는 IoC 컨테이너 유형에서 가능하다. 빈의 스코프를 선언하기 위해서 @Scope 어노테이션을 사용한다. 스코프가 명시되지 않는다면 싱글톤(singleton) 스코프가 기본 값이다. 예를 들면, 프로토타입(prototype) 스코프를 선언하기 위해서, 아래와 같이 @Scope를 사용할 것이다.

@Scope("prototype")
@Bean
public Student studentPrototype() {
  return new Student();
}

@DependsOn

하나의 빈이 다른 빈에 의존을 가지고 있을 때 @DependsOn 어노테이션을 사용한다. 또한, 다른 빈 이전에 빈을 초기화할 필요가 있을 때, @DependsOn*을 사용한다. 빈을 생성할 때 *@DependsOn 속성 값을 의존 빈으로서 정의할 필요가 있다. 게다가, @DependsOn 속성은 명시적으로 하나 이상의 빈이 현재 빈이 초기화되기 전에 초기화될 수 있도록 한다. 예를 들어, BeanA와 BeanB가 있고 BeanA와 BeanB에 의존을 가지고 있는 BeanC가 있다고 해보자. 코드는 아래와 같을 것이다.

@Component
public class BeanA {}

@Component
public class BeanB {}

@Component
@DependsOn(value = {"beanA", "beanB"})
public class BeanC {}

@Order

@Order 어노테이션은 어노테이션이 있는 컴포넌트 혹은 빈의 순서를 정의한다.

컴포넌트의 실행 순서를 정의하는 'value' 인수를 가지고 있다. 하지만 기본 값은 Orders.LOWEST_PRECEDENCE 이다. 이 값은 컴포넌트가 모든 순서가 있는 컴포넌트에서 가장 낮은 우선순위가 있다는 것을 나타낸다. 비슷하게 Ordered.HIGHEST_PRECEDENCE 값은 컴포넌트 사이에서 가장 높은 우선 순위를 가지고 있다는 것을 나타낸다. 만일 @Order 값에서 순위 값을 준다면, 낮은 숫자일수록 가장 높은 우선순위를 가진다. 1을 가진 어노테이션은 2를 가진 어노테이션 이전에 실행될 것이다. 음수(negative)로 설정할 수도 있다. 작은 값의 순서는 항상 우선순위가 있다. 만일 컴포넌트/빈이 @Order가 없다면, 기본적으로 빈 이름의 알파벳 순으로 실행될 것이다. 게다가, 동일한 값을 가진 두 개의 컴포넌트는 컴포넌트의 알파벳 순이 우선된다.

Note: 실행 순서는 아래와 같다.

음수의 값을 가진 컴포넌트가 첫 번째
양수의 값을 가진 컴포넌트가 그 다음
순서 값이 없는 알파벳 순의 컴포넌트

예를 들어 아래 코드를 보자.

@Component
@Order(-1)
public class A {}

@Component
@Order(5)
public class D {}

@Component
@Order(-24)
public class C {}

@Component
@Order(5)
public class B {}

@Component
public class F {}

@Component
public class E {}

위 컴포넌트에서 실행순서는: C, A, B, D, E, F가 될 것이다.

@Primary

같은 타입의 여러 빈을 가지고 있을 때, 특정 빈에 우선순위를 부여하기 위해 @Primary를 사용한다. 예를 들어, Employee 타입의 PermanentEmployee와 ContractEmployee의 두 개의 빈을 가지고 있다고 해보자. 여기서 PermanentEmployee에 우선순위를 주고 싶다. 우선순위를 부여하기 위해 @Primary를 적용하는 방법을 아래 코드로 확인해보자.

@Component
public class ContractEmployee implements Employee {}

@Component
@Primary
public class PermanentEmployee implements Employee {}

@Service
public class EmployeeService {
  @Autowired
  private Employee employee;
  public Employee getEmployee() {
    return employee;
  }
}

말할 필요도 없이, 위의 EmployeeService 클래스에서 PermanentEmployee가 autowire로 주입될 것이다.

Note: PermanentEmployee 컴포넌트에서 @Primary*를 사용하지 않고 애플리케이션을 시작한다면, 스프링은 NoUniqueBeanDefinitionException을 던진다. 하지만, 동일한 타입의 빈을 접근하기 위해서 일반적으로 *@Qualifier("beanName") 어노테이션을 사용한다. 우리의 경우에, 구성 단계에 빈을 선택했기 때문에 여기서는 @Qualifier 어노테이션을 사용할 수 없다.

@Conditional

@Conditional*은 컨포넌트가 특정 조건과 일치할 경우에만 등록이 가능한지를 나타낸다. *@Configuration 클래스가 @Conditional*로 표시되어 있다면, 모든 *@Bean 메소드, @Import 어노테이션, 클래스의 @ComponentScan 어노테이션은 그 조건에 따른다. 간단히 말하면, 어노테이션이 있는 빈은 모든 조건이 충족되었을 때만 생성된다. 애플리케이션 컨텍스트에서 빈을 적용할 수 있도록 사전 정의되거나 상황에 맞는 조건을 사용하도록 하는 새로운 어노테이션인 @Conditional을 스프링 4.0에서 도입했다. 예를 들어, @ConditionalOnJava의 사전 정의된 조건을 한번 보자.

@Bean
@ConditionalOnJava(value = JavaVersion.NINE)
public JavaBean getJavaBean() {
  return new JavaBean();
}

위의 JavaBean은 자바 버전 9인 경우에만 로딩될 것이다. 이제 스프링 부트 프로젝트에서 가장 인기있고 가장 많이 사용되는 사전 정의된 어노테이션인 @ConditionalOnProperty를 한번 알아 보자. 이것은 특정 환경 프로퍼티에 따라 조건적으로 빈을 로딩하게 한다.

@Configuration
@ConditionalOnProperty(
  value="module.enabled",
  havingValue = "true",
  matchIfMissing = true
)
class MyPaymentModule {
  ...
}

MyPaymentModule은 오직 module.enabled 프로퍼티가 true인 경우에만 로딩된다. 만일 프로퍼티가 설정되지 않으면 matchIfMissing이 true이기 때문에 여전히 로딩될 것이다. 이런 식으로 기본적으로 로딩되는 모듈을 만든다.

  • @ConditionalOnProperty
  • @ConditionalOnExpression
  • @ConditionalOnBean
  • @ConditionalOnMissingBean
  • @ConditionalOnResource

위에서 나타낸 조건이 있는 어노테이션은 스프링 부트 애플리케이션에서 사용하는 가장 흔한 형태이다. 스프링 부트는 더 많은 조건이 있는 어노테이션을 제공한다. 하지만 일반적이지 않고 애플리케이션 개발보다 오히려 프레임워크를 개발할 때 더 선호된다. 스프링 부트 프레임워크는 의도적으로 몇 가지를 사용한다.

  • @ConditionalOnClass
  • @ConditionalOnMissingClass
  • @ConditionalOnJndi
  • @ConditionalOnJava
  • @ConditionalOnSignleCandidate
  • @ConditionalOnWebApplication
  • @ConditionalOnNotWebApplication
  • @ConditionalOnCloudPlatform

빈 주입 어노테이션

@Autowired, @Resource, @Inject

이 어노테이션들은 의존성 주입이다. 이것들은 의존성을 처리하기 위해 클래스에 선언적인 방법으로 사용한다. @Resource와 *@Inject는 자바 표현식 패키지인 javax.annotation.Resource와 javax.inject.Inject에 속한다. 하지만 *@Autowired 어노테이션은 org.springramework.beans.factory.annotation 패키지에 포함되어 있다. @Autowired는 스프링 프레임워크의 일부이기 때문에 개발자인 우리는 프로젝트에서 사용하는 것을 더 선호한다. 이런 경우에 의존성 주입은 스프링 프레임워크에서 처리된다. 이 어노테이션 각각은 필드 주입, 생성자 주입 혹은 setter 주입으로 의존성을 처리할 수 있다.

@Autowired

@Autowired는 객체 의존을 암묵적으로 처리하고 주입하기 쉽게 한다. 내부적으로 setter 혹은 생성자 주입을 사용한다. 원시값이나 string값에 @Autowired를 사용할 수 없다. 오직 참조값에서만 동작한다. 우리는 프로퍼티 혹은 필드, setter, 생성자에서 @Autowired를 사용할 수 있다. 프로퍼티에서 @Autowired를 어떻게 사용하는지 보자. PaymentService 빈을 정의하자.

@Service
public class PaymentService {
  public void doPayment() { ... }
}

그리고 나서, 아래와 같이 필드 정의에 @Autowired를 사용하여 PaymentController 빈을 빈을 주입할 것이다.

@Controller
public class PaymentController {
  @Autowired
  private PaymentService service;

  public void getPayment() {
    service.doPayment();
  }
}

여기에서, 스프링 컨테이너가 PaymenttController를 생성할 때 PaymentService를 주입한다.

@Autowired 실행 우선 순위

이 어노테이션은 아래와 같이 실행 우선 순위가 있다.

유형(Type)이 일치할 때 우선
그 다음 Qualifier가 일치할 때
그 다음 이름(Name)이 일치할 때

@Resource 실행 우선 순위

@Resource는 아래와 같이 실행 우선 순위가 있다.

이름(Name)이 일치할 때 우선
그 다음 유형(Type)이 일치할 때
그 다음 Qualifier가 일치할 때

@Inject 실행 우선 순위

이 어노테이션은 @Autowired 어노테이션과 동일한 실행 우선 순위를 가진다.

유형(Type)이 일치할 때 우선
그 다음 Qualifier가 일치할 때
그 다음 이름(Name)이 일치할 때
게다가, @Inject 어노테이션에 접근하기 위해서, javax.inject 라이브러리가 Gradle 혹은 Maven 의존에 선언되어야 한다.

@Qualifier

@Qualifier 어노테이션은 빈 의존을 해결할 때 @Autowired 어노테이션에 추가적인 정보를 제공한다. 컨테이너에 동일한 타입의 빈이 하나 이상 있을 때 어떤 빈을 사용할 지 명시적으로 컨테이너에게 알려줄 필요가 있다. 그렇지 않으면 프레임워크는 autowiring이 가능한 하나 이상의 빈이 있다는 것을 나타내는 NoUniqueBeanDefinitionException을 던질 것이다. 예를 들어, 필요한 빈을 나타내기 위해 @Qualifier 어노테이션을 사용하는 방법을 알아보자. 아래 코드를 보자.

public interface PaymentService {}

@Service
public class CardPaymentService implements PaymentService {}

@Service
public class CashPaymentService implements PaymentService {}

@Controller
public class PaymentController {
  @Autowired
  @Qualified("cardPaymentService")
  private PaymentService service;

  public void getPayment() {
    service.doPayment();
  }
}

여기에서, 특정 구현체(cardPaymentService)의 이름과 함께 @Qualifier 어노테이션을 포함함으로써 스프링 컨테이너가 동일한 타입의 두 개의 빈을 찾는 모호함을 제거한다.

@Primary vs @Qualifier

모호한 상황에서 주입할 빈을 결정할 필요가 있을 때 @Primary 어노테이션을 또한 사용할 수 있다. @Qualifier*와 유사한 이 어노테이션은 동일한 타입이 존재하는 여러 빈이 있을 때 우선순위를 결정한다. 그런 경우에 *@Primary 어노테이션이 있는 빈이 따로 명시되지 않는다면 사용될 것이다. 하지만 이 어노테이션은 기본적으로 주입될 특정 빈을 명시하고자 할 때 유용하다. 주로 기본값의 의미로는 @Primary를 사용하고 반면, *@Qualifier는 특정 경우에 사용한다. @Qualifier와 *@Primary 어노테이션이 둘다 존재하는 경우에 @Qualifier 어노테이션이 우선 사용된다. 이 경우에 클래스 구현체 중 하나에 @Primary 어노테이션을 위치해야만 한다.

@Primary vs @Qualifier는 또한 인터뷰 관점에서 중요한 포인트이다. 질문자는 빈 주입할 때 모호함의 개념을 설명할 때 그들 사이의 차이점을 물을 것이다.

빈 상태 어노테이션

@PostConstruct

스프링은 @PostConstruct를 가진 메소드를 빈 프로퍼티 초기화 이후에 한번만 호출한다. 어노테이션이 있는 메소드는 초기화를 수행하기 위해 의존성 주입이 완료된 이후 실행된다. @PostConstruct를 가진 메소드는 static을 제외한 어떤 접근 수준도 가질 수 있다.

@PreDestory

스프링은 @PreDestroy를 가진 메소드를 스프링이 애플리케이션 컨텍스트 에서 빈을 제거하기 전에 한번 실행된다. 어노테이션 메소드는 빈이 제거되기 전에 실행된다. 예) 종료. @PostConstruct와 유사하게, @PostDestory를 가진 메소드는 static을 제외하고 어떤 접근 수준을 가질 수 있다.

public class MyTestClass {
  @PostConstruct 
  public void postConstruct() throws Exception {
    System.out.println("Init method after properties are set");
  }
  @PreDestroy
  public void preDestory() throws Exception {
    System.out.println("Spring Container is destroyed! clean up");
  }
}

@PostConstruct와 @PreDestory 어노테이션은 스프링에 포함되지 않는 것을 기억하자. 그들은 JEE 라이브러리의 일부이며 common-annotations.jar에 있다. 자바 EE가 자바 9에서 deprecated되었고 자바 11에서 제거되기 때문에 이 어노테이션을 사용하기 위해 추가적인 의존이 필요하다. 여러분이 자바 8 혹은 이전 버전을 사용하고 있다면 추가적인 의존은 필요 없다.

반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유