[Spring] Inversion of Control & Dependency Injection

728x90
반응형
SMALL

Spring Framework

스프링 프레임워크는 자바 플랫폼을 위한 오픈 소스 애플리케이션 프레임워크이다.

 

자바로 서비스를 개발할 때 비즈니스 로직뿐 아니라 트랜잭션 등의 로직까지 작성해야 하는 부담감을 없애고자 EJB(Enterprise JavaBeans)를 사용하였다.

 

EJB를 사용하면서 트랜잭션 등의 로우 레벨 로직 개발 부담은 줄었지만 이런 기능을 사용하기 위해 거대한 EJB를 extends 하거나 implement 하게 되어 간단한 서비스가 무겁게 변하게 되었다.

 

자바의 기본적인 객체지향에 집중하고, 특정 클래스나 라이브러리에 종속되지 않는 POJO로 코드를 작성하여 이 문제를 해결하고자 하였고 스프링 프레임워크가 탄생했다.

 

그래서 대부분의 사람들이 스프링을 설명할 때 경량화된, 가벼운 프레임워크라고 설명한다.

 

EJB에서 POJO로 변경되면서 클래스가 구조적으로 간결해졌다는 의미이지 실질적으로 스프링 프레임워크는 가벼운 프레임워크가 아니다.

 

그리고 스프링의 어원은 EJB라는 겨울을 넘어 새로운 시작의 뜻으로 지었다고 한다.

 


POJO (Plain Old Java Object)

Java EE 등의 중량 프레임워크들을 사용하게 되면서 해당 프레임워크에 종속된 "무거운" 객체를 만들게 된 것에 반발해서 탄생한 용어이다. 즉, 어떤 상속이나 구현 없이 속성과 기능만 있는 객체를 의미한다.

public class User {

    private long userNo;
    private String userId;

    public long getUserNo() {
        return userNo;
    }

    public void setUserNo(long userNo) {
        this.userNo = userNo;
    }

    getter & setter ...

}

스프링은 이러한 POJO 방식을 기반으로 한 프레임워크이다.

 


 

IoC (Inversion of Control)

제어의 역전

 

작성한 메서드나 객체의 호출을 개발자가 결정하는 것이 아닌 외부, 즉 스프링 프레임워크에서 결정한다.

이러한 객체의 호출을 스프링 프레임워크에서 결정하게 되면 객체의 생명주기(Lifecycle) 관리를 스프링 프레임워크에서 하기 때문에 개발자는 온전히 비즈니스 로직 작성에만 집중할 수 있다.

그리고 이러한 객체 호출에 대한 제어권이 프레임워크에 있기에 DI가 가능하다.


 

ApplicationContext (BeanFactory)

스프링 컨테이너

 

ApplicationContextsms BeanFactory 인터페이스의 하위 인터페이스이다.

즉, BeanFactory에 부가 기능을 추가한 것이다.

 

우선 BeanFactory는 스프링 컨테이너의 최상위 인터페이스이다.

즉, 스프링 빈을 관리하고 조회하는 역할을 한다.

 

실제론 BeanFactory와 ApplicationContext를 구분하지만, ApplcationContext가 BeanFactory의 모든 기능을 가지고 있어 BeanFactory를 직접적으로 사용하는 경우는 없다.

 

스프링 컨테이너 내부에는 빈 저장소가 존재하고, 이 저장소에는 key - value 형태로 빈이 저장되어 있다.

  • key : bean name
  • value : bean 객체

 

스프링 컨테이너는 기본적으로 빈을 싱글톤으로 관리한다. 그로인해 싱글톤 컨테이너라고 불리기도 한다.

 

https://hoooon-s.tistory.com/14?category=956412 

 

[Spring - Inflearn] Spring Container & Bean

