Java 웹 애플리케이션에서 IoC 가 적용되는 예

Java Console Application 의 경우 main() 과 같은 엔트리 포인트가 종료되면 애플리케이션의 실행이 종료됩니다.

 

하지만 웹에서 동작하는 웹 애플리케이션의 경우 클라이언트가 외부에서 접속해서 사용하는 서비스이기 때문에

main() 메서드가 종료되면 안됩니다.

 

그런데 서블릿 컨테이너에는 서블릿 클래스만 존재하지 main() 메서드가 존재하지 않습니다.

 

 

서블릿 컨테이너의 동작원리

1. 서블릿 컨테이너는 클라이언트의 요청이 들어옴

2. 그때마다 서블릿 컨테이너 내의 컨테이너 로직 ( service() 메서드 ) 이 서블릿을 직접 실행

 

이러한 과정을 거치므로 main () 메서드가 필요 없습니다.

이 경우 서블릿 컨테이너가 서블릿을 제어하고 있기 때문에 애플리케이션 주도권은 서블릿 컨테이너에 있습니다.

 

그래서 이를 서블릿와 웹 애플리케이션 간에 IoC (제어의 역전) 가 적용되어 있다고 할 수 있습니다.

 

그렇다면 Spring 에는 IoC 개념이 어떻게 적용되어 있을까요??

 

 

DI 의존성 주입

IoC 는 서버 컨테이너 기술, 디자인 패턴, 객체 지향 설계 등에 적용하게 되는 일반적인 개념에 반해

DI ( Dependency Injection ) 는 IoC 개념을 조금 구체화 시킨것이라고 볼 수 있습니다.

 

  • 의존성

객체 지향 프로그래밍에서 의존성이라고 하면 대부분 객체간의 의존성을 의미합니다.

예를 들어서,

A,B 두 개의 클래스 파일이 존재합니다.
A 클래스에서 B 클래스의 메서드 기능을 사용할 것입니다.
이렇게 A 클래스가 B 클래스의 기능을 사용 할 때, "A 클래스는 B 클래스에 의존한다" 라고 합니다.

 

[ 의존 관계 ]

public class MemberController{
	public void static main(String[] args){
    	MenuService ms = new MenuSerivce();
        List<Menu> menuList = ms.getMenuList(); // 의존 관계 성립
    }
}

public class MenuService{
	public List<Menu> getMenuList(){
    	return null
    }
}

위와 같이 의존 관계가 성립됩니다.

위의 과정에서는 아직 의존성 주입이 일어나지 않았습니다.

 

[ 의존성 주입 ]

public class Client{
	public static void main(String[] args){
    	MemberService ms = new MemberService();
    	MemberConroller mc = new MemberConroller(ms);
        
        List<Menu> menuList = mc.getMenu();
    }
}

public class MemberConroller{
	private MemberService memberService;
    
    public MemberConroller(MemberService memberService){
    	this.memberService = memberService;
    }
    
    public List<Menu> getMenu(){
    	return memberService.getMenuList();
    }
}

public class MenuService{
	public List<Menu> getMenuList(){
    	return null;
    }
}

MemberService 기능을 사용하기 위해 MemberController 생성자의 객체를 전달받고 있습니다.

이처럼 생성자를 통해서 어떤 클래스의 객체를 전달 받는 것을 의존성 주입이라고 합니다.

 

생성자의 피라미터로 객체를 전달하는 것"외부에서 객체를 주입한다" 라고 표현을 합니다.

 

 

 

  • 그렇다면 여기서 의미하는 외부는 무엇일까요??

[ Client 객체 ] 에서 [ MemberController 의 생성자 피라미터 ] 로 menuService 를 전달하고 있습니다.

이렇게 객체를 MemberController 외부에서 전달하고 있기 때문에 Client 가 외부가 됩니다.

 

 

 

 

의존성 주입의 실무예제

위는 실무에서의 대화 장면입니다.

 

 - Stub : 메서드가 호출되면 미리 준비된 데이터를 응답하는 과정으로, 고정된 데이터이므로 동일한 데이터를 리턴합니다.

 

 

  • 해야할 것

- 메뉴 데이터 조회 API 를 위해 MenuServiceStub 클래스를 추가로 작성해서 코드를 구성

- MenuService -> MenuServiceStub 으로 클래스명 변경

