💋 JavaConfig을 통한 설정
아래와 같은 테스트 코드를 통과하도록 하며 공부해보자!
class JavaConfigTest {
@Test
void javaConfig() {
ApplicationContext context = new AnnotationConfigApplicationContext(HelloApplication.class);
String[] beanDefinitionNames = context.getBeanDefinitionNames();
System.out.println(Arrays.toString(beanDefinitionNames));
AuthService authService = context.getBean(AuthService.class);
assertThat(authService).isNotNull();
}
}
- AnnotationConfigApplicationContext의 파라미터로 설정 정보(@ComponentScan 어노테이션)를 갖고 있는 클래스를 넘겨줌
- 요구사항
- ApplicationContext에 등록된 빈들의 이름을 차례대로 가져와서 출력해보자!
- AuthService 타입의 클래스의 빈이 ApplicationContext 안에 들어있도록, 빈을 등록해야 한다!
// TODO: Java-based Configuration을 하기 위한 클래스로 지정하기
@Configuration
public class AuthenticationPrincipalConfig {
// TODO: AuthService 빈을 등록하기
@Bean
public AuthService authService() {
return new AuthService();
}
// TODO: AuthenticationPrincipalArgumentResolver를 빈 등록하고 authService에 대한 의존성을 주입하기
@Bean
public AuthenticationPrincipalArgumentResolver authenticationPrincipalArgumentResolver() {
return new AuthenticationPrincipalArgumentResolver(authService());
}
}
- @Configuration 어노테이션 등록
- @Configuration 어노테이션을 사용하는 클래스는 @Component 어노테이션을 사용하는 클래스와 마찬가지로, @ComponentScan 어노테이션이 적용된 패키지나 하위 패키지에서 자동으로 스캔되어 스프링 빈으로 등록된다.
- 메서드 앞에 @Bean 어노테이션 붙이기
- @Configuration 어노테이션이 붙은 클래스는 스프링의 설정 정보를 담은 클래스로 인식된다.
- 이 클래스 내부에서 @Bean 어노테이션이 붙은 메서드는 스프링 빈으로 등록된다.
- 메서드 내부에서 빈으로 관리할 객체 생성 + 의존성 주입
- 의존성을 주입하기 위해서는, @Configuration 어노테이션을 사용하는 클래스 내부에서는 다른 빈을 참조하면 된다!
- 현재의 경우에는, authenticationPricipalArgumentResolver 내부에서 authService()를 호출하는 방식으로 다른 빈을 참조하고 있다!
- 이렇게 @Configuration 파일 내부에서 의존성을 정의해 놓으면, 스프링 컨테이너 내부에서 객체 생성 후 조립까지 완료하게 되는 것이다! 따라서, 앞으로 @Autowired 어노테이션을 이용하여 의존성을 주입할 수 있다.
- 의존성을 주입하기 위해서는, @Configuration 어노테이션을 사용하는 클래스 내부에서는 다른 빈을 참조하면 된다!
✔ 스프링 빈은 어떻게 싱글톤으로 관리되는 걸까?
위의 코드에서 authService()를 호출하게 되면 또 new AuthService()가 실행될 것 같다. 하지만 아래 코드를 테스트해보면,
파란색 칠해놓은 곳이 통과한다?!! 이말은 즉슨, AuthService가 싱글톤으로 관리되고 있다는 것인데???
스프링 컨테이너는 싱글톤 패턴을 적용하지 않아도, 객체 인스턴스를 싱글톤으로 관리한다.
그래서 싱글톤 컨테이너라고도 불리기도 한다!
스프링 빈은 싱글톤으로 관리된다.
스프링 컨테이너는 내부적으로 프록시 패턴을 사용하여 싱글톤 빈을 관리한다.
싱글톤 빈을 생성할 때, 스프링 컨테이너는 원본 빈을 직접 생성하는 대신 프록시 객체를 생성하고, 이 객체를 싱글톤으로 관리해서, 이후에 싱글톤 빈을 요청할 때마다 프록시 객체가 반환된다.
프록시 객체는 원본 빈을 감싸서 원본 빈의 메소드 호출을 대신 처리한다.
[개인적 궁금증] @SpringBootApplication이 뭐길래 설정 정보를 가지고 있음?
먼저 HelloApplication을 살펴보면,
@SpringBootApplication
public class HelloApplication {
public static void main(String[] args) {
SpringApplication.run(HelloApplication.class, args);
}
}
@SpringBootApplication 어노테이션이 있다.
내부에 들어가보면, @ComponentScan이 있다.
✔ @ComponentScan
@ComponentScan 어노테이션은 해당 클래스가 속한 패키지와 하위 패키지를 스캔하여 @Component, @Service, @Repository, @Controller 등의 어노테이션이 붙은 클래스를 스프링 빈으로 등록한다.
✔ @ComponentScan 탐색 위치 설정
하지만 점점 패키지가 커지다 보면... 이걸 다 확인하는데 오래 걸리게 된다. 따라서 꼭 필요한 위치부터 스캔할 수 있도록 시작 위치를 별도로 지정할 수 있다.
@ComponentScan(
basePackages = "hello.core"
}
여러 개 일 때는,
@ComponentScan(
basePackages = {"hello.core", "hello.service"}
}
패키지의 위치를 직접 지정하는 방법은, 패키지의 이름이 바뀌었을 때 발견하기 어려우므로 좋지 않다.
@SpringBootApplication을 통해서 보통 @ComponentScan의 설정 정보 클래스를 대체하므로, 이 클래스를 프로젝트 최상단에 두면 된다. (스프링에서 기본적으로 제공하는 위치이니깐 굳이굳이 안 바꾸면 됨!)
어노테이션에는 상속이 없다. 어노테이션이 다른 어노테이션을 가지고 있는 것을 인식할 수 있는 것은 자바 언어가 지원하는 기능이 아닌, 스프링이 지원하는 기능이다!