ahnnyung ,/Spring

[Spring] @Autowired(필드주입, 수정자주입, 생성자주입), 그리고 @AllArgsConstructor

hi,ho 2020. 11. 24. 17:18
반응형

Spring 어노테이션 중엔 @Autowired라는 어노테이션이있다. 이 어노테이션을 특정 필드에 부여하면 IoC컨테이너 안에 존재하는 특정 필드와 같은 타입의 Bean을 찾아 자동으로 주입해주게 된다.

만약 같은 타입의 Bean이 여럿 존재한다면??[보러가기👀]

이 @Autowired 어노테이션이 없었다면 아래와 같이 코드가 지저분해진다...

// TestService.java
@Service
public class TestService {
    private TestRepository testRepository;

    public TestService(TestRepository testRepository){
        this.testRepository = testRepository;
    }
    ...
}

// TestRepository.java
@Repository
public class TestRepository {
    ...
}

//XML
<Bean id="testRepository" class="com.ahndding.spring.repository.TestRepository"/>

<Bean id= "testService" class="com.ahndding.spring.service.TestService">
    <constructor-arg name="testRepository" ref="testRepository" />
</Bean>

하지만 @Autowired를 쓴다면?

// TestService.java
@Serivce
public class TestService {

    @Autowired
    private TestRepository testRepository;
    ...
}

// TestRepository.java
@Repository
public class TestRepository {
    ...
}

끝이다. 얼마나 편리한가!

@Autowired사용법으론 위와 같은 필드(Field)주입 방식, 수정자(Setter) 주입 방식, 생성자(Constructor) 주입 방식이 존재한다. 각각의 사용 방법은 아래와 같다.


사용법

  1. 필드(Field)주입 방식

    // TestService.java
    @Serivce
    public class TestService {
    
        @Autowired
        private TestRepository testRepository;
        ...
    }
  1. 수정자(Setter) 주입 방식

    // TestService.java
    @Serivce
    public class TestService {
    
        private TestRepository testRepository;
    
        @Autowired
        public void setTestRepository(TestRepository testRepository){
            this.testRepository = testRepository;
        }
        ...
    }
  1. 생성자(Constructor) 주입 방식

    // TestService.java
    @Serivce
    public class TestService {
    
        private TestRepository testRepository;
    
        @Autowired
        public TestService(TestRepository testRepository){
            this.testRepository = testRepository;
        }
        ...
    }

문제점

우선 필드 주입 방식(Field Injection). 가장 편리하고 가장 심플한 코드로 보이지만 문제점이 가장 많은 친구이다. 문제점은 아래와 같다.

 

* 단일 책임의 원칙 위반
의존성을 주입하기가 쉽다. @Autowired 선언 아래 3개든 10개든 막 추가할 수 있으니 말이다. 여기서 Constructor Injection을 사용하면 다른 Injection 타입에 비해 위기감 같은 걸 느끼게 해준다. Constructor의 파라미터가 많아짐과 동시에 하나의 클래스가 많은 책임을 떠안는다는 걸 알게된다. 이때 이러한 징조들이 리팩토링을 해야한다는 신호가 될 수 있다.

 

* 의존성이 숨는다.
DI(Dependency Injection) 컨테이너를 사용한다는 것은 클래스가 자신의 의존성만 책임진다는게 아니다. 제공된 의존성 또한 책임진다. 그래서 클래스가 어떤 의존성을 책임지지 않을 때, 메서드나 생성자를 통해(Setter나 Contructor) 확실히 커뮤니케이션이 되어야한다. 하지만 Field Injection은 숨은 의존성만 제공해준다.

 

