반응형

스프링에서 의존성 주입 방법은 크게 세 가지가 있다.

   - Setter 주입

   - 필드 주입

   - 생성자 주입

 

Setter 주입(Setter Injection)

Setter에 의존성 주입 어노테이션을 사용하면 스프링 컨테이너에서 해당 객체를 찾아 주입시켜준다.
이는 스프링 컨테이너가 자동으로 Setter를 호출한다는 것을 의미하며 Setter 메소드 호출 시점은 스프링 빈 생성 직후이다.

이 방식의 문제는 Setter의 접근이 열려있기 때문에 의존성 주입받은 객체가 중간에 수정될 가능성이 있다는 것이다.

 

public interface PersonRepository {

    String printName(String name);

}
@Repository
public class HeroRepository implements PersonRepository{

    @Override
    public String printName(String name) {
        return name;
    }
    
}
@Service
public class PersonService {

    private PersonRepository personRepository;

    @Autowired
    public void setPersonRepository(PersonRepository personRepository) {
        this.personRepository = personRepository;
    }

    public String print(String name){
        return personRepository.printName(name);
    }
}
@Controller
public class PersonController {

    private final PersonService personService;

    @Autowired
    public PersonController(PersonService personService) {
        this.personService = personService;
    }

    @GetMapping("/person")
    public String person(@RequestParam("name") String name, Model model){

        personService.setPersonRepository(
                (a) -> "123"
        );

        String personName = personService.print(name);
        model.addAttribute("data", personName);
        return "person";
    }

}

 

먼저 @Service 에서는 PersonRepository를 의존성 주입받고 있다. 해당 인터페이스 구현체의 동작은 입력받은 것을 단순히 그대로 리턴하는 기능을 하고 있다. "홍길동" 이라고 입력을 받으면 "홍길동" 이라고 반환할 것이다.

 

컨트롤러 부분에서 우려하던 일이 발생한다.

- 초기에 주입받은 객체를 그대로 사용하면 문제가 없으나 Setter가 접근가능하기 때문에 임의로 수정될 수 있다. 

 

현재 컨트롤러에서 새로운 PersonRepository 를 만들어 서비스를 새로 설정하였다.

이는 처음 주입받은 객체의 동작과는 다르게 입력과 무관하게 "123"을 반환하게 된다.

해당 서비스를 사용하는 모든 객체들이 영향을 받아 무수히 많은 사이드 이펙트가 발생할 수 있다.

 

초기 의존성 주입받은 객체는 한번 할당 받으면 수정될 일이 거의 없다.

수정되는 경우가 있더라도 런타임 시 변경될 일이 아니라 수정된 객체를 다시 서버에 배포하여 적용하는 것이 해당 객체를 사용하는 다른 객체들에게 영향이 없을 것이다.

 

따라서 위의 Setter 주입보다는 생성자 주입 방법을 권장한다.

필드 주입(Field Injection)

필드 주입은 Setter 주입과 유사한 방식으로 이루어지기 때문에, Setter 주입의 단점이 필드 주입에 그대로 계승된다. 거기에 더하여 Setter 주입은 Setter를 호출해서 의존 객체를 변경할 수 있지만, 필드 주입은 초기 한 번 의존 객체를 주입 받으면 스프링 컨테이너 밖에서 변경이 어렵다.

 

의존성 주입 어노테이션을 해당 필드에 써주기만 하면 의존성 주입이 되어 가장 간편하다.

 

@Service
public class PersonService {

    @Autowired
    private PersonRepository personRepository;

    public String print(String name){
        return personRepository.printName(name);
    }
}

 

생성자 주입(Constructor Injection)

권장하는 의존성 주입 방식이다.

 

생성자를 통해 의존 관계를 주입하는 방법이다.

생성자 주입은 생성자의 호출 시점에 1회 호출 되는 것이 보장되며 final 키워드를 사용할 수 있다.

이는 의존성 주입된 서비스 객체를 컨트롤러 내부에서 바꿀 수 없음을 의미한다.(Immutable)

- final로 선언된 레퍼런스타입 변수는 반드시 선언과 함께 초기화가 되어야 하기 때문에 세 가지 방법 중 생성자 주입에서만 사용할 수 있다.

 

또한 순환참조 발생 시, 객체 생성 시점에서 발견할 수 있어 서비스 도중 문제가 발생하지 않고 문제부분을 바로 파악하여 수정할 수 있다.

 

@Service
public class PersonService {

    private final PersonRepository personRepository;

    @Autowired
    public PersonService(PersonRepository personRepository) {
        this.personRepository = personRepository;
    }

    public String print(String name){
        return personRepository.printName(name);
    }
}
@Controller
public class PersonController {

    private final PersonService personService;

    @Autowired
    public PersonController(PersonService personService) {
        this.personService = personService;
    }

    @GetMapping("/person")
    public String person(@RequestParam("name") String name, Model model){

        String personName = personService.print(name);
        model.addAttribute("data", personName);
        return "person";
    }

}

 

 

[참고자료]

스프링 - 생성자 주입을 사용해야 하는 이유, 필드인젝션이 좋지 않은 이유

스프링 순환 참조(Circular Reference)

[Spring] 생성자 주입을 사용해야 하는 이유

반응형

+ Recent posts