
김영한 강사님의 스프링 핵심 원리의 강의 내용과 자료를 이용했음을 밝힙니다.
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
public DiscountPolicy discountPolicy() {
return new FixDiscountPolicy();
}
}
자 이게 순수 자바로만 만든 AppConfig이다.
(저번 블로그에서 작성했던 AppConfig에 주문, 할인 정책을 추가했다)
자 좋다 DIP, OCP, IOC 다 좋은데 어쨌든 나는 스프링을 배우러 왔으니 이제 이 순수한 자바 코드를 스프링으로 전환을 해야 겠다.
자 그러기 위해서는 일단 위의 메서드들을 스프링 빈으로 등록을 해줘야 한다.
등록하는 방식은 간단하다. Annotation(@)을 이용하면 간단하게 등록된다.
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl( memberRepository(), discountPolicy());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
}
}
자 이렇게 메소드들 위해 @Bean을 달아서 스프링 빈에 추가할 수가 있다.
그리고 @Configuration은 빈을 등록하기 위해 class 위에 달아주는 애노테이션이다.
그리고 아직 배우지는 않았지만 나중에 배울 싱글톤(?)을 유지시켜주는 역할을 한다고 한다.
자 그럼 스프링 빈은 무엇일까?텍스트
-> 스프링 컨테이너가 관리하는 자바 객체를 의미한다고 인터넷에 나와있다.
하지만 우리는 스프링 컨테이너도 모른다. 그래서 내용을 진행한 다음에 밑에서 설명하도록 하겠다.
public class MemberApp {
public static void main(String[] args) {
AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService();
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("find Member = " + findMember.getName());
}
}
기존의 MemberApp이다.
우리가 잘 아는 순수 자바코드로 멤버를 등록하고 찾는 것을 확인할 수 있다.
public class MemberApp {
public static void main(String[] args) {
// AppConfig appConfig = new AppConfig();
// MemberService memberService = appConfig.memberService();
ApplicationContext applicationContext =
new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = applicationContext.getBean("memberService", 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("find Member = " + findMember.getName());
}
}
그리고 이것이 스프링 콘테이너를 적용한 MemberApp 코드이다.
차이를 비교를 해보자면
- AppConfig 객체를 직접 생성하지 않고
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);이렇게 ApplicationContext를 통해 가져온다.
- AppConfig의 메소드를 ApplicationContext의 getBean 메소드에 파라미터를 가져오려는 메소드의 이름과 리턴타입을 넣어서 가져온다.
자 이렇게 하면 Spring으로 전환했다고 할 수 있다.
그러면 ApplicationContext,스프링컨테이너, 스프링 빈이 뭔지 알아보자.
ApplicationContext 선언은 Spring을 할 때 항상 선언해줘야 한다.
ApplicationContext는 BeanFactory라는 스프링 콘테이너 최상위 인터페이스를 상속받고 있다.
BeanFactory가 핵심적인 기능들(Bean 관리, 조회, getBean() 제공)을 제공하고 ApplicationContext가 환경변수, 국제화 기능 등등 편리한 부가 기능을 더해서 제공한다
그리고 이 ApplicationContext를 스프링 컨테이너라고 한다.
기존의 개발자가 AppConfig를 통해 직접 객체생성, DI와 다르게 이제는 스프링 컨테이너를 통해서 객체 생성, DI를 한다.
스프링 컨테이너는 @Configuration이 붙은 AppConfig를 구성 정보로 사용한다.
이 구성정보(AppConfig)에서 @Bean이라 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록하고 이 등록된 객체들을 스프링 빈이라고 한다.
스프링 빈은 @Bean이 붙은 메소드 명을 스프링 빈의 이름으로 사용한다.
applicationContext.getBean("memberService", MemberService.class);
파라미터로 스프링 빈의 이름과 리턴타입을 받는데 이름이 메소드 명인 memberService이다.
자 이제 스프링 컨테이너를 통해 스프링 빈이 대강 어떤 것인지 알았으니 조금 더 알아보자.
1. 빈 이름이 겹쳐서는 안된다.
앞에서 말했듯이 메소드의 이름이 빈의 이름이 될텐데, 메소드의 이름은 중복이 가능하다.
그렇기에 자동으로 두면 빈의 이름도 겹쳐져서 중복이 가능하다.
그래서 이름 막기 위해 빈 이름을 임의로 정할 수가 있는데
@Bean(name="설정하고 싶은 이름")
이런 식으로 임의로 정할 수 있다.
2. 빈은 구체타입으로 저장된다.
사실 어떻게 보면 당연하지만 나는 아직 초보라서 그런지 많이 헷갈렸다.
나중에 보면 빈을 가져오는 메소드 getBean을 보면
MemberService memberService = ac.getBean("memberService", MemberService.class);
이런 식으로 빈의 이름과 리턴 타입을 가지고 빈을 가져온다.
이 코드를 보고 나는 memberService라는 빈이 리턴타입이 MemberService.class라고 생각했지만 정확히는 emberService.class를 상속한 MemberServiceImpl.class이다.
즉 빈의 리턴타입은 구체타입으로 저장된다.
하지만 빈을 타입으로 찾을 때는 인터페이스로도 찾을 수 있다.
추가로 덧붙이자면 구체타입이 아닌 인터페이스로 찾는 것이 유연성이 좋기 때문에 인터페이스로 찾는 것이 권장된다.
1. ac.getBean()
파라미터로 빈 이름, 빈의 리턴 타입을 받는데
ac.getBean("memberService", MemberService.class);
ac.getBean(MemberService.class);
ac.getBean(MemberServiceImpl.class);
위와 같이
- 빈 이름, 리턴타입을 둘 다 넣어 찾을 수도 있고
- 그냥 리턴 타입만 넣어서 찾을 수도 있고
- 구체타입을 넣어서 찾을 수도 있다.
하지만 위에서 말했듯이 구체타입으로 찾는 것은 유연성이 떨어지기에 지양된다.
참고로 이름만 넣고 찾고 싶다면 ac.getBeanDefinition(이름)을 이용하면 된다.
ac.getBeanDefinition("memberService");
타입으로 찾았을 때, 스프링 빈이 둘 이상이면 NoUniqueBeanDefinitionException이 발생한다.
이럴 경우에 특정 빈을 찾고 싶으면 빈 이름을 넣어서 찾고
특정 리턴 타입의 빈들을 모두 찾고 싶다면
ac.getBeansOfType(MemberService.class)
이런 식으로 getBeanOfType을 사용하면 되겠다.
참고로 최상위 객체인 Object를 넣으면 당연히 부모가는데 모든 자식들이 딸려나온다.
즉 모든 빈이 출력되는 것이다.
2. ac.getBeanDefinitionNames()
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
스프링에 등록된 모든 빈 이름을 조회한다.
하지만 우리가 등록한 빈 외에도 스프링 시스템 자체로 등록한 빈들이 있다.
그렇기에 우리가 등록한 빈들만을 보고 싶다면 if문으로 Role을 확인해서 걸러야 한다.
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
Object bean = ac.getBean(beanDefinitionName);
System.out.println("name=" + beanDefinitionName + " object=" + bean);
}
}
이런 식으로 bean에 있는 getRole() 메서드를 활용해
ROLE_APPLICATION: 직접 등록한 애플리케이션 빈
ROLE_INFRASTRUCTURE: 스프링이 내부에서 사용하는 빈
Role 확인을 하면 된다.
사실 이건 좀 나도 완전히 이해가 안된 개념이기는 하다.
스프링을 보면 우리는 지금 자바 코드 스프링을 사용했지만 xml로도 스프링을 이용할 수 있고 그 외 다양한 방식으로 스프링을 이용할 수 있다.
HOW!!
이걸 가능하게 하는 것이 바로 BeanDefinition 즉 빈 설정 메타정보이다.
이 BeanDefinition이 스프링이 가져야 할 정보들 목록을 가지고 있다.
그리고 자바 코드든 xml이든 어떤 방식으로든 그 정보들을 전달을 해주면 BeanDefinition이 그 정보들을 기반으로 객체를 생성하는 것이다.
BeanDefinition 정보로는 이런 것들이 있다고 한다.
- BeanClassName: 생성할 빈의 클래스 명(자바 설정 처럼 팩토리 역할의 빈을 사용하면 없음)
- factoryBeanName: 팩토리 역할의 빈을 사용할 경우 이름, 예) appConfig
- factoryMethodName: 빈을 생성할 팩토리 메서드 지정, 예) memberService
- Scope: 싱글톤(기본값)
- lazyInit: 스프링 컨테이너를 생성할 때 빈을 생성하는 것이 아니라, 실제 빈을 사용할 때 까지 최대한 생성을 지연
처리 하는지 여부- InitMethodName: 빈을 생성하고, 의존관계를 적용한 뒤에 호출되는 초기화 메서드 명
- DestroyMethodName: 빈의 생명주기가 끝나서 제거하기 직전에 호출되는 메서드 명
- Constructor arguments, Properties: 의존관계 주입에서 사용한다. (자바 설정 처럼 팩토리 역할의 빈을 사용
하면 없음)
음... 이렇게 스프링으로 교체를 해보았는데,, 사실 원래 순수 java 코드로 하던 AppConfig와 비교해서 더 편리하다는 생각은 아직 들지 않는다.
아마 아직 스프링에 대해 배운게 많지 않아서 그런 것 같다.
아직까지 이 스프링을 하면서 느끼는 것은 객체지향적으로 짜는 것의 효율성, 중요성이다.
이제 싱글톤이라는 것을 배운다고 하는데 이걸 배우면 스프링의 기능 자체에 인상을 받을 수 있을지 궁금하다.
:)