[Spring] 스프링 빈 등록하는 2가지 방법 (컴포넌트 스캔, 직접 등록) !!

728x90

 

1. 컴포넌트 스캔으로 스프링 빈 자동 등록

- @Component 가 붙은 클래스는 스프링 빈으로 자동 등록된다.

- `@Controller`, `@Service`, `@Repository`, `@Configuration` 은 @Component 를 포함하고 있어 자동 등록된다.

- 일반적으로 정형화된 컨트롤러, 서비스, 리포지토리 같은 코드에 사용

 

ComponentScan 을 통한 탐색 범위 지정

@ComponentScan(
  basePackages = "hello.core",
}

- basePackages : 탐색할 패키지의 시작 위치를 지정하면, 해당 패키지를 포함한 하위 패키지까지만 스캔된다.

(ex. "hello.core" 라면, hello.core 패키지를 포함한 하위 패키지만 스캔 (형제 패키지는 등록 X))

(@ComponentScan(basePackages = "패키지명")을 명시하면 스캔 범위를 조정할 수 있다.)

- basePackages 를 지정하지 않으면, 설정 클래스(@Configuration) 위치를 기준으로 스캔된다.

> 일반적으로 @SpringBootApplication (내부에 @ComponentScan 포함됨) 를 프로젝트 최상단 패키지에 위치시키는 것을 권장한다.

 

>참고<

- 스프링 빈은 싱글톤으로 하나만 등록하여 공유해서 사용한다.

- 애노테이션이 특정 애노테이션이 들고 있다고 인식하는 것은 스프링이 지원하는 기능(자바 지원 X)

 

컴포넌트 스캔 외 다른 부가 기능

`@Controller` : 스프링 MVC 컨트롤러로 인식

`@Repository` : 스프링 데이터 접근 계층으로 인식하고, 데이터 계층의 예외를 스프링 예외로 변환

`@Configuration` : 스프링 설정 정보로 인식, 스프링 빈이 싱글톤으로 유지하도록 추가 처리

`@Service` : 특별한 처리 X, 개발자들이 비즈니스 계층이라고 인식하도록 도움

 

필터

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyIncludeComponent {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyExcludeComponent {
}
@MyIncludeComponent
public class BeanA {
}

@MyExcludeComponent
public class BeanB {
}

이렇게 애노테이션을 만들고, 클래스 위에 애노테이션을 추가하면 된다

@MyIncludeComponent : 컴포넌트 스캔 대상에 추가할 클래스

@MyExcludeComponent : 컴포넌트 스캔 대상에서 제외할 클래스

@Configuration
@ComponentScan(
    includeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class),
    excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class)
)
class ComponentFilterAppConfig {
}

`includeFilters` : 컴포넌트 스캔 대상을 추가로 지정

`excludeFilters` : 컴포넌트 스캔에서 제외할 대상을 지정

그러면 BeanA 는 스프링 빈으로 등록되고, BeanB 는 스프링 빈으로 등록되지 않는다.

 

중복 등록과 충돌

자동 빈 등록 vs 자동 빈 등록 : `ConfilictingBeanDefinitionException` 예외 발생 (일어날 일 거의 X)

수동 빈 등록 vs 자동 빈 등록 : 수동 빈이 자동 빈을 오버라이딩 하며 아래와 같은 로그를 남긴다.

Overriding bean definition for bean 'memoryMemberRepository' with a different definition: replacing

 

Spring Boot 2.1 부터는 오버라이딩 하는 옵션의 기본값을 `false`로 바꾸었다.

application.properties 에서 다음 설정으로 허용 가능하다.

spring.main.allow-bean-definition-overriding=true

 

 

자동 주입

1. 생성자 주입 (가장 좋은 방법)

- 생성자를 통해 의존 관계를 주입하는 방법이다.

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

- 불변, 필수 의존관계에 사용한다.

- 프레임워크에 의존하지 않고 순수한 자바 언어의 특징을 가장 잘 살리는 방법이다.

 

@Autowired : 등록된 생성자로 스프링이 의존성 주입하여 연결해 준다.

