Spring사용하는 이유

728x90

Spring을 사용하는 이유

DIP 를 위반하는 간단한 예시

class LightBulb {
    void turnOn() {
        System.out.println("LightBulb: Bulb turned on...");
    }

    void turnOff() {
        System.out.println("LightBulb: Bulb turned off...");
    }
}

class Switch {
    private LightBulb lightBulb;

    public Switch() {
        this.lightBulb = new LightBulb();
    }

    void operate() {
        // ... some logic ...
        lightBulb.turnOn();
        // ... some logic ...
    }
}

위와 같은 예가 있다 가정한다. 위 예는 DIP(Dependency Inversion Principle) 를 위배한다. 그이유는 Switch 클래스가 LightBulb 라는 구체 클래스에 의존하기 때문이다. 이렇게되면 만약 lightBulb 가아닌 다른 빛을 내는 기기의 의존성을 주입받기 위해선 Switch 클래스를 수정해야한다.


이런 상황을 해결하기위해선

아래와같이 LightBulb 가 인터페이스를 상속받게 하며, Switch 클래스는 생성자를 통해 인터페이스 의존성을 주입받으면 된다.

interface SwitchableDevice {
    void turnOn();
    void turnOff();
}

class LightBulb implements SwitchableDevice {
    @Override
    public void turnOn() {
        System.out.println("LightBulb: Bulb turned on...");
    }

    @Override
    public void turnOff() {
        System.out.println("LightBulb: Bulb turned off...");
    }
}

class Switch {
    private SwitchableDevice device;

    public Switch(LightBulb lightBulb) {
        this.device = lightBulb;
    }

    void operate() {
        // ... some logic ...
        device.turnOn();
        // ... some logic ...
    }
}

위처럼 Switch 는 인터페이스인 SwitchableDevice 를 의존한다. 하지만 아직 문제가 있다. 아직도 LightBulb 외 다른 빛을 내는 기기의 의존성을 주입받기 위해서는 Switch 클래스의 생성자에 수정이 필요하다. 이럴땐 Switch 클래스의 생성자의 파라미터에 LightBulb 가 아닌 SwitchableDevice 를 넘겨주면 된다.


interface SwitchableDevice {
    void turnOn();
    void turnOff();
}

class LightBulb implements SwitchableDevice {
    @Override
    public void turnOn() {
        System.out.println("LightBulb: Bulb turned on...");
    }

    @Override
    public void turnOff() {
        System.out.println("LightBulb: Bulb turned off...");
    }
}

class Switch {
    private SwitchableDevice device;

    public Switch(SwitchableDevice device) {
        this.device = device;
    }

    void operate() {
        // ... some logic ...
        device.turnOn();
        // ... some logic ...
    }
}

class AppConfig {
    Switch configureSwitch() {
        return new Switch(new LightBulb());
    }
}

이렇게하면 이제 Switch 의 빛을 내는 기기를 바꾸고 싶을때 더이상 Switch 클래스를 건드리지 않아도 된다. 즉 AppConfig 라는 클래스에 우리가 사용하고 싶은 구체 클래스를 지정만 해주면 우리는 원하는 Switch 를 얻을수 있게 된다.



Spring 을 사용하는 이유는 다음과 같다.

  1. JAVA를 통해 코드를 짤때 객체를 구현하는 클래스를 Ex)AppConfig 처럼 다른 클래스로 분리해서 코드를 짜야한다. 그렇지 않으면 DIP(의존관계 역전 원칙)원칙 즉 하나의 클래스가 추상클래스와 구체클래스에 동시에 의존할수 있기때문이다.

    • 즉 DIP 의 핵심은 추상화에 의존을 해야한다는 것이다.
  2. 또한 AppConfig처럼 객체를 구현하는 클래스를 따로 분리하는순간 OCP원칙(소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다)에 의하여 우리가 소프트웨어 요소를 새롭게 확장해도 사용영역의 변경은 닫혀있으면서 AppConfig (구성영역)의 코드만 수정하면 되기때문에 OCP원칙도 지킬수 있게된다.

  3. 마지막으로 AppConfig를 통해 객체를 생성하고 연결하는 역활을 담당하게하고, 클라이언트 객체는 실행에 대한 책임만을 지게 함으로서 SRP(단일 책임 원칙)을 지킬수있다.

  4. 위 이유들 때문에 AppConfig처럼 구현객체를 생성하며 연결하는 클래스를 따로 구현한다. AppConfig처럼 객체를 생성하고 관리하면서 의존관계를 연결해 주는 것을 IoC컨테이너 또는 DI컨테이너라 한다.

  5. 기존에는 AppConfig를 사용해 직접 객체를 생성 및 의존관계를 주입했지만, 스프링 컨테이너를 사용해 위과정을 자동화 할수있다.!!!!



Container 생성, Bean등록

Spring Container에서는 @Configuration이 붙은 AppConfig를 설정(구성)정보로 사용한다. 이때 @Bean이라 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록한다. 이렇게 스프링 컨테이너에 등록된 객체를 스프링 빈이라 한다.

 @Configuration
 public class AppConfig {

    @Bean
     public MemberService memberService() {
         return new MemberServiceImpl(memberRepository());
     }

   ...
 }

@Configuration에 의해 AppConfig을 구성정보로 사용한다는 의미이며, @Bean이라 적힌 memberService()에 의해 반환된 객체를 스프링 컨테이너에 등록한다. 이렇게 스프링 컨테이너에 등록된 객체를 스프링 빈이라 하며, 스프링 빈의 이름은 메서드의 명 (memberService)를 사용한다.



SpringContainer 에서 Bean 가져오는 방법

스프링을 사용함으로서 필요한 객체를 AppConfig에서 조회하는게 아닌 스프링 컨테이너에 등록된 스프링빈(객체)을 조회한다. 이때 스프링빈은 applicationContext.getBean()메서드를 통해 찾을수 있다.

ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
 MemberService memberService = applicationContext.getBean("memberService", MemberService.class);

ApplicationContext을 통해 AppConfig안에 있는 환경설정 정보를 가지고 스프링 컨테이너를 만들어준다.

applicationContext.getBean("memberService", MemberService.class);을 통해 bean을 가져온다.

getBean("메서드이름",타입) 형식을 맞춰 입력해주면 된다.



Spring Container

Spring Container을 만드는 2가지 방법이존재한다.

  • XML을 기반으로 만들수있으며
  • 애노테이션 기반의 자바 설정 클래스로 만들수있다.(우리가 위에서 했던 방식)
  • 위 사진처럼 Spring Container에 Spring Bean이 어떻게 저장되는지 알수있다.
  • Bean의 이름은 메서드이 이름, 빈 객체는 메서드의 return 객체가 되는것을 알수있다.

🙏Reference

  • [김영한님 Spring 강의
728x90

'Spring' 카테고리의 다른 글

[SPRING] Bean Scope  (0) 2023.03.05
[SPRING] Bean Life cycle, call back  (0) 2023.03.05
[SPRING] 의존관계 자동 주입  (1) 2023.03.05
[SPRING] Component scan  (0) 2023.03.03
[SPRING] Singleton  (0) 2023.02.26

+ Recent posts