spring

스프링컨테이너, 스프링빈 등록, 싱글톤 패턴 보장

finite라이프 2024. 4. 7. 02:05

자바는 객체지향 프로그래밍 언어이다. 자바프로그램은 수많은 객체들이 상호작용되면서 실행된다. 스프링에서 이 객체들의 라이프사이클과 의존관리를 관리하는 하는 바구니를 스프링 컨테이너라고 한다.

 

스프링은 등록된 스프링빈(객체)을 가지고 실행되는데, 스프링빈을 등록하는 방법은

1. @Configuration어노테이션이 붙은 클래스에 직접 @Bean어노테이션으로 객체를 등록해주기

2. @ComponentScan을 이용해서 @Component어노테이션이 붙은 클래스들을 스프링빈으로 등록해주는 방법이 있다.

 

1번예시  @Bean을 통해 직접 mainController를 등록해주었다.

@Configuration
public class AppConfiguration {

@Bean
public MainController mainController() {
return new MainController();
}
}

 

2번예시  class위에 @Component를 넣어 @ComponentScan의 대상이 되게 하였다.

@Component
@RequiredArgsConstructor
public class UserService {

private final ItemRepository itemRepository;

public ItemRepository getItemRepository() {
return itemRepository;
}
}

 

@ComponentScan의 대상은 @SpringBootApplication이 위치한 곳의 하위폴더에 위치한 클래스들이다.

@SpringBootApplication안에 실제로 @ComponentScan이 있는 것을 확인할 수 있다.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

 

등록된 스프링 빈들은 스프링 컨테이너에 의해 다른 빈 객체에 주입되기도 한다.

 

@ComponentScan을 통해 등록된 객체(빈)들은 오른쪽 그림처럼 의존관계가 주입되는데 이때 @AutoWired를 쓸 수도 있고, 

생성자 주입을 쓸 수도 있다.

 

스프링은 기본적으로 싱글톤 패턴을 지원하는데, 싱글톤 패턴은 클래스의 객체가 딱 1개만 생성되는 것을 보장하는 디자인 패턴이다.

한 클래스의 객체a가 서로다른 두 클래스의 객체에 의존관계가 됐을때, 이 두객체안에 있는 객체a는 같은주소를 가진 동일한 객체일까?

@Component
@RequiredArgsConstructor
public class UserService {

private final ItemRepository itemRepository;

public ItemRepository getItemRepository() {
return itemRepository;
}
}
@Service
@Getter
@RequiredArgsConstructor
public class ItemService {

private final ItemRepository itemRepository;

public ItemRepository getItemRepository() {
return itemRepository;
}
}

이 두개의 서비스에서  itemRepository객체를 필드로 가지고 있다.

@SpringBootTest
public class InstanceTest {
@Autowired
private UserService userService;
@Autowired
private ItemService itemService;

@Test
@DisplayName("userService itemRepository itemService itemRepository는 같나")
public void test1() {
assertThat(userService.getItemRepository()).isEqualTo(itemService.getItemRepository());
}
}

테스트를 통해 확인한 결과 두 객체는 동일한 객체이다.

스프링이 없는 순수 자바로 테스트를 해보자. 위의 코드와 아래코드는 다르게 표현된 같은 코드이다.단 아래는 스프링을 안썼기에 직접 대입을 해준 것이다.

public class InstanceTest {

private UserService userService = new UserService(new ItemRepository());

private ItemService itemService = new ItemService(new ItemRepository());

@Test
@DisplayName("userService itemRepository itemService itemRepository는 같나")
public void test1() {
assertThat(userService.getItemRepository()).isEqualTo(itemService.getItemRepository());
}
}

 

이런 에러가 나타난다. 두 객체는 다른 객체이다.

스프링이 객체들을 스프링빈으로 등록하여 관리할때는 이런 결과가 나오지 않았다.

 

스프링은 스프링빈의 객체가 딱 1개만 생성되는 것을 보장하기 위해 CGLIB라는 기술을 사용한다.

class AppConfigurationTest {

@Test
void configurationDeep() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfiguration.class);
AppConfiguration bean = ac.getBean(AppConfiguration.class);
System.out.println("bean.getClass() = " + bean.getClass());
}
}

이 테스트를 실행시켜 보면 

bean.getClass() = class springselfStudy.study.configuration.AppConfiguration$$SpringCGLIB$$0

이런 결과가 나오는 것을 볼 수 있다. 우리가 작성한 Appconfiguration에 $$SpringCGLIB$$0이 붙었다. 이것은 스프링이 CGLIB라는 바이트 코드 조작 라이브러리를 사용해서 내가 만든 클래스를 상속받은 어떤 클래스를 만들고, 그 클래스를 스프링 빈으로 등록한 것이다. 이 클래스가 스프링빈의 객체가 딱 1개만 생성되는 것을 보장해준다.

 

 

 

 

 

 

 

김영한님의 스프링강의 https://www.inflearn.com/course/스프링-핵심-원리-기본편/dashboard 참고