본문 바로가기

스프링

스프링 컨테이너, 빈

스프링 컨테이너, 빈

스프링은 여러 가지 SOLID 원칙을 준수하기 위해 IoC, DI 등을 핵심으로 삼고 있다.

이를 위해 스프링 컨테이너가 존재한다.

스프링 컨테이너는 위의 목적을 달성하기 위해 자바 객체를 컨테이너에 스프링이 직접 관리하게된다.

관리하는 이 자바 객체를 빈이라고 하고 이름과 객체 한 묶음으로 함께 저장한다.

쉽게 이해하기 위해 그림으로 표현하면 다음과 같다.

[스프링 컨테이너]

이렇게 스프링은 스프링 컨테이너로 빈을 관리한다.

그렇다면 어떤식으로 실제로 관리하는지 살펴보자.

이를 쉽게 살펴보기 위해 다음과 같은 예제를 들고자 한다.

public interface EmployeeRepository {

    void save(Employee employee);

    Employee findById(Long employeeId);

    List<Employee> findAll();
}

public class EmployeeRepositoryImplement implements EmployeeRepository {

    private static Long sequence = 0L;
    private Map<Long, Employee> store = new HashMap<>();

    @Override
    public void save(Employee employee) {
        employee.setId(++sequence);
        store.put(employee.getId(), employee);
    }

    @Override
    public Employee findById(Long employeeId) {
        return store.get(employeeId);
    }

    @Override
    public List<Employee> findAll() {
        return new ArrayList<>(store.values());
    }
}

public interface EmployeeService {

    void join(Employee employee);

    Employee findEmployee(Long employeeId);

    List<Employee> findAllEmployee();
}

public class EmployeeServiceImplement implements EmployeeService {

    public final EmployeeRepository employeeRepository;

    public EmployeeServiceImplement(EmployeeRepository employeeRepository) {
        this.employeeRepository = employeeRepository;
    }

    @Override
    public void join(Employee employee) {
        employeeRepository.save(employee);
    }

    @Override
    public Employee findEmployee(Long employeeId) {
        return employeeRepository.findById(employeeId);
    }

    @Override
    public List<Employee> findAllEmployee() {
        return employeeRepository.findAll();
    }

    public EmployeeRepository getEmployeeRepository() {
        return employeeRepository;
    }
}

이번 예제에서는 이전과 같이 직원과 직원의 입사에 관한 로직를 토대로 설명하려고 한다.

EmployeeServiceImplement를 보면 생성자를 통해 EmployeeRepository를 주입받는 모습을 확인해줄 수 있다.

그렇다면 어떻게 스프링이 이를 주입시키는지 확인해보자.

@Configuration
public class Config {

    @Bean
    public EmployeeRepository employeeRepository() {
        return new EmployeeRepositoryImplement();
    }

    @Bean
    public EmployeeService employeeService() {
        return new EmployeeServiceImplement(employeeRepository());
    }

}

스프링에서는 다음과 같은 방식으로 빈을 등록해준다.

물론, 빈을 등록하는데 있어서 지금과 같이 수동으로 빈을 등록해주는 경우와 자동으로 빈을 등록해주는 경우가 있지만 이는 바로 다음에서 알아보도록 하고 일단 스프링 컨테이너에 빈이 등록되었다고 치자.

근데 여기서 의문점이 들 수 있다.

우리가 실제로 서비스를 진행할 때, 저장을 담당하는 RepositoryemployeeRepository()에서 한번 불리고 employeeService에서도 한번 호출이되니 총 두번 new 되어 두 개의 Repository 객체가 생성되는 것이 아닌가라는 의문이 들 수 있다.

하지만 그렇지 않다.

스프링에서는 컨테이너를 싱글톤으로 유지해준다.

여기서 말하는 싱글톤이란, 클래스의 객체를 단 하나만 만들어 사용하는 경우를 말한다.

이를 실제로 확인해보자.

public class Container_Bean {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);

    @Test
    public void Container_Bean() throws Exception {
        EmployeeRepository employeeRepository = ac.getBean("employeeRepository", EmployeeRepository.class);
        System.out.println("employeeRepository = " + employeeRepository);

        EmployeeServiceImplement employeeService = ac.getBean("employeeService", EmployeeServiceImplement.class);
        System.out.println("employeeService = " + employeeService);

        Assertions.assertThat(employeeRepository).isSameAs(employeeService.getEmployeeRepository());
    }

}

여기서, 빈 이름은 @Configuration설정에서 메소드 이름의 앞 글자를 소문자로 하고 나머지는 그대로 처리하는 방식으로 처리하기에 이름을 받았다.

또한 EmployeeServiceImplement를 직접 받는 것은 좋지 않고 추상객체인 EmployeeService를 받는 것이 좋으나 EmployeeRepository를 받기 위해 이렇게 받았다.

AnnotationConfigApplicationContextgetBean메소드를 통해 두 개의 빈을 불러 EmployeeServiceEmployeeRepository와 동일한지 확인해주는 것을 통해 싱글톤을 보장하는지 확인해보면 다음과 같은 결과를 얻을 수 있다.

다음과 같이 스프링 컨테이너는 싱글톤으로 해당 빈들을 관리해주는 것을 확인할 수 있었다.

우리가 이렇게 편리하게 관리를 받기 위해서는 빈을 등록하는 방법을 자세히 알아야될텐데 이 방법에 대해서는 바로 다음에 같이 살펴보도록 하겠다.

'스프링' 카테고리의 다른 글

MVC, Spring MVC  (0) 2021.09.18
빈 초기화 콜백, 소멸전 콜백  (0) 2021.09.16
같은 타입의 빈 등록(중복)  (0) 2021.09.16
수동 빈 등록, 자동 빈 등록  (2) 2021.09.15
스프링의 핵심  (0) 2021.09.14