[Spring] Dependency Injection을 하는 다양한 방법!(@Autowired, Setter 주입, 생성자 주입)

안녕하세요. 오늘은 그동안 사용해왔던 여러가지 Dependency Injection 방법들에 대해서 소개해보고자 합니다.

 

일단 Dependency Injection이 무엇인지부터 이야기가 선행되어야 할텐데요.

 

다들 아시겠지만 스프링에서의 가장 기본적인 개념이 되겠고, 특정 계층(컨트롤러, 서비스 등)에 선언된 local 변수들의 인스턴스를 스프링 컨테이너 내에서 Bean이라는 객체로 관리되게 됩니다.(관리하여 주입해줄 객체) 

 

한글로는 의존객체라고하며 A라는 클래스에 B라는 객체가 로컬변수로 선언되어 B의 메소드가 사용되고 있을 때, B가 없으면 A 클래스의 인스턴스가 생성될 수 없기 때문에 A는 B에 의존한다라고 합니다.

 

따라서 스프링은 프레임워크이기 때문에 싱글톤으로 이러한 A, B라는 Bean 객체를 스프링 컨테이너에서 관리하도록 합니다. (싱글톤은 인스턴스 1개로 관리되는 패턴입니다.)

 

이러한 개념에서 Dependency Injection이 필요로해지게 됩니다.

 

Dependency Injection 방법들

아마 대부분의 회사나 학원이나 개인이 사용하는 방법일텐데요. 문법이 굉장히 단순합니다. 우리가 흔히 사용하는 @Autowired 라는 어노테이션이 여기에서 사용됩니다.

 

@Autowired를 사용한 DI(타입 주입)
@Service
public class MemberService {

    @Autowired
    private MemberRepository memberRepository;
    
}

가장 기본적인 필드주입 방법으로 Bean 컨테이너에 존재하는 객체들중 @Autowired에 붙여진 객체와 같은 타입의 인스턴스를 주입해주는 방법입니다. 타입만 같다면 그냥 주입해줍니다. 그런데 같은 타입이 2개라면 문제가 발생합니다.

 

다음의 상황을 보시죠.

 

@Configuration
public class AppConfig {

    @Bean(name = "modelMapperMain")
    public ModelMapper modelMapper() {
        return new ModelMapper();
    }

    @Bean(name = "modelMapperSub")
    public ModelMapper modelMapperSub(){
        return new ModelMapper();
    }
}

DataSource를 이용한 예제가 가장 적당하다고 생각했지만 보다 쉬운 예제를 위해서 자주쓰이는 ModelMapper를 예로 들었습니다. 특정한 상황에 쓰이는 두가지 ModelMapper가 있다고 할때! 그저 타입으로만 주입하는 @Autowired는 에러를 내게 됩니다. 

 

한개의 빈이 필요한데 2개가 발견되었습니다.

 


 

@Autowired를 사용한 DI(이름 주입)

이럴 때는 주로 @Qualifier 어노테이션을 활용해서 Bean Name으로 접근합니다.

바로 위의 코드에서 @Bean(name = "") 속성을 이용해 Bean 에 이름을 각각 설정해주었습니다.

@Controller
public class XXXController {

    @Qualifier("modelMapperMain")
    private ModelMapper modelMapper;
    
}

다음과 같이 주입해주면 modelMapperMain이라는 이름을 가진 Bean을 주입해주게 됩니다.

 


Setter 주입

최근에는 위험성 때문에 잘 사용되는 방법은 아니지만 그래도 있는 방법이기 때문에 공유합니다. 

 

보통 VO나 DTO 같은 Model 객체들은 Getter/Setter가 거의 필수적으로 들어가게 되는데, 스프링에서는 Bean을 주입할 때 Setter를 통해서 주입해주는 경험을 제공합니다.

 

@Autowired
public void setModelMapper(ModelMapper modelMapper) {
  System.out.println("modelMapper 주입");
  this.modelMapper = modelMapper;
  System.out.println(modelMapper.toString());
}

@Autowired 는 빼도되고 안빼셔도 됩니다. 

 

잘 주입됐네요!

 


 

생성자 주입

최근에 가장 스프링측에서 추천하는 DI 방법으로 생성자로 인스턴스를 생성할 때 주입시켜주는 방법입니다. 

 

@Controller
public class XXXController {

    private XXXRepository xxxRepository;
    private ModelMapper modelMapper;
    private XXXValidator xxxValidator;

    public EventController(EventRepository xxxRepository, 
                           ModelMapper modelMapper, 
                           EventValidator xxxValidator) {
                           
        this.xxxRepository = xxxRepository;
        this.modelMapper = modelMapper;
        this.xxxValidator = xxxValidator;
    }
}

 


 

Lombok 라이브러리를 많이들 사용하시니까 Lombok 라이브러리의 @AllArgsConstructor를 이용한 주입방법도 있습니다.

 

@AllArgsConstructor를 통한 주입

@AllArgsConstructor
@Controller
public class XXXController {

    private XXXRepository xxxRepository;
    private ModelMapper modelMapper;
    private XXXValidator xxxValidator;

}

@AllArgsConstructor는 해당 클래스의 모든 필드들을 초기화하는 생성자를 만듭니다. 결과적으로 컴파일을 하고 나면 바로 위의 코드와 같은 모습입니다.

 


 

@RequiredArgsConstructor를 통한 주입

하지만 저는 여기서 더 나아가서 final 키워드를 사용한 @RequiredArgsConstructor 어노테이션을 이용한 주입방법을 소개해드리고자 합니다.

 

먼저 소스를 보면

 

@RequiredArgsConstructor
@Controller
public class XXXController {

    private final XXXRepository xxxRepository;
    private final ModelMapper modelMapper;
    private final XXXValidator xxxValidator;

}

@RequiredArgsConstructor 어노테이션은 final 키워드가 붙은 필드들을 생성자 parameter로 선언하여 생성자를 만들게 됩니다.

 

약간 @AllArgsConstructor 를 통한 방법과 차이가 없어보이지만 엄청난 차이가 존재합니다. 바로 필드들이 모두 final로 선언되기 때문에 딱 한번만 초기화되고 더이상 변경될 여지가 없어지게 됩니다.

 

위에 적어둔 모든 방법들은 모두 final이 아니기 때문에 변경될 여지가 있는 필드들입니다.

 

굳이?? 라고 생각이 들 수도 있지만 프로그래머라면 그렇게 생각해서는 안된다고 느끼기에..

 

어차피 싱글톤으로 객체가 관리되고 필드에 Injection되는 객체들은 한번만 초기화되고 나면 건들여서는 안될 존재들입니다.

 

그 가능성을 제거해주니 좀더 secure한 코딩스타일인것 같습니다. 다른 주입방법들보다 코드 Line도 많이 잡아먹는 스타일이 아니기도 하구요!!

 

 


이상 Dependency Injection을 하는 여러가지 방법들에 대해서 공유해보았습니다.!

 

 

댓글

Designed by JB FACTORY