* DI 컨테이너의 결합성과 테스트 용이성
DI 프레임워크의 핵심 아이디어는 관리되는 클래스가 DI 컨테이너에 의존성이 없어야 한다. 즉, 필요한 의존성을 전달하면 독립적으로 인스턴스화 할 수 있는 단순 POJO여야한다. DI 컨테이너 없이도 유닛테스트에서 인스턴스화 시킬 수 있고, 각각 나누어서 테스트도 할 수 있다. 컨테이너의 결합성이 없다면 관리하거나 관리하지 않는 클래스를 사용할 수 있고, 심지어 다른 DI 컨테이너로 전환할 수 있다.
하지만, Field Injection을 사용하면 필요한 의존성을 가진 클래스를 곧바로 인스턴스화 시킬 수 없다.

 

* 불변성(Immutability)
Constructor Injection과 다르게 Field Injection은 final을 선언할 수 없다. 그래서 객체가 변할 수 있다.

 

* 순환 의존성
Constructor Injection에서 순환 의존성을 가질 경우 BeanCurrentlyCreationExeption을 발생시킴으로써 순환 의존성을 알 수 있다.

  • 순환 의존성이란? First Class가 Second Class를 참조하는데 Second Class가 다시 First Class를 참조할 경우 혹은 First Class가 Second Class를 참조하고, Second Class가 Third Class를 참조하고 Third Class가 First Class를 참조하는 경우 이를 순환 의존성이라고 부른다.

그래서?

Setter Injection Vs Contructor Injection

Setter Injection

Setter Injection은 선택적인 의존성을 사용할 때 유용하다. 상황에 따라 의존성 주입이 가능하다. 스프링 3.x 다큐멘테이션에서는 Setter Injection을 추천했었다.

Constructor Injection
Constructor Injection은 필수적인 의존성 주입에 유용하다. 게다가 final을 선언할 수 있으므로 객체가 불변하도록 할 수 있다. 또한 위에서 언급했듯이 순환 의존성도 알 수 있다. 그로인해 나쁜 디자인 패턴인지 아닌지 판단할 수 있다.
스프링 4.3버전부터는 클래스를 완벽하게 DI 프레임워크로부터 분리할 수 있다. 단일 생성자에 한해 @Autowired를 붙이지 않아도 된다.(완전 편한데?!) 이러한 장점들 때문에 스프링 4.x 다큐멘테이션에서는 더이상 Setter Injection이 아닌 Constructor Injection을 권장한다.

[Spring Framework Reference Documentation(4.3.29.RELEASE)] 

다음은 해당 링크로부터 발췌한 내용이다.

Constructor-based or setter-based DI?

Since you can mix constructor-based and setter-based DI, it is a good rule of thumb to use constructors for mandatory dependencies and setter methods or configuration methods for optional dependencies. Note that use of the @Required annotation on a setter method can be used to make the property a required dependency.

The Spring team generally advocates constructor injection as it enables one to implement application components as immutable objects and to ensure that required dependencies are not null. Furthermore constructor-injected components are always returned to client (calling) code in a fully initialized state. As a side note, a large number of constructor arguments is a bad code smell, implying that the class likely has too many responsibilities and should be refactored to better address proper separation of concerns.

Setter injection should primarily only be used for optional dependencies that can be assigned reasonable default values within the class. Otherwise, not-null checks must be performed everywhere the code uses the dependency. One benefit of setter injection is that setter methods make objects of that class amenable to reconfiguration or re-injection later. Management through JMX MBeans is therefore a compelling use case for setter injection.

Use the DI style that makes the most sense for a particular class. Sometimes, when dealing with third-party classes for which you do not have the source, the choice is made for you. For example, if a third-party class does not expose any setter methods, then constructor injection may be the only available form of DI.

 

 

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

Lombok의 다음과 같은 어노테이션을 사용해서 직접 생성자를 만들지 않고 Constructor Injection 하는 법

@RequiredArgsConstructor - 초기화 되지 않은 final 필드와 @NonNull 어노테이션이 붙은 필드에 대한 생성자를 생성한다.

@AllArgsConstructor - 모든 필드에 대한 생성자를 생성합니다. 또한 의존성 주입 할 대상이 많아졌을 때 훨씬 깔끔하다.

 

 

따라서 수정된 나의 코드는 이렇다.

수정전
수정 후

 

반응형