본문 바로가기
Back-end/멋쟁이사자처럼

[멋쟁이사자처럼] 1주차 정기세션(스프링이란?, DI와 IoC)

by 잔디🌿 2024. 3. 12.

    스프링 탄생배경

     

    EJB는 엔터프라이즈 자바빈즈(Enterprise Java Bean)의 줄임말이다. 애플리케이션의 업무 로직을 가지고 있는 서버 애플리케이션이다. 

    JB는 자바 객체를 재사용 가능하게 컴포넌트화 시킬 수 있는 코딩방침이니까 EJB는 컴포넌트들을 관리하는 컨테이너이다.

     

    EJB에는 코드들이 많아지는 문제가 발생한다.(객체들간의 의존성을 해결하기 위해서) 따라서 EJB를 사용하지 않고 객체를 관리할 수 있는 컨테이너가 개발되었는데 그것이 스프링이다.

     

    스프링이란?

     

    스프링이라는 단어를 특정한 하나를 지칭하는 경우도 있지만 대부분 여러 기술들의 집합체를 의미한다.

    스프링 내에 기술들은 스프링 프레임워크, 스프링 부트, 스프링 데이터... 등이 있다.

     

    스프링 프레임워크가 핵심이고 이 기술들을 편리하게 이용하게 해주는 것이 스프링 부트이다.

     

    스프링 부트

     

    스프링 프레임워크 기반 프로젝트를 복잡한 설정없이 쉽고 빠르게 만들어주는 프레임워크이다.

    (프레임워크란? : 어떠한 목적을 달성하기 위해 복잡하게 얽혀있는 문제를 해결하기 위한 구조로, 소프트웨어 개발에 있어 하나의 뼈대 역할을 한다.)

     

    스프링은 좋은 객체지향 어플리케이션을 개발할 수 있게 도와주는 프레임워크이다.

     

    객체지향 프로그래밍

     

    객체지향 프로그래밍은 컴퓨터프로그램을 여러개의 독립된 객체들의 모임으로 생각하는 것이다.

    각각의 객체들은 메세지를 주고받고, 데이터를 처리할 수 있다.

    객체지향프로그래밍은 프로그램을 유연하고 변경이 용이하게 만든다.

     

    좋은 객체지향설계의 5개의 원칙

     

    -SRP : 단일 책임 원칙

    -OCP : 개방-폐쇠 원칙

    -LSP : 리스코프 치환 원칙

    -ISP : 인터페이스 분리 원칙

    -DIP : 의존관계 역전 원칙

     

    SRP 

    한 클래스는 하나의 책임만 가져야 한다.

     

    하나의 책임이라는 것이 기준은 변경이다. 변경이 있을 때 파급효과가 적어야 단일 책임 원칙을 잘 따른 것이라고 볼 수 있다.

     

    OCP

    소프트웨어 요소는 확장에는 열려있으나, 변경에는 닫혀있어야한다.

    여기서부터 다형성이란 개념이 나온다. 나는 다형성에 대해 잘 몰라서 이 개념에 대해 알아봤다.

     

    <다형성이란>

    다형성이란 하나의 타입에 여러 객체를 대입할 수 있는 성질을 말한다.

    다형성을 사용하면 기능을 확장하거나 객체를 변경해야 할 때 타입변경 없이 객체 주입만으로 수정이 일어날 수 있게 하고, 중복되는 코드는 상속을 사용하여 제거할 수 있으므로 객체지향설계와 가까워질 수 있다.

     

    다형성을 구성하는 방법은 세가지가 있는데 

    오버로딩, 오버라이딩, 함수형 인터페이스이다.

     

    오버로딩

    public class PrintStream {
    	...
    	public void println() {
    		this.newLine();
    	}
    
    	public void println(boolean x) {
      		synchronized(this) {
          	this.print(x);
          	this.newLine();
      		}
    	}
    
    	public void println(char x) {
        	synchronized(this) {
            	this.print(x);
            	this.newLine();
        	}
    	}
    
    	public void println(int x) {
        	synchronized(this) {
            	this.print(x);
            	this.newLine();
        	}
    	}
    	...
    }

    이 코드는 자바의 println 함수이다. 함수명이 모두 같은데 받는 매개변수에 따라 다른 함수가 호출된다. 위와 같은 작업은 여러 종류의 타입을 받아들여 결국엔 같은 기능을 하도록 만들기 위한 작업이다. 메소들을 동적으로 호출하니 다형성이라고 볼 수 있다.

     

    오버라이딩

     

    오버라이딩은 상위클래스의 메서드를 하위클래스에서 재정의하는 것을 의미한다.

    public abstract class Figure {
        protected int dot;
        protected int area;
    
        public Figure(final int dot, final int area) {
            this.dot = dot;
            this.area = area;
        }
    
        public abstract void display();
    
    	  // getter
    }
    public class Triangle extends Figure {
        public Triangle(final int dot, final int area) {
            super(dot, area);
        }
    
        @Override
        public void display() {
            System.out.printf("넓이가 %d인 삼각형입니다.", area);
        }
    }

    위와같이 말이다.

     

    함수형 인터페이스

    람다식을 사용하기 위한 API로 자바에서 제공하는 인터페이스에 구현할 메소드가 하나 뿐인 인터페이스를 의미한다.

    enum과 함께 사용하면 다형성의 장점을 경험할 수 있다.

     

    위에서 설명한 다형성으로 OCP를 지킬 수 있다.

    그런데 다형성을 활용하였음에도 기존의 코드가 변경될 수 있다. 그러면  OCP를 지키지 못하는 것이다.

    이를 해결하기 위해 객체를 생성하고 연관관계를 맺어주는 별도의 설정자가 필요한데, 이를 스프링 컨테이너가 해준다.

     

    LSP

    프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다는 원칙이다.

     

    다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야한다는 뜻이다. 예를 들어 자동차 인터페이스의 엑셀기능은 앞으로 가는 것인데, 어떠한 구현체가 엑셀을 밟았을 때 뒤로 가게 구현한다면 이는 LSP를 위반한 것이다.

     

    ISP

    특정 클라이언트를 위한 인터페이스 여러개가 범용 인터페이스 하나보다 낫다는 원칙이다.

     

    이를 통해 인터페이스가 명확해지고, 대체 가능성이 높아진다.

     

    DIP

    구체화가 아닌 추상화에 의존하라는 원칙이다.

     

    코드는 인터페이스에 의존하여 작성되어야 한다는 뜻으로, 프로그래머는 추상화에 의존해야한다.

     

     

    객체지향의 핵심은 다형성이다. 하지만 다형성만으로는 OCP와 DIP를 지킬 수 없다. 

    이를 스프링의 DI기술이 가능하게 한다.

     

    의존관계

     

    "A가 B를 의존한다" 의 뜻은 B가 변경된다면 A도 변경된다는 의미이다.

     

    의존관계가 발생하는 상황은 

    -B가 A의 필드

    -B가 A 메서드의 파라미터

    -B가 A의 로컬변수

    -B로 메세지를 보냄

     

    public class Barista {
    	private IceVanillaLatteRecipe iceVanillaLatteRecipe;
    	
    	public Barista() {
            iceVanillaLatteRecipe = new IceVanillaLatteRecipe();        
        }
    }

    위 코드에서는 바리스타는 아이스바닐라라떼 레시피에 의존한다고 볼 수 있다.

     

    public class Barista {
    
        private CoffeeRecipe coffeeRecipe;
    	
        public Barista(CoffeeRecipe coffeeRecipe) {
            this.coffieRecipe = coffieRecipe;        
        }
    }
    
    
    
    
    interface CoffeeRecipe {
    } 
    
    class IceVanillaLatteRecipe implements CoffeeRecipe {
    }
    출처: https://ttl-blog.tistory.com/87 [Shin._.Mallang:티스토리]

    코드를 위와 같이 인터페이스를 사용하도록 바꾸면 다양한 의존관계를 맺을 수 있게 되어 실제 구현 클래스와의 관계가 느슨해지고 결합도가 낮아진다.

     

    의존관계는 정적인 클래스 의존관계와 동적인 객체 의존관계가 있다. 

     

    정적인 클래스 의존관계는 import 코드만 보고 의존관계를 파악할 수 있는 의존관계이다.

    위 코드에서 바리스타는 커피레시피에 의존한다는 것을 코드만으로도 알 수 있다. 하지만 이런 클래스 의존관계만으로는 어떤 커피레시피 구현객체가 바리스타에 주입될지 알 수 없다.

     

    동적인 객체 인스턴스 의존관계

    애플리케이션 실행시점에 실제 생성된 객체 인스턴스의 참조가 연결된 의존관계이다.

    위의 예시에서 애플리케이션 실행 시점에 바리스타의 생성자로 아이스바닐라라떼를 주입함으로써 의존관계를 형성할 수 있다.

     

    의존관계 주입 - DI

    애플리케이션 실행시점에 외부에서 실제 구현 객체를 생성하고 이를 의존하는 클래스들에 해당 구현 객체를 연결해주는 것을 DI라고 한다.

    public class Application {
    
    	public static void main(String [] args) {
            CoffeeRecipe recipe = new IceVanillaLatteRecipe();
            Barista b = new Barista(recipe);  //DI, 의존관계 주입
    
            // ...
        }
    }
    출처: https://ttl-blog.tistory.com/87 [Shin._.Mallang:티스토리]

    위 코드에 의존관계 주입을 하면 다음과 같아진다.

     

    이를 통해 정적인 클래스 의존관계의 변경 없이 동적인 객체 인스턴스 의존관계를 쉽게 변경할 수 있다.

     

    토비의 스프링에서는 다음 세 가지 조건을 충족하는 작업을 의존관계 주입이라고 설명한다.

    • 클래스 모델이나 코드에는 런타임 시점의 의존관계가 드러나지 않는다. 그러기 위해서는 인터페이스만 의존하고 있어야 한다.
    • 런타임 시점의 의존관계는 컨테이너나 팩토리 같은 제3의 존재가 결정한다.
    • 의존관계는 사용할 오브젝트에 대한 레퍼런스를 외부에서 제공(주입)해줌으로써 만들어진다.

    제어의 역전(IoC)

    프로그램의 제어 흐름을 개발자가 직접 관리하는 것이 아니라 외부에서 관리하는 것이다.

     

    스프링을 사용하여 어플리케이션을 실행할 때 개발자가 코드를 작성하지 않았음에도 스프링이 내부적으로 다양한 작업들을 진행하는 것과 같이 프로그램의 제어 흐름이 개발자가 아닌 외부에 있는 것을 제어의 역전이라고 한다.

     

    쉽게 생각하면 프로그램의 진행흐름은 이미 만들어져있으며, 그 사이에 개발자의 코드가 추가되는 것이다. 

     

    제어의 역전에서는 객체가 자신이 사용할 객체를 스스로 선택하지도, 생성하지도 않는다. 심지어 어떻게 만들어지고 어디서 사용되는지도 모른다. 모든 제어 권한을 자신이 아닌 다른 대상에게 위임하기 때문이다.

     

    public class BaristaFactory {
    
        public Barista barista() {
            CoffeeRecipe recipe = new IceVanillaLatteRecipe();
            Barista b = new Barista(recipe);  //DI, 의존관계 주입
            return b;
        }
    }
    출처: https://ttl-blog.tistory.com/87 [Shin._.Mallang:티스토리]
    public class Application {
    
    	public static void test(String [] args) {
            
            Barista b = new BaristaFactory().barista();  //IoC 제어의 역전
        }
    }
    출처: https://ttl-blog.tistory.com/87 [Shin._.Mallang:티스토리]

    위 코드에서 바리스타와 바리스타팩토리에 제어의 역전이 적용되어있다. 

    원래 바리스타가 커피레시피를 결정하고 생성하는 제어권을 가지고 있었으나 지금은 바리스타팩토리에 있다. 

    자신이 어떤 커피레시피를 만들고 사용할지를 결정할 권한을 바리스타팩토리에게 넘겼으니 바리스타는 이제 수동적인 존재이다.

     

    따라서 테스트는 바리스타팩토리가 만드록 초기화해서 자신에게 사용하도록 공급해주는 커피레시피를 사용할 수 밖에 없다.

     

    이게 제어의 역전이다

     

    프레임워크와 라이브러리

     

    내가 작성한 코드의 제어권이 외부에 있고 외부에서 나의 코드를 대신 실행하면 그건 프레임워크이다. 

    그러나 내가 작성한 코드가 직접 프로그램의 흐름을 담당한다면 그것은 라이브러리이다. 

     

    프레임워크는 내 코드가 도구가 되어 프레임워크에서 사용되는 것이고 라이브러리는 라이브러리가 내 도구가 되어 내 코드에서 사용되어지는 것이다.

     

    끗!

    와 진짜 너무너무너무너무너무너무너무너무너무너무너무너무너무너무너무 어렵다 

    그래도 배운 내용 이렇게 써보니까 쫌 정리가 된다..............

    좋은 코드를 짜기 위한 길은 생각보다 멀고도 험한 것 같다.

    힘들지만 열심히 해야겠다.

    https://ttl-blog.tistory.com/86?category=906283

     

    [Spring] 스프링이란?

    우선 스프링의 탄생 배경을 알아보겠습니다. EJB(Enterprise Java Bean) EJB를 검색해보면 다음과 같이 설명합니다. 엔터프라이즈 자바빈즈(Enterprise JavaBeans; EJB)는 기업환경의 시스템을 구현하기 위한

    ttl-blog.tistory.com

    https://ttl-blog.tistory.com/87

     

    [Spring] IoC와 DI

    의존관계 "A가 B를 의존한다" 위의 뜻은 의존의 대상인 B가 변경된다면, 해당 변경의 영향이 A에도 영향을 미친다는 것을 의미합니다. 아래 예시를 보며 자세히 이해해보겠습니다. public class Barista

    ttl-blog.tistory.com