- Client , MenuController 는 MenuService 에 의존하기 때문에 클래스명을 변경해야됨

 

이렇게 업무의 변경사항이 생겼다고 수정된 클래스를 사용하는 수많은 클래스들을 바꿔야 한다면 상당히 비효율적입니다.

 

그래서 강한 결합이 아닌 느슨한 결합이 필요합니다.

느슨한 결합으로 만들기 위한 대표적인 방법이 [ 인터페이스 사용 ] 입니다.

즉, 클래스를 직접적으로 의존하는게 아니라 인터페이스에 의존하도록 해주는 것입니다.

 

MenuConroller 가 MenuService 를 의존하지만,

MenuService 의 구현체가 MenuServiceImp, MenuServiceStub 둘 중에 뭔지는 알 필요가 없는 것입니다.

그저 메뉴 목록 데이터를 조회만 할 수 있으면 되는 것입니다.

 

[ 느슨한 결합 ]

public interface MenuService {
	List<Menu> getList();
}

public class MenuServiceImp implements MenuService {
	@Override
    List<Menu> getList(){
    	return List.Of (
        	new Menu(1,"Americano",2400),
            new Menu(2,"Latte",3000)
        );
    }
}

public class MenuController(){
	private MenuService ms;
    
    public MenuController(MenuService ms){
    	this.ms = ms;
    }
    
    public List<Menu> getMenu(){
    	return ms.getList();
    }
}

public class client{
	public static void main(String[] args){
    	MenuService ms = new MenuServiceImp(); // 업캐스팅
        MenuController mc = new MenuController(ms);
        
        List<Menu> menu = mc.getMenu();
    }   
}

- 업캐스팅 : [ client 객체 ] 의 (  인터페이스 타입 변수  =  new 인터페이스 구현체 ) 와 같은 할당 방법

 

하지만 위와 같은 방법에도 문제가 있습니다.

Service 구현체에서  new 키워드로 직접 객체를 생성해주고 있습니다.

이는 스프링을 통해서 해결이 가능합니다.

 

public inteface MemberService{
	void save();
	List<Menu> getList();
}

public class MemberServiceImp implements MemberService{
    ArrayList<Menu> list = new ArrayList<>();
    
    @Override
    void save(Menu menu){
    	list.add(menu);
    }
    
	@Override
    List<Menu> getList(){
    	return list;
    }
}

public class client{
	ApplicationContext ac = new AnnotationApplicationContext(SpringConfig.class);
    MenuController mc = ac.getBean(MenuController.class);
    
    List<Menu> list = mc.getMenu();
}

public class MemberController(){
    public MemberService ms;
    
    @Autowired
    public MemberController(MemberService ms){
    	this.ms = ms;
    }
    
    List<Menu> getMenu(){
    	return ms.getList();
    }
}

@Configuration
@ComponentScan(basePackageClasses = Client.class)
public class SpringConfig(){
	
    @Bean
    public MemberService getMemberService(){
    	return new MemberServiceImp();
    }
    
    @Bean
    public MemberController getMemberController(){
    	return MemberController(getMemberService());
    }
}
  • [ 동작과정 ]
  • 테스트 코드에서 위의 코드를 호출한다고 가정하겠습니다.
  • Menu 객체를 생성해 MemberService 메서드로 리스트를 저장
  • MemberController 를 호출하면 SpringConfig 라는 IoC 컨테이너로 가서 빈 조회
  • 빈이 존재한다면 해당 되는 빈을 mc 에 할당
  • mc 의 getMenu 로 Menu를 조회

위와 같은 동작과정을 거친다면 Spring Framework 영역에서 빈등록, 의존성 주입으로 의존 객체들을 관리 해줍니다.

그래서 MenuService 의 구현체만 변경해줌으로써 한번만 코드를 수정하면 수정 부분에 대해 의존하고 있는 부분까지도

다 같이 적용이 되는 마법같은 일이 일어납니다.

 

역할과 구현의 분리의 중요성을 알 수 있는 예제였습니다.

'스터디' 카테고리의 다른 글

UML 작성법  (0) 2022.08.10
AOP - 관점 지향 프로그래밍  (0) 2022.08.09
프레임워크와 라이브러리  (0) 2022.08.09
스키마와 쿼리 디자인  (0) 2022.08.04
트랜잭션  (0) 2022.08.04

+ Recent posts