티스토리 뷰

공부를 하면서 객체를 주입 받을때 주입 받는 방법은 총 3가지 방법이 존재한다.

나는 @Autowired 를 이용하여 필드에 붙이는 방식이 편하여 그동안 이렇게 주입했었는데

이 방법은 좋지 못한 방법이라는 글을 보았다. 그래서 정리하고자 한다.

 

| 생성자 주입(Constructor Injection)


단일 생성자인 경우에는 @Autowired 어노테이션 조차 붙이지 않아도 되지만 생성자가 2개 이상인 경우에는 생성자에 어노테이션을 붙여주어야 한다.

 

예를들어 단일 생성자 같은 경우

@Component
public class MadExample {

    // final로 선언할 수 있는 보너스
    private final HelloService helloService;

    // 단일 생성자인 경우는 추가적인 어노테이션이 필요 없다.
    public MadExample(HelloService helloService) {
        this.helloService = helloService;
    }
}

 

 

| 필드 주입(Field Injection)

 

필드주입 같은 경우 사용법이 매우 간단하고 내가 자주 사용했던 방법이다.

@Autowired 어노테이션을 붙여주면 자동으로 의존성이 주입이 된다. 이 방식을 고수했던 이유는 편리하기 때문에 고집했다.

@Component
public class fieldExample
{
	//filed 주입(@Autowired)을 통한 주입
	@Autowired
    private example example;
    
}

 

 

| 수정자 주입 (Setter Injection)

수정자(Setter)를 이용한 주입 방법이다.

꼭 setter 메서드일 필요는 없는데 메서드 이름이 수정자 네이밍 패턴인 (setXxx)가 아니어도 동일한 기능만 하면 됩니다.

보통은 일관성과 코드를 명확하게 하기 위하여 정확한 이름 사용을 권장드립니다.

 

@Component
public class Example
{
	private Example example;
    
    //setter 를 이용한 방식
    @Autowired
    public void setExample(Example example)
    {
    	this.exampleService = exampleService;
     }
     
 }

 

 

이렇게 3가지 방법이 존재합니다.

 

 

다시 본론으로 돌아와 @Autowired 어노테이션을 냅두고 왜 왜 !! 생성자 주입 방법을 권장할까?

 

| 생성자 주입 방법이 주는 장점

  • 순환 참조를 방지할 수 있다.
    • 순환 참조가 발생하는 경우 애플리케이션이 구동되지 않는다.
  • 테스트 코드 작성이 편리하다.
    • 단순 POJO를 이용한 테스트 코드를 만들 수 있다.
  • 나쁜 냄새를 없앤다.
    • 조금 더 품질 좋은 코드를 만들 수 있다.
  • immutable 하다.
    • 실행 중에 객체가 변하는 것을 막을 수 있다.
    • 오류를 사전에 방지할 수 있다.

 

1.순환 참조를 방지할 수 있다.

 

개발을 하다 보면 여러 컴포넌트 간에 의존성이 생기는데 그중에서도 A가 B를 참조하고 B가 다시 A를 참조하는 순환 참도 발생할 수 있습니다.

빈이 생성된 후에 비지니스 로직으로 인해 서로의 메서드를 순환 참조하는 형태입니다.

 

Test1이 test2를 순환 참조 하는 경우

@Service
public class Test1
{

	//순환 참조
    @Autowired
    private Test2 test2;
    
    //Test1 class 메소드 에서 Test2 class의 메소드를 호출하고 있다.
    public void test1Method()
    {
    	test2.test2Method()
     }
     
  }

Test2가 test1을 순환 참조 하는 경우

public class Test2
{

		@Autowired
        private Test1 test1;
        
        public void test2Method()
        {
        	Test1.test1Method();		//Test2 클래스에서 Test1 클래스의 메소드 호출 즉 순환참조 되는 상황
         }
 }

 

이런 상태에서 실행 할 경우 다음과 같은 오류메시지가 전달된다. (실행이 된다는 점!! 유의하자  )

java.lang.StackOverflowError: null

여기서 유의 깊게 봐야할 것은 애플리케이션이 실행시 아무런 오류나 경고가 없이 구동이 된다는 것이다.

( 코드가 호출되기 전까지 문제를 발견할 수 없다.)

 

 

그렇다면 생성자 주입을 할 경우에는 ?

@Service
public class Test1
{
	//final 선언
	private final Test2 test2;
    
    public Test1(Test2 test2)
    {
    	this.test2 = test2;
     }
 }
@Service
public class Test2
{
	//final 선언
	private final Test1 test1;
    
	public Test2(Test1 test1)
    {
    	this.test1=test1;
     }
     
}

 

이런 경우에는  BeanCurrentlyInCreationException 오류가 발생하여 애플리케이션이 실행조차 되지 않는다.

(사전에 오류를 방지할 수 있다.)

 

| 실행을 똑같이 했는데 왜 오류가 나고 안나고 차이점이 뭘까?

->빈을 주입하는 순서가 다르기 때문이다.

 

 

수정자 주입(Setter Injection)은 주입 받으려는 빈의 생성자를 호출하여 빈을 찾거나 BeanFactrory에 등록을 한다.

그 후 생성자 인자에 사용하는 빈을 찾거나 만든다. 다음 주입하려는 빈 객체의 수정자를 호출하여 주입한다.

 

필드주입(Field Injection)은 수정자 주입 방법과 동일하게 먼저 bean을 생성 후 어노테이션이 붙은 필드에 해당하는 bean을 찾아 주입하는 방법이다. setter Injection과 같이 bean을 먼저 생성 후 필드에 대해 주입한다.

 

반면,

생성자 주입(Constructor Injection)은 생성자로 객체를 생성하는 시점에 필요한 bean을 주입한다.

생성자의 인자에 사용되는 bean을 찾거나 beanFactory에 만든다. 그 후 찾은 인자 bean으로 주입하려는 bean의 생성자를 호출한다.

즉, 먼저 빈을 생성하지 않는다. 

 

그렇기 때문에 순환 참조는 생성자 주입에서만 문제가 된다.


객체 생성 시점에 bean을 주입하기 때문에 서로 참조하는 객체가 생성되지 않은 상태에서 그 bean을 참조하기 때문에 오류가 발생한다.

 

 

 

 

( * Lombok을 이용할 경우 다음과 같은 어노테이션을 사용해서 생성자를 만들지 않아도 된다는 점 참고 바랍니다. )

 

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

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

 

'Spring' 카테고리의 다른 글

@RequestParam과 @PathVariable?  (0) 2020.07.25
[Spring]ResponseEntity  (0) 2020.03.11
[Spring Test]-IntStream의rangeClosed  (0) 2020.03.10
[Spring]- UriComponentsBuilder란?  (0) 2020.03.09
[Spring]addAttribute와 addFlashAttribute 차이점  (0) 2020.03.08