Spring/SpringCore - basic

스프링 핵심 원리 - 스프링으로 전환

_Jin_ 2024. 11. 13.

Repeat is the best medicine for memory

 

제어의 역전(Ioc)과 의존관계 주입(DI)

 

앞서 순수 자바코드로 진행한 예제를 통해 스프링이 하는 역할에 대해 파악하고 이해하는 과정을 정리하였다.

 

스프링은 좋은 객체 지향적 설계와 구현을 도와주는 도구이다. 구체적으로는 필요한 구현체를 생성하고 연결해주는 역할을 담당하여 프로그램에 대한 제어 흐름 권한을 AppConfig가 가져갔다. 따라서 해당 객체를 필요로하는 객체( 클라이언트 )는 더 이상 구현체가 아닌 추상체에 의존하면서 실행에 필요한 로직만 수행하게 되었다.

 

이처럼 프로그램에 있어 제어의 흐름( ex, 클라이언트가 필요한 객체를 직접 호출 ) 직접 관리하는 것이 아닌 외부에서 관리하는 것을 제어의 역전(IoC)으로 부른다. ( = 프레임워크 )

 

한편, 의존관계의 관점에서 살펴보면 클라이언트는 실제 구현체에 의존하지 않고 추상체에만 의존하고 있음에도 NPE가 발생하지 않고, 기능이 돌아간다.

이를 설명하기 위해서는 ‘ 정적인 클래스 의존관계 ‘와 ‘ 동적인 클래스 의존관계(인스턴스) ‘를 분리하여 생각해봐야 한다.

 

 

 

먼저, 정적인 클래스 의존관계는 애플리케이션을 실행하지 않음에도 파악할 수 있는 의존 관계로 import 코드를 보거나 클래스 내부에서 사용하는 객체의 코드만으로도 이해가 되는 의존관계이다.
예를 들어 OrderServiceImpl 의 코드 내부에는 MemberRepository나 DiscountPolicy 객체를 사용하지 위해 사용될 것이다.

반면에, 동적인 객체 인스턴스 의존 관계는 애플리케이션 실행 시점에 실제 생성된 객체 인스턴스의 참조와 연결된 의존 관계를 말한다.
이는 애플리케이션의 실행 시점( 런타임 )에 외부에서 실제 구현 객체를 생성하고, 필요로 하는 클라이언트에 전달하여 연결되는 것을 의존관계 주입이라고 부른다.( =AppConfig의 역할 )

의존관계 주입은 객체 인스턴스의 생성과 참조값을 전달하는 형식이기에 클라이언트는 필요한 객체의 호출에 있어서 코드를 변경할 필요가 없어진다.( 추상체에 의존 ) 대신 의존관계 주입의 책임을 지닌 객체가 생성한 인스턴스의 변경만 코드로 작성하면 되기 때문이다.

 

결국 위의 내용을 종합하자면, 앞서 만든 AppConfig의 책임이자 역할에 대한 정리이다.

이처럼, 대신 객체를 생성하고 관리하며 의존관계를 연결해주는 것을 IoC 컨테이너 또는 DI 컨테이너라고 부르는 것이다.

 

 

스프링으로 전환하기

지금까지 AppConfig를 통해 스프링이 하는 역할에 대해 파악했으니 직접 스프링을 사용해보자.

먼저 AppConfig를 스프링 기반으로 변경해보자.

 

@Configuration
public class AppConfig { 

    @Bean
    public MemberService memberService() {
        return new MemberServiceImpl(MemberRepository());
    }
    @Bean
    public MemberRepository MemberRepository() {
        return new MemoryMemberRepository();
    }
    @Bean
    public OrderService orderService() {
        return new OrderServiceImpl(MemberRepository(), DiscountPolicy());
    }
    @Bean
    public DiscountPolicy DiscountPolicy() {
        return new RateDiscountPolicy();
    }
}

 

각 코드 위에 어노테이션과 특정 문자가 달려있다. 그 의미는 아래와 같다.

 

@Configuration을 클래스 앞에 붙여서 앱의 구성정보,설정정보 파일임을 명시해준다.

@Bean 어노테이션을 각 메서드에 붙여준다. → 각 객체들이 스프링 컨테이너에 등록된다.

( 스프링 컨테이너에 key값은 메소드명, value값은 retrun 객체가 할당되어 등록된다. )

 

그리고 이를 사용하는 클라이언트 쪽 코드도 변경해보자.

 

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());
 	}
}

 

만들었던 AppConfig를 @Configuration와 @Bean을 통해서 스프링 컨테이너에 등록하였으니, 등록된 객체를 사용하는 모습이다.

 

본래 new AppConfig로 객체를 할당하여 사용했지만,

ApplicationContext 타입의 객체를 사용하고 있다.( 구현체는 AnnotationConfigApplicationContext을 사용 - 어노테이션 기반으로 설정하였기 때문이다. )

 

ApplicationContext가 곧 스프링 컨테이너에 해당하는 타입인 것이다. 그리고 등록한 값들 중에 getBean() 메서드를 사용하여 빈(객체)의 key값과 value값을 가져와 memberService 변수에 할당하고 사용하여도 문제없이 그대로 로직이 작동한다.

 

그리고 실행 후 로그를 살펴보면,

위와 같이 설정으로 등록한 객체 값들이 빈으로 등록된 것을 확인할 수 있다.

 

다른 서비스였던 OrderApp도 스프링으로 전환해보자.

 

public class OrderApp {
	 public static void main(String[] args) {
 
	// AppConfig appConfig = new AppConfig();
	// MemberService memberService = appConfig.memberService();
	// OrderService orderService = appConfig.orderService();
 
 	ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
 	MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
	OrderService orderService = applicationContext.getBean("orderService",OrderService.class);
 
 	long memberId = 1L;
 	Member member = new Member(memberId, "memberA", Grade.VIP);
 	memberService.join(member);
 	Order order = orderService.createOrder(memberId, "itemA", 10000);
 
 	System.out.println("order = " + order);
 	}
}

 

역시 마찬가지로 스프링 컨테이너에 등록된 객체를 사용하기 위해 Context에서 빈을 가져와 사용한다.

 

그리고 아래와 같이 객체가 성공적으로 등록되어 있음을 로그로 확인할 수 있다.

 

지금까지 AppConfig를 사용해서 직접 객체를 생성하고, 의존성 주입을 실행했지만 위의 예시는 스프링 컨테이너를 통해 이를 지원받고 있다.

어노테이션 기반으로 컨테이너를 구성할 때, @Configuration이 붙은 클래스는 설정 정보로 사용하며 @Bean이 적힌 메서드를 호출하여 반환된 객체를 스프링 컨테이너에 등록하고 있다.( 스프링 빈 → 스프링 빈은 메서드 명을 스프링 빈의 이름으로 사용한다. )

이와 같은 설정을 통해서 개발자는 필요한 객체를 스프링 컨테이너의 스프링 빈을 찾아 사용하도록 변경된 것이다.

댓글