[Section 2] Container, Component Scan, 의존 관계

Kim·2022년 10월 14일
0

Boot Camp

목록 보기
30/64
post-thumbnail

Java 기반 Container 설정

@Bean & @Configuration

@Bean@Configuration은 자바 기반 설정의 가장 중요한 애너테이션이다.
메서드가 Spring 컨테이너에서 관리할 새로운 객체를 인스턴스화 시키고, 구성 및 초기화한다는 것을 나타내는 데 사용한다.

@Configuration
public class DependencyConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

AnnotationConfigApplicationContext 로 스프링 컨테이너를 인스턴스화 하는 방법

AnnotationConfigApplicationContext 구현은 아래와 같은 애너테이션이 달린 클래스로 피라미터를 전달받고 있다.

  • @Configuration 클래스
  • @Component 클래스
  • JSR-330 메타데이터

@Configuration 클래스가 입력으로 제공되면, @Configuration 클래스 자체가 Bean 정의로 등록되고 클래스 내에서 선언된 모든 @Bean 메서드도 Bean 정의로 등록된다.

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(DependencyConfig.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

@Component 클래스와 JSR-330 클래스가 제공되면, Bean 정의로 등록되며 필요한 경우 해당 클래스 내에서 @Autowired 또는 @Inject와 같은 DI 메타데이터가 사용되는 것으로 가정한다.

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

@Component 또는 JSR-330 주석이 달린 클래스는 위와 같이 생성자에 입력으로 사용한다.

@Bean 애너테이션 사용하기

@Bean은 메서드 레벨 애너테이션이다. <bean />에서 제공하는 일부 속성을 지원한다.
@Bean 애너테이션은 @Configuration-annoted 또는 @Component-annoted 클래스에서 사용할 수 있다.

Bean 선언하기

@Bean 애너테이션을 메서드에 추가해서 Bean으로 정의(선언)할 수 있다.

@Configuration
public class DependencyConfig {

    @Bean
    public TransferServiceImpl transferService() {
        return new TransferServiceImpl();
    }
}

Bean 정의가 있는 인터페이스를 구현해 bean configuration을 설정할 수 있다.

public interface BaseConfig {

    @Bean
    default TransferServiceImpl transferService() {
        return new TransferServiceImpl();
    }
}

@Configuration
public class DependencyConfig implements BaseConfig {

}

Bean 의존성

@Bean 애너테이션이 추가된(@Bean-annotated) 메서드는 Bean을 구축하는데 필요한 의존성을 나타내는데 매개 변수를 사용할 수 있다.

@Configuration
public class DependencyConfig {

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }
}

@Bean 애너테이션 사용

@Configuration 애너테이션을 사용하기

@Configuration는 해당 객체가 bean definitions의 소스임을 나타내는 애너테이션이다. @Bean-annoted 메서드를 통해 bean을 선언한다.
@Configuration 클래스의 @Bean 메서드에 대한 호출을 사용하여 bean 사이의 의존성을 정의할 수도 있다.

Bean 사이에 의존성 주입하기

Bean이 서로 의존성을 가질 때, 의존성을 표현하는 것은 다른 bean 메서드를 호출하는 것과 같이 간단하다.

@Configuration
public class DependencyConfig {

    @Bean
    public BeanOne beanOne() {
        return new BeanOne(beanTwo());
    }

    @Bean
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}

beanOne은 생성자 주입을 통해 beenTwo에 대한 참조를 받는다.


Component Scan

스프링은 설정 정보 없이 자동으로 스프링 빈을 등록하는 컴포넌트 스캔이란 기능을 제공한다.
@ComponentScan@Component가 붙은 모든 클래스를 스프링 빈으로 등록해주기 때문에 설정 정보에 붙여주면 된다. 의존관계도 자동으로 주입하는 @Autowired 기능도 제공한다.

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

@Configuration
@ComponentScan
public class AutoDependencyConfig {

}

컴포넌트 스캔 기본 대상

  • @Component : 컴포넌트 스캔에서 사용된다.

  • @Controller & @RestController : 스프링 MVC 및 REST 전용 컨트롤러에서 사용된다.

  • @Service : 스프링 비즈니스 로직에서 사용된다.
    특별한 처리를 하지 않으며, 개발자들이 핵심 비즈니스 로직이 여기에 있다는비즈니스 계층을 인식하는데 도움이 된다.

  • @Repository : 스프링 데이터 접근 계층에서 사용된다.
    스프링 데이터 접근 계층으로 인식하고, 데이터 계층의 예외를 스프링 예외로 변환해준다.

  • @Configuration : 스프링 설정 정보에서 사용된다.
    스프링 설정 정보로 인식하고, 스프링 빈이 싱글톤을 유지하도록 추가 처리를 한다.

Filter

  • includeFilters : 컴포넌트 스캔 대상을 추가로 지정한다.

  • excludeFilters : 컴포넌트 스캔에서 제외할 대상을 지정한다.

  • FilterType 옵션
    ANNOTATION: 기본값, 애너테이션으로 인식해서 동작한다.
    ASSIGNABLE_TYPE: 지정한 타입과 자식 타입을 인식해서 동작한다.
    ASPECTJ: AspectJ 패턴을 사용한다.
    REGEX: 정규 표현식을 나타낸다.
    CUSTOM: TypeFilter라는 인터페이스를 구현해서 처리한다.


다양한 의존 관계 주입 방법

생성자 주입

생성자를 통해 의존 관계를 주입받는 방법이다.
생성자에 @Autowired를 하면 스프링 컨테이너에 @Component로 등록된 bean에서 생성자에 필요한 bean들을 주입한다.

