기존에는 개발자가 직접 자바코드로 모든 것을 했다면

이제는 스프링 컨테이너에 객체를 스프링 빈으로 등록하고 스프링 컨테이너에서 스프링 빈을 찾아서 사용하도록 변경되었다 

 

 

먼저 스프링 설정정보를 설정해보자

 

1.  AppConfig에 @Configuration 어노테이션 붙이기

  • @Configuration 이 붙은 AppConfig 클래스 내의 모든 메서드들을 스캔하여 반환하는 객체를 빈으로 등록시키고 스프링 컨테이너에 객체를 집어넣어 관리한다.

 

2. 각 메서드에 @Bean 어노테이션 붙이기

  • @Bean 이 붙은 메서드명을 스프링 빈으로 등록하고 스프링 빈의 이름으로 사용한다.
  • 빈 이름을 변경할 수도 있다.
    • @Bean(name="memberService2") 
@Configuration
public class AppConfig {

	@Bean
	public MemberService memberService() {
		return new MemberServiceImpl(memberRepository());
	}
    ... 
}

 

 

 

이렇게 @Bean 이 붙은 메서드가 스프링 컨테이너에 빈 이름으로 저장이 됨 

 

※ 주의

빈 이름은 항상 다른 이름을 부여해야한다.

같은 이름을 부여하면 다른 빈이 무시되거나 기존 빈을 덮어버리는 오류가 발생한다.

 

 

?궁금증? @Configuration 어노테이션을 사용하면 객체를 빈으로 등록되니 @Bean 안 써도 되지않을까? 

@Bean 어노테이션이 붙지 않아도 메서드가 빈으로 등록이 되긴하지만 명시적으로 빈을 등록하면 코드의 가독성과 명확성을 높일 수 있으니 쓰는걸 권장한다고 한다. 

 

 

정리해서 @Configuration @Bean 두 어노테이션을 붙이면 스프링 컨테이너에 의해 관리되고, 필요에 따라 인스턴스화되고 주입된다.

 

 

 

스프링 컨테이너 생성하기 

 

 

1. 스프링 컨테이너 생성
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

2. applicationContext.getBean(이름,반환타입)
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);

 

  • ApplicationContext 는 스프링 컨테이너 
    • 모든 객체들을 관리해주는 곳
  • ApplicationContext 는 인터페이스
    • 그 인터페이스를 구현한 것 중 하나가 AnnotationConfigApplicationContext 이다.



기존에는 개발자가 필요한 객체를 AppConfig 를 사용해 직접 생성하고 조회하고 DI를 했지만 이제부턴 스프링 컨테이너를 통해서 사용한다

  • ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
    • 스프링 컨테이너를 생성할 때는 구성 정보를 지정해줘야한다 
    • 파라미터에 AppConfig 을 넣으면 AppConfig 의 @Bean 이 붙은 설정정보들을 스프링 컨테이너에 넣어서 관리해준다.

 

 

스프링 컨테이너를 통해 필요한 스프링 빈(객체)를 찾을 수 있다.

  • MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
    • getBean("찾을 객체 메서드 이름",  반환타입)
    • AppConfig에서 @Bean이 붙여진 memberService 이름을 찾는다.

 

 

 

결과

public class MemberApp {

	public static void main(String[] args) {
		ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
		MemberService memberService = applicationContext.getBean("memberService", MemberService.class); // memberService 메서드 이름 객체를 찾을거야, 반환타입은 class
		
		//회원가입하기
		Member member = new Member(1L,"memberA",Grade.VIP);
		memberService.join(member);
		
		//회원가입 확인해보고 비교해보기
		Member findMember = memberService.findMember(1L);
		System.out.println("new member = " + member.getName()); 
		System.out.println("findMember = " + findMember.getName());
	}

}
@Configuration
public class AppConfig {

	@Bean
	public MemberService memberService() {
		return new MemberServiceImpl(memberRepository());
	}
	
	@Bean
	public MemoryMemberRepository memberRepository() {
		return new MemoryMemberRepository();
	}
	
	@Bean
	public OrderService orderService() {
		return new OrderServiceImpl(memberRepository(), discountPolicy()); //discountPolicy()로 대체해주기
	}
	
	@Bean
	public DiscountPolicy discountPolicy() {
//		return new FixDiscountPolicy();  // 1만원이든 2만원이든 천원 할인적용
		return new RateDiscountPolicy(); // 1만원 천원할인, 2만원이면 2천원할인
	}
}

* key(빈이름) : memberService / value(빈객체) : MemberServiceImpl

 

 

@Bean 붙은 메서드들이 빈으로 정의 된걸 볼 수 있다. 

 

 

 

 

스프링 컨테이너 생성 과정

 

1. 스프링 컨테이너 생성

 

 

 

 

2. 스프링 빈 등록

 

 

 

 

