Web/Spring

[Spring] Bean 등록의 이점

스프링 Bean의 이점

저번 시간에는 스프링의 탄생 목적에 대해 알아보았습니다.

2022.04.28 - [Web/Spring] - [Spring] 스프링의 탄생목적 - SOLID, DI 컨테이너

 

저번시간의 내용을 짧게 정리해보겠습니다.

스프링은 좋은 객체지향 프로그래밍을 위해서 탄생했고, 좋은 객체지향 프로그래밍을 위해서는 의존성을 주입해주는 IoC 컨테이너가 필요했습니다.

스프링은 IoC컨테이너를 자동으로 생성해줌으로써 객체지향 프로그래밍을 도와줍니다.

 

하지만 단지 의존성 주입을 자동으로 해주는 것만으로 스프링을 사용하는 것은 아닌데요.

이번 시간에는 객체를 스프링 Bean으로 등록하면 어떤 이점이 있는지 알아보도록 하겠습니다.

 

 

의존성 주입

스프링이 어떻게 의존성 주입을 해줄까요?

스프링은 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 컴포넌트 스캔이라는 기능을 제공합니다.

 

 

@Component

@Repository
public class StationDaoImpl implements StationDao {
    ...
}

사용하려는 구현체에 @Component 를 붙여줍니다.

그러면 이제 MemberServiceImpl은 컴포넌트 스캔의 대상으로 등록됩니다.

 

 

@ComponentScan

Applicaion에 붙은 @SpringBootApplication안에 @ComponentScan이 있습니다.

@SpringBootApplication
public class SubwayApplication {

    public static void main(String[] args) {
        SpringApplication.run(SubwayApplication.class, args);
    }
}

 

@ComponentScan은 @Component가 붙은 모든 클래스를 스프링 빈으로 등록합니다.

이때 스프링 빈의 기본 이름은 클래스명을 사용하되 맨 앞글자만 소문자를 사용합니다.

 

@Repository
public class StationDaoImpl implements StationDao {
    ...
}

StationService를 사용하는 객체에서는 스프링 컨테이너를 통해 의존성을 자동으로 주입받습니다.

스프링은 컨테이너에서 인터페이스 StationDao의 구현체중 빈으로 등록된 객체를 찾아서 주입합니다.

기본적으로 StationDao의 타입중에서 빈으로 등록된 객체를 가져온다고 보면 됩니다.

 

@Service
public class StationService {

    private final StationDao stationDao;

    public StationService(StationDao stationDao) {
        this.stationDao = stationDao;
    }
}

 

 

싱글톤

웹 어플리케이션은 여러 고객이 동시에 요청할 수 있습니다.

그러면 고객이 요청할때마다 새로운 객체를 만들어야 할까요?

 

고객이 요청할때마다 새로운 객체를 만든다고 하면, 트래픽이 초당 100이 발생할때 초당 100개의 객체가 생성됩니다.

성능 저하로 이어질 수 있겠죠.

 

이 문제를 해결하려면 객체를 싱글톤으로 관리하면됩니다.

그래서 클라이언트 요청에 해당 객체가 약 1개만 생성되고, 공유하도록 설계하는 것이죠.

 

보통 싱글톤 패턴은 아래와 같이 작성합니다.

public class SingletonService {
	//1. static 영역에 객체를 딱 1개만 생성해둔다.
	private static final SingletonService instance = new SingletonService();

	//2. 생성자를 private으로 선언해서 외부에서 new 키워드를 사용한 객체 생성을 못하게 막는다. 
	private SingletonService() {}

	//3. public으로 열어서 객체 인스턴스가 필요하면 이 static 메서드를 통해서만 조회하도록 허용한다.
  public static SingletonService getInstance() {
      return instance;
	}
}

 

내가 직접 싱글톤을 만들 때 문제점

  1. 싱글톤 패턴을 구현하는 코드 자체를 추가적으로 직접 구현해야 한다.
  2. 클라이언트가 구체 클래스에 의존한다. → DIP, OCP 위반.
  3. 내부 속성을 변경하거나 초기화 어렵다.
    1. 클래스 로딩시점에 모든 것이 결정되기 때문에
  4. 생성자가 private이라서 자식 클래스를 만들기 어렵다.
  5. 유연성이 떨어진다. (의존성 주입을 못함)

 

스프링은 싱글톤의 모든 단점을 해결하며 객체를 싱글톤으로 유지한다.

@Service
public class StationService {

    private final StationDao stationDao;

    public StationService(StationDao stationDao) {
        this.stationDao = stationDao;
    }
}
  • 우리가 싱글톤을 직접 구현하지 않아도 된다.
  • DIP, OCP 위반을 하지않고 있다.

 

이와같이 스프링 빈으로 등록하면 내가 직접 손을 대지 않고도 싱글톤의 단점을 해결하며 싱글톤을 사용할 수 있습니다.

하지만 스프링에서 제공하는 싱글톤 객체를 사용할때에도 유의할 점이 있습니다.

 

 

싱글톤 방식 유의점

- 무상태로 설계해야 한다.

 

만약 빈으로 등록된 객체에 상태가 존재한다면,

여러번의 인스턴스 요청에도 같은 인스턴스를 반환하는 싱글톤의 특징때문에 여러곳에서 같은 상태에 접근할 수 있는 문제가 발생합니다.

@Service
public class StatefulService {
    private int price; //상태를 유지하는 필드
    
    public void order(String name, int price) { 
        System.out.println("name = " + name + " price = " + price);
        this.price = price; //여기가 문제!
    }
    
    public int getPrice() {
        return price;
    } 
}

사용자 요청이 동시에 2개가 들어왔고, 사용자 A는 10000원짜리를 주문했지만 20000원이 찍히는 문제가 발생합니다.

// ThreadA: A사용자 10000원 주문
statefulService1.order("userA", 10000);

// ThreadB: B사용자 20000원 주문
statefulService2.order("userB", 20000);

//ThreadA: 사용자A 주문 금액 조회
int price = statefulService1.getPrice();

//ThreadA: 사용자A는 10000원을 기대했지만, 기대와 다르게 20000원 출력
System.out.println("price = " + price);
assertThat(statefulService1.getPrice()).isEqualTo(20000);

싱글톤으로 관리되는 객체가 상태를 유지했기 때문에 발생한 문제입니다.

그래서 스프링이 빈으로 관리할 객체는 항상 무상태를 유지하도록 설계해야 합니다.

 

 

 

이외에도

빈 생명주기 관리

데이터베이스 커넥션 풀이나, 네트워크 소켓처럼 애플리케이션 시작 시점에 연결이 필요한 객체 또는

어플리케이션 종료시점에 안전하게 모두 종료해야하는 객체들을 관리해줍니다.