이때, 스프링 빈으로 등록되어 스프링이 관리하는 객체에서만 동작한다.

 

> 참고 <

- @Autowired 는 생성자가 1개만 있으면 생략 가능하다.

- 생성자 주입을 제외한 나머지 방법은 생성자 이후에 호출되므로 `final` 키워드를 사용할 수 없다. 

@Controller
public class MemberController {

    private final MemberService memberService;

    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }
}

@Service
public class MemberService {

    private final MemberRepository memberRepository;

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

 

2. 수정자 주입 (setter 주입)

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

- 선택, 변경 가능성이 있는 의존관계에 사용한다.

- 자바 빈 프로퍼티 규약의 수정자 메서드 방식을 사용하는 방법이다.

 

- 객체가 불완전한 상태로 생성될 가능성이 있다.

 

> 참고 <

- 자바 빈 프로퍼티 규약이란 필드의 값을 직접 변경하지 않고 set / get 메서드를 통해 값을 읽거나 수정하는 규칙이다.

@Component
public class OrderServiceImpl implements OrderService {
  private MemberRepository memberRepository;
  private DiscountPolicy discountPolicy;

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

  @Autowired
  public void setDiscountPolicy(DiscountPolicy discountPolicy) {
    this.discountPolicy = discountPolicy;
  }
}

 

자동 주입 대상을 옵션으로 처리하는 방법

- `@Autowired(required = false)` :  자동 주입할 대상이 없으면 수정자 메서드 자체가 호출 X

- `org.springframework.lang.@Nullable` : 자동 주입할 대상이 없으면 null 이 입력

- `Optional<>` : 자동 주입할 대상이 없으면 Optional.empty 가 입력

 

>참고<

- @Nullable 은 생성자 자동 주입에서 특정 필드에만 사용해도 된다.

// 호출 안됨
@Autowired(required = false)
public void setNoBean1(Member member) {
}

// null 호출
@Autowired
public void setNoBean2(@Nullable Member member) {
}

// Optional.empty 호출
@Autowired(required = false)
public void setNoBean3(Optional<Member> member) {
}

 

3. 필드 주입 (추천 X)

- 필드에 바로 주입하는 방법이다.

- 테스트 코드에서 간편하게 사용할 수 있다.

- 스프링 설정을 목적으로 하는 @Configuration 같은 곳에서만 특별한 용도로 사용하기도 한다.

 

- 외부에서 변경이 불가능해서 테스트하기 힘들다.

- DI 프레임워크(Spring)가 없으면 사용할 수 없다.

@Component
public class OrderServiceImpl implements OrderService {
  
  @Autowired
  private MemberRepository memberRepository;

  @Autowired
  private DiscountPolicy discountPolicy;
}

 

4. 일반 메서드 주입

- 일반 메서드를 통해서 주입받을 수 있다.

- 한 번에 여러 개의 빈을 주입받을 수 있다.

- 동적 주입이 필요할 때 사용한다. (특정 조건에 따라 주입하는 경우)

- 일반적으로 사용하지 않는다.

@Component
public class OrderServiceImpl implements OrderService {

  private MemberRepository memberRepository;
  private DiscountPolicy discountPolicy;

  @Autowired
  public void init(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
    this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
  }
}

 

2. 자바 코드로 직접 스프링 빈 등록하기

-  @Service, @Repository 없이 직접 @Bean 을 사용하여 스프링 빈을 등록한다.

- 구현체 변경이 필요한 경우 유용하다.

- @Configuration을 붙인 설정 클래스(AppConfig)에서 @Bean을 선언하여 등록한다.

- 컴포넌트 스캔과 함께 혼용할 수 있다.

 

>참고<

- @Configuration 없이 @Bean만 사용해도 스프링 빈으로 등록되지만, 싱글톤을 보장하지 않는다

@Configuration
public class SpringConfig {

    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository());
    }
    
    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
}

 

 



 

 

 

 

출처 | 스프링 입문(김영한) - 인프런

출처 | 스프링 핵심 강의 - 기본편(김영한) - 인프런

728x90