3. 스프링 의존 관계 설정

 

  • 동적인 의존관계 설정을 스프링이 해준다.
  • 스프링 컨테이너는 설정 정보를 참고하여 의존관계를 주입(DI)한다. 
    • '설정 정보를 참고한다'는 말은 "MemberService 에서 memberRepository를 의존할거야"  이런걸 보고 DI 하는 것단순히 자바 코드를 호출하는것이 아님.

 

 

 

스프링은 빈을 생성하고 의존관계를 주입하는 단계가 나누어져있다 

하지만 자바코드로 스프링 빈을 등록하면 생성자를 호출하면서 의존관계 주입도 한번에 처리된다. 

이부분은 의존관계 자동주입을 공부해보기! 

 

 

 

 

이제 스프링빈이 잘 등록됐는지 테스트해보자

 

빈 출력

 

  • getBeanDefinitionNames() : 등록된 모든 빈의 이름을 문자열 배열로 반환하는 메서드
public class ApplicationContextInfoTest {

	AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
	
	@Test
	@DisplayName("모든 빈 출력하기")
	void findAllBean() {
		String[] beanDefinitionNames = ac.getBeanDefinitionNames(); // bin에 등록된 모든 이름 출력
		for (String beanDefinitionName : beanDefinitionNames) { //리스트 for문으로 보게
			Object bean = ac.getBean(beanDefinitionName); //타입을 지정하지 않았기에 Object로!
			System.out.println("name = " + beanDefinitionName + "Object = " + bean);
		}
	}
}

 

 

 

빨간 네모박스는 스프링이 내부적으로 스프링을 자체 확장하기 위한 기반 빈들이고

초록색 네모박스가 내가 직접 등록한 빈들이다.

 

 

 

빨간 네모박스 빼고 내가 직접 등록한 빈들만 보고싶다면? 

public class ApplicationContextInfoTest {

	AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
	
	@Test
	@DisplayName("모든 빈 출력하기")
	void findAllBean() {
		String[] beanDefinitionNames = ac.getBeanDefinitionNames(); // bin 정리된 이름 등록
		for (String beanDefinitionName : beanDefinitionNames) { //리스트 for문으로 보게
			Object bean = ac.getBean(beanDefinitionName); //타입을 지정하지 않았기에 Object로!
			System.out.println("name = " + beanDefinitionName + "Object = " + bean);
		}
	}
	
	//스프링 내부빈 말고 내가 등록한 빈들 출력하기
	@Test
	@DisplayName("애플리케이션 빈 출력하기")
	void findApplicationBean() {
		String[] beanDefinitionNames = ac.getBeanDefinitionNames(); 
		for (String beanDefinitionName : beanDefinitionNames) { 
			//getBeanDefinition은 빈에 대한 각 정보들을 꺼낼 수 있음
			BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName); 
			
			//BeanDefinition.ROLE_INFRASTRUCTURE : 스프링 내부 빈
			//BeanDefinition.ROLE_APPLICATION : 직접 등록한 빈
			if(beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) { 
				Object bean = ac.getBean(beanDefinitionName); //타입을 지정하지 않았기에 Object로!
				System.out.println("name = " + beanDefinitionName + "Object = " + bean);
			}
		}
	}
}

 

 

 

빨간 네모박스 제외한 내가 등록한 빈만 출력한 것을 볼 수 있다. 

 

 

 

애플리케이션 빈 출력할때 getRole() 로 구분할 수 있다. 

  • BeanDefinition.ROLE_APPLICATION 
    • 내가 애플리케이션을 개발하기 위해 등록한 빈들
  • BeanDefinition .ROLE_INFRASTRUCTURE
    • 스프링이 내부에서 사용하는 빈들

 


 

여기서 든 궁금증! 


AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

MemberService memberService = ac.getBean("memberService", MemberService.class);

 

1. getBean() 메서드 파라미터에 왜 구현 클래스를 넣어야하지?

빈 이름은 고유한데 굳이 구현 클래스를 적어야하나?

만약 빈 이름이 여러개일 경우 구현클래스를 적지않으면 일일히 찾아야하니 적어야할텐데..

▶ 아래처럼 getBean() 에서 반환타입을 적지않고 이름만 명시할 경우 Object 로 반환된다 

Object memberService = ac.getBean("memberService");

가독성과 안정성 측면에서 안좋다고 함 그러므로 가능한 한 반환받을 객체의 타입을 명시하는게 좋아서 반환타입을 적는게 좋다! 

 

 

2. 그리고 구현 클래스니까 getBean("memberService", MemberServiceImpl.class) 를 써도 되지않나? 

MemberService 인터페이스인데 왜 .interface 라고 적지않는거지?

▶ 부모 인터페이스 또는 구현체를 적어도 상관은 없지만 다형성을 위해 부모 인터페이스를 넣는게 더 좋다고 함

▶ 자바 언어는 빌드가 되면 인터페이스가 .class 가 된다 따라서 구분하지 않는다고 함

 

 

 

 

 

반응형
LIST

+ Recent posts