특징

  • 생성자 호출 시점에 딱 1번만 호출되는 것이 보장된다.

  • 불변과 필수 의존 관계에 사용된다.

  • 생성자가 1개만 존재하는 경우에는 @Autowired를 생략해도 자동 주입된다.

  • NullPointerException 을 방지할 수 있다.

  • 주입받을 필드를 final로 선언 가능하다.

@Component
public class CoffeeService {
  private final MemberRepository memberRepository;
  private final CoffeeRepository coffeeRepository;

  @Autowired
  public OrderServiceImpl(MemberRepository memberRepository, CoffeeRepository coffeeRepository) {
    this.memberRepository = memberRepository;
    this.coffeeRepository = coffeeRepository;
  }
}

수정자 주입 (setter)

setter라 불리는 필드의 값을 변경하는 수정자 메서드를 통해 의존 관계를 주입하는 방법이다.

특징

  • 선택과 변경 가능성이 있는 의존 관계에 사용된다.

  • 자바 bean 프로퍼티 규약의 수정자 메서드 방식을 사용한다.

@Component
public class CoffeeService {
  private final MemberRepository memberRepository;
  private final CoffeeRepository coffeeRepository;

  @Autowired
  public void setMemberRepository(MemberRepository memberRepository) {
    this.memberRepository = memberRepository;
  }

  @Autowired
  public void setCoffeeRepository(CoffeeRepository coffeeRepository) {
    this.coffeeRepository = coffeeRepository;
  }
}
  • 생성자 주입과의 차이점은 생성자 대신 set 필드명 메서드를 생성해 의존 관계를 주입한다.

  • 수정자 경우 @Autowired를 입력하지 않으면 실행되지 않는다.
    @Component가 실행하는 클래스를 스프링 빈으로 등록한다.
    스프링 빈으로 등록하고 의존 관계를 주입하는데, @Autowired에 있는 것들을 자동으로 주입한다.

생성자는 1개일 때, @Autowired가 없어도 작동되는 이유는 무엇일까?

스프링이 해당 클래스 객체를 생성해 bean에 넣어야하는데, 생성할 때 생성자를 부를 수 밖에 없다. 그러므로 bean을 등록하면서 의존 관계 주입도 같이 발생하게 된다.

필드 주입

필드에 @Autowired를 붙여서 바로 주입하는 방법이다.

특징

  • 코드가 간결해서 예전에 많이 사용된 방식이지만, 외부에서 변경이 불가능하여 테스트하기 힘들다는 단점이 있다.

  • DI 프레임워크가 없으면 아무것도 할 수 없다.

  • 실제 코드와 상관 없는 특정 테스트를 하고 싶을 때 사용할 수 있다.

  • 정상적으로 작동되게 하려면 결국 setter가 필요하므로 수정자 주입을 사용하는게 더 편리하다.

@Component
public class CoffeeService {
  @Autowired
  private final MemberRepository memberRepository;
  @Autowired
  private final CoffeeRepository coffeeRepository;
}

일반 메서드 주입

일반 메서드를 사용해 주입하는 방법이다.

특징

  • 한 번에 여러 필드를 주입받을 수 있다.
  • 일반적으로 사용되지 않는다.

옵션 처리

주입할 스프링 빈이 없을 때 동작해야하는 경우가 있다.
`@Autowire만 사용하는 경우 required 옵션 기본값인 true가 사용되어 자동 주입 대상이 없으면 오류가 발생하는 경우가 있다.

자동 주입 대상 옵션 처리 방법

  • Autowired(required=false) : 자동 주입할 대상이 없으면 수정자 메서드 자체가 호출되지 않게 된다.
  • org.springframework.lang.@Nullable : 자동 주입할 대상이 없으면 null이 입력된다.
  • Optional<> : 자동 주입할 대상이 없으면 Optional.empty가 입력된다.

생성자 주입을 사용해야 하는 이유

  • 불변
    의존 관계 주입은 처음 애플리케이션이 실행될 때 대부분 정해지고 종료 전까지 변경되지 않고 변경되면 안 된다.
    수정자 주입 같은 경우에는 이름 메서드를 public으로 열어두어 변경이 가능하기 때문에 적합하지 않다.
    생성자 주입은 객체를 생성할 때 최초로 1번만 호출되고 그 이후에는 다시는 호출되는 일이 없기 때문에 불변하게 설계할 수 있다.

  • 누락
    호출했을 때는 NPE(Null Point Exception)이 발생하는데 의존관계 주입이 누락되었기 때문에 발생한다.
    생성자 주입을 사용하면 주입 데이터 누락 시 컴파일 오류가 발생한다.

  • final 키워드 사용 가능
    생성자 주입을 사용하면 필드에 final 키워드를 사용할 수 있다.
    생성자에서 값이 설정되지 않으면 컴파일 시점에서 오류를 확인할 수 있다.
    java: variable (데이터 이름) might not have been initialized
    생성자 주입을 제외한 나머지 주입 방식은 생성자 이후에 호출되는 형태이므로 final 키워드를 사용할 수 없다.

  • 순환 참조
    순환 참조를 방지할 수 있다.
    개발하다보면 여러 컴포넌트 간에 의존성이 생기게 된다. (A → B를 참조하고, B → A를 참조)
    필드 주입과 수정자 주입은 bean이 생성된 후에 참조를 하기 때문에 애플리케이션이 어떠한 오류와 경고 없이 구동됩니다.
    실제 코드가 호출될 때까지 문제를 알 수 없다.
    생성자를 통해 주입하게되면 BeanCurrentlyInCreationException이 발생하게 된다.

0개의 댓글