Spring(3) - "콩.. 좋아하세요?"- Bean의 도입

김유담·2024년 6월 2일

spring

목록 보기
3/11
post-thumbnail

김영한 강사님스프링 핵심 원리의 강의 내용과 자료를 이용했음을 밝힙니다.

👨‍💻 스프링으로 전환(1) - Bean 등록

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 위에 달아주는 애노테이션이다.
그리고 아직 배우지는 않았지만 나중에 배울 싱글톤(?)을 유지시켜주는 역할을 한다고 한다.

자 그럼 스프링 빈은 무엇일까?텍스트
-> 스프링 컨테이너가 관리하는 자바 객체를 의미한다고 인터넷에 나와있다.

하지만 우리는 스프링 컨테이너도 모른다. 그래서 내용을 진행한 다음에 밑에서 설명하도록 하겠다.

👨‍💻 스프링으로 전환(2) - 스프링 컨테이너 적용

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 확인을 하면 된다.

👨‍💻 BeanDefinition(빈 설정 메타정보)

사실 이건 좀 나도 완전히 이해가 안된 개념이기는 하다.

스프링을 보면 우리는 지금 자바 코드 스프링을 사용했지만 xml로도 스프링을 이용할 수 있고 그 외 다양한 방식으로 스프링을 이용할 수 있다.

HOW!!

이걸 가능하게 하는 것이 바로 BeanDefinition빈 설정 메타정보이다.

BeanDefinition이 스프링이 가져야 할 정보들 목록을 가지고 있다.

그리고 자바 코드든 xml이든 어떤 방식으로든 그 정보들을 전달을 해주면 BeanDefinition이 그 정보들을 기반으로 객체를 생성하는 것이다.

BeanDefinition 정보로는 이런 것들이 있다고 한다.

  • BeanClassName: 생성할 빈의 클래스 명(자바 설정 처럼 팩토리 역할의 빈을 사용하면 없음)
  • factoryBeanName: 팩토리 역할의 빈을 사용할 경우 이름, 예) appConfig
  • factoryMethodName: 빈을 생성할 팩토리 메서드 지정, 예) memberService
  • Scope: 싱글톤(기본값)
  • lazyInit: 스프링 컨테이너를 생성할 때 빈을 생성하는 것이 아니라, 실제 빈을 사용할 때 까지 최대한 생성을 지연
    처리 하는지 여부
  • InitMethodName: 빈을 생성하고, 의존관계를 적용한 뒤에 호출되는 초기화 메서드 명
  • DestroyMethodName: 빈의 생명주기가 끝나서 제거하기 직전에 호출되는 메서드 명
  • Constructor arguments, Properties: 의존관계 주입에서 사용한다. (자바 설정 처럼 팩토리 역할의 빈을 사용
    하면 없음)

👨‍💻 스프링 빈을 배워본 후기

음... 이렇게 스프링으로 교체를 해보았는데,, 사실 원래 순수 java 코드로 하던 AppConfig와 비교해서 더 편리하다는 생각은 아직 들지 않는다.

아마 아직 스프링에 대해 배운게 많지 않아서 그런 것 같다.

아직까지 이 스프링을 하면서 느끼는 것은 객체지향적으로 짜는 것의 효율성, 중요성이다.

이제 싱글톤이라는 것을 배운다고 하는데 이걸 배우면 스프링의 기능 자체에 인상을 받을 수 있을지 궁금하다.

:)

profile
잘하진 못할지언정 꾸준히 하는 개발자:)

0개의 댓글