스프링 컨테이너와 빈

Jaca·2021년 8월 6일
0

Container

Container는 인스턴스의 생성과 소멸을 관리하며, 생성된 인스턴스들에게 추가적인 기능을 제공 하는 것이다.

Container는 개발자가 작성한 코드의 처리과정을 위임받은 독립적인 존재라고 생각하면 된다.

Container는 적절한 설정만 되어있다면 누구의 도움 없이도 작성한 코드를 스스로 참조한 뒤 알아서 객체의 생성과 소멸을 컨트롤해준다.

Spring에서 객체 생성 및 연결의 책임이 Container로 인계된다.

이때 Spring Container에서 관리하는 객체를 Bean이라고 한다

BeanFactory와 ApplicationContext

우리가 Container를 생성하게되면 ApplicationContext를 이용하게 된다.
Spring Container의 최상위 인터페이스인 BeanFactory 인터페이스도 존재한다.

BeanFactory 인터페이스는 스프링 빈을 관리하고 조회 하는 역할을 담당하며, 가장 유용한 getBean() 메소드를 제공한다.
이 외에는 많은 기능을 제공하지만, ApplicationContextBeanFactory 인터페이스를 상속받아, 모든 메소드를 사용할 수 있으면서 더많은 부가기능을 제공한다.

Bean

이렇게 컨테이너를 만들면, 컨테이너 내에 저장되고 스프링이 관리하는 정보를 빈이라고 한다.

BeanSpring Container가 관리하는 자바 객체를 말한다.

Spring Contatiner에 의해 인스턴스화, 관리, 생성된다.

Bean Container는 의존성 주입을 통해 Bean 객체를 사용할 수 있도록 해준다.

Spring에서 Bean은 Singleton 패턴으로 생성되어 관리된다.

Configuration

그러면 Container에 어떻게 Bean을 등록할 수 있을까?

1. @Configuration

먼저 Java class에서 @Configuration Annotation을 사용해서 직접 @Bean을 등록해주는 방법이다.

@Configuration // 설정정보를 저장하는..
public class AppConfig {

    @Bean
    public MemberService memberService() {
        System.out.println("call AppConfig.memberService");
        return new MemberServiceImpl(getMemberRepository());
    }

    @Bean
    public MemberRepository getMemberRepository() {
        System.out.println("call AppConfig.getMemberRepository");
        return new MemoryMemberRepository();
    }

    @Bean
    public OrderService orderService() {
        System.out.println("call AppConfig.orderService");
        return new OrderServiceImpl(getMemberRepository(), getDiscountPolicy());
    }

    @Bean
    public DiscountPolicy getDiscountPolicy() {
        return new RateDiscountPolicy();
    }
}

위와 같이 @Configuration를 통해 이 클래스가 설정 정보임을 알려주고 사용 할 객체에 대해 @Bean Annotation을 붙여 준다.

같은 객체에 대한 생성자가 여러개 있어도 Spring에서 Singleton 패턴으로 관리 해준다.

2. XML

XML 파일에 직접 Bean을 등록하여 Application의 Bean을 설정하는 방법이다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id = "memberService" class = "hello.core.member.MemberServiceImpl">
        <constructor-arg name = "memberRepository" ref = "memberRepository" />
    </bean>

    <bean id = "memberRepository" class = "hello.core.member.MemoryMemberRepository" />

    <bean id = "orderService" class = "hello.core.order.OrderServiceImpl">
        <constructor-arg name = "memberRepository" ref = "memberRepository" />
        <constructor-arg name = "discountPolicy" ref = "discountPolicy" />
    </bean>

    <bean id = "discountPolicy" class = "hello.core.discount.RateDiscountPolicy" />


</beans>

위와 같이하면 AppConfig.java 클래스와 같은 동작을 한다.
이 파일을 resources 하위의 xml파일을 별도로 생성해야한다.

XML의 속성

  • class(필수) : 정규화된 자바 class 이름
  • id : bean의 고유 식별자
  • scope : 객체의 범위 (sigleton, prototype 등)
  • constructor-arg : 생성 시 생성자에 전달할 인수
  • property : 생성 시 bean setter에 전달할 인수
  • init-method, destroy-method

ComponentScan

위의 Configuration의 방식을 수동이라고 한다면 ComponentScan은 자동이라고 이해하면 쉽다.

