목차

    DI(Dependency injection)

    DI란 스프링이 다른 프레임워크와 차별화되어 제공하는 의존 관계 주입 기능으로, 객체를 직접 생성하는 게 아니라 외부에서 생성한 후 주입 시켜주는 방식이다. DI(의존성 주의)를 통해서 모듈 간의 결합도가 낮아지고 유연성이 높아진다.
    객체 생성 방식 설명
    기존방식
    A객체가 B, C객체를 new 생성자를 통해서 직접 생성하는 방법
    *의존성 주입 방식*
    외부에서 생성 된 객체를 setter()를 통해 사용하는 방법

    - A객체에서 B, C객체를 사용(의존)할 때, A객체에서 직접 생성하는 것이 아니라 외부(IOC컨테이너)에서 생성된 B, C객체를 조립(주입) 시켜 setter 혹은 생성자를 통해 사용하는 방식이다.

    - 스프링에서는 객체를 Bean 이라고 부르며, 프로젝트가 실행될때 사용자가 Bean으로 관리하는 객체들의 생성과 소멸에 관련된 작업을 자동적으로 수행해주는데 객체가 생성되는 곳에 스프링에서는 Bean 컨테이너라고 한다.

    의존성 주입이 생겨난 이유

    public class BugService {
        public void countLeg(){
            BugRepository bug = new Fly();
            bug.legCount();
        }
    }

    위 처럼 new 를 통해 직접 객체를 생ㅅ헝하면 BugRepository는 Fly클래스에 의존 되어버린다.

    내가 Fly가 아니라 다른 객체를 사용하고 싶어도 코드를 수정하기 전까진 불가능하다. 

     

    public class BugService {
        private BugRepository bugRepository;
    
        public BugService(BugRepository bugRepository){
            this.bugRepository = bugRepository;
        }
    
        public void countLeg(){
            bugRepository.legCount();
        }
    }
    
    //Test
    @Test
    public void testLeg(){
        BugRepository bug = new LadyBug(); // Fly(), Mantis()... 등등
        BugService bugService = new BugService(bug); //bugService의 생성자로 의존성주입!
        bugService.countLeg();
    }

     

    이렇게 생성자로 어떤 객체를 받아서 BugRepository를 생성해주면 의존성을 주입받았다고 할 수 있다. 즉 Test 쪽에서 보면 bug를 여러가지로 생성가능하다. 따라서 BugService에 넘겨주는 객체에 따라 의존성을 주입받을 수 있다.

    의존성 주입 방법 

    생성자를 이용하는 방법

     

    • 장점
      • 필수적으로 사용해야 하는 레퍼런스 없이는 인스턴스를 만들지 못하도록 강제함
      • 테스트 코드 작성시 생성자를 통해 의존성 주입 용이함
    • 단점
      • 어쩔 수 없는 순환 참조는 생성자 주입으로 해결하기 어려움(가급적 순환참조 발생하지 않는게 중요)
      • 순환 참조? A가 B가 참조하고 B가 A를 참조하는 경우
    @Service
    public class BugService {
    
    private BugRepository bugRepository;
        @Autowired
        public BugService(BugRepository bugRepository){
            this.bugRepository = bugRepository;
        }
    }

    Field 변수를 이용한 방법

    • 장점 
      • 간단하다.
    • 단점
      • 의존 관계가 눈에 잘 보이지 않아 추상적이고, 이로 인해 의존성 관계가 과도하게 복잡해질 수 있다.
      • SRP : 단일책임원칙에 반하는 안티패턴
      • 단위 테스트시 의존성 주입이 용이하지 않음
    @Service
    public class BugService {
        @Autowired
        private BugRepository bugRepository;
    }

    setter를 이용한 방법

    • 장점
      • 의존성이 선택적으로 필요한 경우에 사용(set을 불러올때 사용하니깐!?)
      • 생성자에 모든 의존성을 기술하면 과도하게 복잡해질 수 있는 것으로 선택적으로 나눠 주입 할 수 있게 부담을 덜어줌
      • 생성자 주입과 setter 주입 방법을 적절하게 분배하여 사용을 권장
    @Service
    public class BugService {
    
        private BugRepository bugRepository;
    
        @Autowired
        public void setBugRepository(BugRepository bugRepository) {
            this.bugRepository = bugRepository;
        }
    }

    DI(Dependency Injection) 세 가지 방법

    느슨한 결합

    - 객체를 주입 받는다는 것은 외부에서 생성된 객체를 인터페이스를 통해서 넘겨받는 것이다. 이렇게 하면 결합도를 낮출수 있고, 런타임시에 의존관계가 결정되기 때문에 유연한 구조를 가진다.

    - SOULD 원칙에서 O에 해당하는 Open Closed Principle을 지키기 위해서 디자인 패턴 중 전략 패턴을 사용하게 되는데, 생성자 주입을 사용하게 되면 전략 패턴을 사용하게 된다.

     

    Fielid Injection 필드 주입

    @Component
    public class SampleController {
        @Autowired
        private SampleService sampleService;
    }

    Field Injection을 사용하면 안되는 이유

    • 단일 책임(SRP)의 원칙 위반

    의존성을 주입하기가 쉽다.
    @Autowired 선언 아래 개수 제한 없이 무한정 추가할 수 있으니 말이다.

    여기서 Constructor Injection을 사용하면 다른 Injection 타입에 비해 위기감을 느끼게 해준다.

    Constructor의 parameter가 많아짐과 동시에 하나의 Class가 많은 책임을 떠안는다는 걸 알게된다.

    이때 이러한 징조들이 Refactoring을 해야한다는 신호가 될 수 있다.

    Setter Injection 세터 주입

    @Component
    public class SampleController {
        private SampleService sampleService;
     
        @Autowired
        public void setSampleService(SampleService sampleService) {
            this.sampleService = sampleService;
        }
    }

    Construtor Injection 생성자 주입 (권장)

    아래 처럼 Constructor에 @Autowired Annotation을 붙여 의존성을 주입받을 수 있다.

    @Component
    public class SampleService {
        private SampleDAO sampleDAO;
     
        @Autowired
        public SampleService(SampleDAO sampleDAO) {
            this.sampleDAO = sampleDAO;
        }
    }
    
    @Component
    public class SampleController {
    
    	private final SampleService sampleService = new SampleService(new SampleDAO());
        
    	...
    }

    Spring Framework Reference에서 권장하는 방법은 생성자를 통한 주입이다.

    생성자를 사용하는 방법이 좋은 이유는 필수적으로 사용해야하는 의존성 없이는 Instance를 만들지 못하도록 강제할 수 있기 때문이다.

    Spring 4.3버전부터는 Class를 완벽하게 DI Framework로부터 분리할 수 있다.

    단일 생성자에 한해 @Autowired를 붙이지 않아도 된다.
    Spring 4.3부터는 클래스의 생성자가 하나이고 그 생성자로 주입받을 객체가 Bean으로 등록되어 있다면 @Autowired를 생략할 수 있다.

    또한 앞서 살펴본 Field Injection의 단점들을 장점으로 가져갈 수 있다.

     

    • null을 주입하지 않는 한 NullPointerException 은 발생하지 않는다.

    의존관계 주입을 하지 않은 경우에는 Controller 객체를 생성할 수 없다.
    즉, 의존관계에 대한 내용을 외부로 노출시킴으로써 컴파일 타임에 오류를 잡아낼 수 있다.

    • final 을 사용할 수 있다.

    final로 선언된 레퍼런스타입 변수는 반드시 선언과 함께 초기화가 되어야 하므로 setter 주입시에는 의존관계 주입을 받을 필드에 final 을 선언할 수 없다.

    final의 장점은 객체가 불변하도록 할 수 있는 점으로, 누군가가 Controller 내부에서 Service 객체를 바꿔치기 할 수 없다는 점이다.

    • 순환 의존성을 알 수 있다.

    앞서 살펴 본 Field Injection에서는 컴파일 단계에서 순환 의존성을 검출할 방법이 없지만, Construtor Injection에서는 컴파일 단계에서 순환 의존성을 잡아 낼 수 있다.

    • 의존성을 주입하기가 번거로워 위기감을 느낄 수 있다.

    Construtor Injection의 경우 생성자의 인자가 많아지면 코드가 길어지며 개발자로 하여금 위기감을 느끼게 해준다.

    이를 바탕으로 SRP 원칙을 생각하게 되고, Refactoring을 하게 된다.

    이러한 장점들 때문에 스프링 4.x Documents에서는 Constructor Injection을 권장한다.

     

    굳이 Setter Injection을 사용하려면, 합리적인 default를 부여할 수 있고 선택적인(optional) 의존성을 사용할 때만 사용해야한다고 말한다.
    그렇지 않으면 not-null 체크를 의존성을 사용하는 모든 코드에 구현해야한다.

    결국 더 좋은 디자인 패턴과 코드 품질을 위해서는 Constructor Injection을 사용해야 한다.

    참고 : https://velog.io/@gillog/Spring-DIDependency-Injection-%EC%84%B8-%EA%B0%80%EC%A7%80-%EB%B0%A9%EB%B2%95

    IoC(Inversion of Control)

    IoC란 '제어의 역전' 이라는 의미로 말 그대로 메소드나 객체의 호출작업을 개발자가 아닌 외부에서 결정되는 것이다. 스프링이 모든 의존성 객체를 스프링이 실행될때 다 만들어주고 필요한 곳에 주입시켜줌으로써 Bean들은 싱글턴 패턴의 특징을 가진다.

    'Spring > [인프런] Spring FrameWork' 카테고리의 다른 글

    Spring AOP 개념  (0) 2022.01.21
    Spring 커넥션풀(c3p0)  (0) 2022.01.09
    Spring JDBC 연동 & JDBC Template  (0) 2022.01.08
    리다이렉트, 인터셉트  (0) 2022.01.06
    세션, 쿠키  (0) 2022.01.03

    + Recent posts