스프링 컨테이너 ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class); MemberService memberService = applicationContext.getBean("memberService", MemberServi..

hoooon-s.tistory.com

 


 

Bean

스프링 IoC 컨테이너가 관리 하는 객체

 

Component Scanning을 통해 등록하거나 또는 XML이나 자바 설정 파일에 일일이 등록

 

  • @Component
    • @Repository
    • @Service
    • @Controller

 


 

DI (Dependency Injection)

의존성 주입

 

Spring Freamwork가 다른 Frameworkd와 차별화되어 제공하는 의존 관계 주입 기능이다.

개발자가 객체를 직접 생성하여 사용하는 게 아닌, 스프링 프레임워크에서 주입받아 사용하는 기능이다.

 


 

필드 주입 (Field Injection)

@RestController
public class SampleController {

    @Autiwired
    private SampleService service;

}

필드에 @Autowired 어노테이션만 사용하면 되는 가장 간단한 의존성 주입이다.

 

코드도 간결하고 주입 방법도 간단하여 과거에는 많이 사용하였지만 현재는 지양하는 방법이다.

 

왜냐하면 필드 주입은 객체 내부에서 다른 객체를 생성하기에 DI Container와 강한 결합으로 연결되는 구조이다.
강한 결합으로 인해 수정도 쉽지 않고, 내부에서 생성 하기에 외부에서도 사용할 수 없다.

 

또한, 생성자 주입처럼 final 객체를 만들 수 없다.

 

마지막으로 의존성 주입이 쉽게 무제한으로 가능하며, 이로인해 하나의 클래스가 갖는 책임이 너무 많아질 가능성이 있어 단일 책임 원칙을 위반한다.

 

그렇기에 과거에는 사용하였지만 현재는 지양하고 있다.

 


 

수정자 주입 (Setter Injection)

@RestController
public class SampleController {

    private SampleService service;

    @Autowired
    public void setService(SampleService service) {
        this.service = service;
    }

}

setter 메서드에 @Autowired 어노테이션을 붙여 사용하는 의존성 주입이다.

 

상황에따라 선택적 의존성 주입이 가능하여 스프링 3.x에서는 추천했다.

 

하지만, setter를 통해 SampleService를 주입해주지 않아도 SampleController 객체 생성이 가능하여 NPE가 발생할 가능성이 있다.

즉, 주입이 필요한 객체가 주입되지 않았는데도 객체 생성이 될 수 있는 문제점이 존재한다.

 


 

생성자 주입 (Constructor Injection)

@RestController
public class SampleController {

        private final SampleService service;

        @Autowired
        public SampleController(SampleService service) {
            this.service = service;
        }

}


@RestController
@RequiredArgsConsrtuctor
public class SampleController {

        private final SampleService service;

}

스프링 프레임워크에서도 권장하고, 최근 많이 사용하는 방법이다.

 

생성자 주입의 경우 수정자 주입과 다르게 의존관계를 주입하지 않는 경우 객체를 생성할 수 없다.
즉, 의존관계에 대한 내용을 외부로 노출시킴으로써 컴파일 시점에 오류를 잡을 수 있다.

 

Spring 4.3 이후, 클래스의 생성자가 하나이고, 그 생성자로 주입받을 객체 전부가 Bean으로 등록되어 있다면 @Autowired 생략 가능하다.

 

생성자 주입의 경우 객체를 생성할 때 1회만 호출이 되고, 이후 호출 되는 일이 없기에 불변하게(final) 설계가 가능해진다.
그렇기에 순환 참조를 방지할 수 있다.

 


 

순환 참조

@Component
public class SampleA {
    private SampleB b;

    public SampleA(SampleB b) {
        this.b = b;    
    }
}

@Component
public class SampleB {
    private SampleA a;

    public SampleB(SampleA a) {
        this.a = a;    
    }
}

SampleA라는 객체(Bean)은 SampleB라는 객체를 참조하고, SampleB라는 객체는 다시 SampleA를 참조하는 경우, 즉 서로가 서로를 계속해서 순환 참조 하는 에러이다.

 

필드 주입이나 수정자 주입은 SampleA 클래스의 객체가 생성되는 시점에 SampleB를 생성할 필요가 없다. 그렇기에 컴파일 상에는 문제가 없다.


하지만 실행되는 중에 SampleA 클래스나 SampleB 클래스가 로드 될 때 런타임 오류가 발생하는 크리티컬한 장애가 될 수 있다.

 

이와 다르게 생성자 주입은 생성자에서 의존성 주입이 일어나기 때문에, SampleA 클래스의 객체가 생성될 때 SampleB 클래스 객체를 생성하여 컴파일 시 이를 잡아 낼 수 있다.


 

참고

- https://day0404.tistory.com/48

728x90
반응형
LIST