만약 등록 해야할 Bean 수십개정도 된다면 슬슬 누락, 실수의 가능성을 무시할 수 가 없다.
그래서 설정 정보가 없어도 자동으로 Bean을 등록해주는 기능을 제공하며,
의존 관계도 자동으로 주입해주는 @Autowired 기능도 제공한다.

  • basePackages : 탐색할 패키지의 시작 위치를 지정한다. 이 패키지를 포함해서 하위 패키지를 모두 탐색한다.
    basePackages = {"hello.core", "hello.service"} 이렇게 여러 시작 위치를 지정할 수도 있다.
  • basePackageClasses : 지정한 클래스의 패키지를 탐색 시작 위치로 지정한다.
    만약 지정하지 않으면 @ComponentScan 이 붙은 설정 정보 클래스의 패키지가 시작 위치가 된다.
  • excludeFilter : 스캔 대상에서 제외할 조건을 명시한다.

AutoAppConfig 클래스는 비어있지만, 현재 클래스의 위치부터 탐색하며 @Component가 붙은 클래스를 찾아 Bean으로 등록한다.
위 코드에서는 hello.core 부터 탐색을 시작한다.

이전에 수동 AppConfig.java에서는 @Bean 으로 직접 설정 정보를 작성했고, 의존관계도 직접 명시했다. 이제는 이런 설정 정보 자체가 없기 때문에, @Autowired 를 통해 의존관계 주입도 이 클래스 안에서 해결해야 한다.

  @Component
  public class MemberServiceImpl implements MemberService {
      private final MemberRepository memberRepository;

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

ComponentScan의 동작 과정

ComponentScan 은 @Component 가 붙은 모든 클래스를 스프링 빈으로 등록한다. 이때 스프링 빈의 기본 이름은 클래스명을 사용하되 맨 앞글자만 소문자를 사용한다.

  • 빈 이름 기본 전략: MemberServiceImpl 클래스 memberServiceImpl
  • 빈 이름 직접 지정: 만약 스프링 빈의 이름을 직접 지정하고 싶으면 @Component("memberService2") 이런식으로 이름을 부여하면 된다.

Autowired의 의존관계 자동 주입

생성자에 @Autowired 를 지정하면, 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입한다. 이때 기본 조회 전략은 타입이 같은 빈을 찾아서 주입한다.
getBean(MemberRepository.class) 와 동일하다고 이해하면 된다.
생성자에 파라미터가 많아도 다 찾아서 자동으로 주입한다.

ComponentScan의 스캔 대상

@ComponentScan은 기본적으로 @Component가 붙은 모든 클래스를 등록한다.

하지만 이 외에 스캔하는 대상이 좀 더 있다.

  • @Controlller : 스프링 MVC 컨트롤러에서 사용
  • @Service : 스프링 비즈니스 로직에서 사용
  • @Repository : 스프링 데이터 접근 계층에서 사용
  • @Configuration : 스프링 설정 정보에서 사용

해당 클래스의 소스 코드를 보면 @Component 를 포함하고 있는 것을 알 수 있다.

위 어노테이션들은 스캔 대상일 뿐만 아니라 부가 기능을 수행한다.

  • @Controller : 스프링 MVC 컨트롤러로 인식
  • @Repository : 스프링 데이터 접근 계층으로 인식하고, 데이터 계층의 예외를 스프링 예외로 변환해준다.
  • @Configuration : 스프링 설정 정보로 인식하고, 스프링 빈이 싱글톤을 유지하도록 추가 처리를 한다.
  • @Service : @Service 는 특별한 처리를 하지 않는다. 대신 개발자들이 핵심 비즈니스 로직임을 예상할 수 있다.

중복 등록과 충돌

컴포넌트 스캔에서 같은 빈 이름을 등록하면 어떻게 될까?

  1. 자동빈등록vs자동빈등록
  2. 수동빈등록vs자동빈등록

자동 빈 등록 vs 자동 빈 등록

컴포넌트 스캔에 의해 자동으로 스프링 빈이 등록되는데, 그 이름이 같은 경우 스프링은 오류를 발생시킨다.
ConflictingBeanDefinitionException 예외 발생

수동 빈 등록 vs 자동 빈 등록

이 경우 수동 빈 등록이 우선권을 가진다. (수동 빈이 자동 빈을 오버라이딩 해버린다.)
하지만 의도한 상황이 아닌 빈 오버라이딩을 예상하지 못한 채로 충돌이 일어나게되면 정말 잡기 어려운 버그가 만들어진다.
이를 막기 위해 최근 스프링 부트에서는 수동 빈 등록과 자동 빈 등록이 충돌나면 오류가 발생하도록 기본 값을 바꾸 었다.
필요하다면 resources의 초기 설정에서 spring.main.allow-bean-definition-overriding=true 명령어를 통해 조절할 수 있다.

profile
I am me

0개의 댓글