2-3 의존주입 받는 방법들

코딩강사·2022년 10월 18일
post-thumbnail

2-2 의존주입 받는 방법들

스피링 객체(빈 Bean)을 생성하고 주입(제공)받기 위한 방법은 크게 2가지입니다.

  1. @Bean 객체
  2. @Component 객체

@Configuration 클래스와 @Bean 메서드 객체는 이전 장에서 배웠습니다.

일반적으로 Bean을 생성하기 위해서는 @Component가 더 많이 사용됩니다.
그럼 @Bean과 @Component는 어떻게 다른가요?

@Bean은 개발자가 수정이 불가능한 외부 라이브러리들을 Bean으로 등록하고 싶은 경우에 사용합니다. @Configuration를 선언한 클래스에서 사용하며 메소드에만 넣을 수 있습니다. 보통은 메소드 이름이 곧 Bean 이름으로 탄생합니다.

다음 예제는 @Bean의 용도를 설명하기 위한 스프링 배치(Batch) 예제입니다. JobBuilderFactory 외부 클래스를 가져다 exampleJob Bean을 만들때 사용합니다. @Bean은 이런 용도입니다.

@Configuration
public class AppConfig {
	@Autowired public JobBuilderFactory jobBuilderFactory;

    @Bean
    public Job ExampleJob(){
        Job exampleJob = jobBuilderFactory.get("exampleJob")
                .start(step())
                .build();
        return exampleJob;
    }

    @Bean
    public Step step() {
        //코드생략...
    }
}

@Component은 개발자가 직접 만든 수정이 가능한 클래스들인 경우에 사용하고, @Bean에 등록하지 않아도 자동 주입이 가능하도록 해주는 어노테이션입니다.

이제부터는 앞으로 주로 사용하게 될 @Component에 집중적으로 알아보도록 하겠습니다.

주입이란 용어는 스프링 프레임워크가 자바 객체 즉 Bean을 자동생성하고 제공받는 것을 의미합니다.

@Component로 Bean을 주입받는 방법은 크게 3가지 방법이 있습니다.

1.필드(field) 주입 : 일반적인 방법
2.수정자(setter) 주입
3.생성자(constructor) 주입 : 추천하는 방법

필드(field) 주입은 가장 기본적인 형식이며 @Autowired를 이용하여 간단하게 의존주입(객체생성)을 받을 수 있습니다.

@Component
public class MyClass {
	//필드 주입, final 사용불가
	@Autowired
    private Member member; // 스프링 역할 : Member member = new Member();
}

하지만 final 예약어를 사용할 수 없습니다. final은 자바에서 한번 값이 설정되면 또다시 값을 할당할 수 없도록 하는 상수(const) 예약어입니다. final을 통해 한번 설정된 객체변수에 다시 객체가 할당될 수 없도록 객체 불변성을 지원할 수 있습니다.

수정자(setter) 주입은 setter 메서드를 통한 객체 주입입니다. @Autowired를 setter 메서드에 등록해서 사용합니다. final 예약어를 사용할 수 없습니다. setter 메서드의 매개변수에 스프링이 자동으로 member객체를 생성(new)해서 주입해줍니다.

@Component
public class MyClass {
	//수정자 주입, final 사용불가
 	private Member member;
	
	@Autowired
    public void setMember(Member member) { // 스프링 역할 : Member member = new Member();
        this.member = member;
    }
}

생성자(constructor) 주입은 생성자를 통한 주입입니다. 위와 달리 final 예약어를 사용할 수 있습니다.

@Component
public class MyClass {
	//생성자 주입, final 사용가능
 	private final Member member;
	
    @Autowired
    public MyClass(Member member) { // 스프링 역할 : Member member = new Member();
        this.member = member;
    }
}

주입받는 방법인 필드, 수정자, 생성자 주입 중 가장 추천하는 방식은 생성자 주입입니다. 이유는 3가지 장점이 있습니다.

  1. final 선언이 가능하다.
  2. 순환 참조 방지가 된다.
  3. 테스트 코드 작성이 쉽다.

final 선언으로 객체를 재할당하는 것을 방지할 수 있습니다. 또한 순환 참조를 방지할 수 있습니다.

순환 참조란 예를 들어 A클래스가 B클래스 객체를 주입받기 원하고 B클래스가 A클래스를 주입받기 원할 때를 순환참조라 합니다. 이때는 어떤 객체도 준비되지 못해 순환참조의 오류에 빠집니다. 닭이 먼저인가? 달걀이 먼저인가? 하는 모순과도 같습니다. 생성자 주입을 통해 순환 참조를 방지할 수 있습니다.

@Component
public class ClassA {
   @Autowired
   private ClassB b;
   
   public void doit() {
	   b.doit();
   }
}
@Component
public class ClassB {
	@Autowired
   private ClassA a;
   
   public void doit() {
	   a.doit();
   }
}

또한 @Test로 테스트 코드를 작성할 때 Mockito로 Bean을 주입받지 않아도, 단순히 원하는 객체를 생성한 후, 생성자에 넣어 테스트하면 됩니다.

public class ClassATest {
    @Test
    public void test() {
        ClassB b = new ClassB();
        ClassA a = new ClassA(b);
        a.doit();
    }
}

결론적으로 이야기 하면 가급적 생성자 주입을 사용하고, 간단하게 주입을 사용할 때는 필드주입을 사용하면 됩니다.

자 이제 예제를 통해 3가지 주입 방법을 코드로 확인해 보겠습니다.
Ex05DIType 프로젝트를 만들되 Group은 com.study로, Artifact와 Name을 Ex05DIType로, Package Name을 com.study.springboot로 생성하겠습니다. 나머지 설정은 이전 예제와 동일합니다. GENERATE를 클릭 후 다운로드 한 후 내문서>springboot폴더로 이동 후 여기서 풀기를 한 후 인텔리제이로 열기를 합니다.

resources폴더의 application.properties에 다음 내용을 추가하고 저장합니다. 서버 포트 설정은 이 책에서는 모든 프로젝트에 동일하게 8090포트로 들어갑니다.

server.port=8090

com.study.springboot 폴더에 Member 클래스를 생성하겠습니다. 인텔리제이의 자동 생성 기능을 이용하면, 생성자와 getter/setter 메서드를 쉽게 만들 수 있습니다. private String name = "사임당"; 아랫줄에서 우클릭 후 생성 메뉴를 클릭합니다.

package com.study.springboot;

import org.springframework.stereotype.Component;

@Component
public class Member {
    private String name = "사임당";

	//<-여기서 우클릭 후 생성 메뉴를 이용합니다.	
}

기본생성자는 선택 안함 버튼을, 필드가 있는 생성자는 필드 선택 후 확인을 클릭합니다. 기본생성자를 안 만들어주면 Bean 등록이 안되니 만들어주겠습니다.

getter 및 setter 메뉴를 이용하여 getter와 setter 메서드를 생성합니다.

완성된 클래스의 내용은 아래와 같습니다.
예제 URL : https://bit.ly/3gdD3du

package com.study.springboot;

import org.springframework.stereotype.Component;

@Component

public class Member {
    private String name = "사임당";

    //기본생성자 
    //기본생성자 안 만들어주면 Bean 등록이 안됨.
    public Member() {
    }
    //필드가 있는 생성자
    public Member(String name) {
        this.name = name;
    }
    //getter
    public String getName() {
        return name;
    }
    //setter
    public void setName(String name) {
        this.name = name;
    }
}

com.study.springboot 폴더에 MainController 클래스를 생성하겠습니다. 아래와 같이 코드를 추가하겠습니다. 예제 URL : https://bit.ly/3VwSduF

package com.study.springboot;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class MainController {

    @GetMapping("/")
    @ResponseBody
    public String main(){
        return "스프링 웹 애플리케이션";
    }

}

코드 설명
@Controller은 내부에 @Component 어노테이션을 가지고 있어서, 개발자가 직접 개발한 클래스를 Bean에 등록할 경우 사용합니다.
@GetMapping("/")은 루트 경로의 HTTP GET요청에 대한 응답 메서드를 정의합니다.
@ResponseBody은 응답 메소드 main의 반환값을 문자열로 응답한다. 웹 브라우저는 "스프링 웹 애플리케이션"이라는 문자열을 받을 것입니다.

Ex05DiTypeApplication 클래스의 main함수를 실행하겠습니다.

크롬 웹브라우저에서 아래 주소로 서버 요청을 해보겠습니다.

localhost:8090

웹브라우저에 정상적으로 문자열이 출력되었다면 우리의 스프링부트 서버가 잘 동작한 것입니다.

이제 필드 주입, 수정자 주입, 생성자 주입을 차례로 코드를 추가해 볼 것입니다. 먼저 가장 일반적으로 사용하는 필드 주입 코드를 추가합니다. 예제 URL : https://bit.ly/3VwSduF

package com.study.springboot;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class MainController {

    @GetMapping("/")
    @ResponseBody
    public String main(){
        return "스프링 웹 애플리케이션";
    }

	//<추가된 부분
    //1. 필드주입, final 사용불가
    @Autowired
    private Member member1; //스프링 역할 : Member member1 = new Member();

    @GetMapping("/field")
    @ResponseBody
    public String field(){

        System.out.println( member1.getName() );

        return "field() 호출됨.";
    }
	//추가된 부분>
}

코드 설명
@Autowired은 아래 기술된 클래스 객체를 주입받습니다. 즉 스프링은 @Component가 붙은 클래스의 @Autowired을 찾아서 Member member1에 Member클래스 객체를 생성해줍니다.

저장 한 후 다시 실행하고 크롬 웹브라우저에서 아래 주소로 서버 요청을 해보겠습니다.

localhost:8090/field

field() 호출됨 이라고 웹브라우저에 출력되고, 출력뷰(콘솔창)에 사임당 이라는 글자도 보일 것입니다.

필드 주입은 클래스 멤버 변수만 선언해주고 @Autowired만 기술해주면 되어서 간단히 주입받을 때 사용할 수 있습니다.

field() 메서드 아래에 아래 코드를 추가해 보겠습니다. 예제 URL : https://bit.ly/3VwSduF

package com.study.springboot;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class MainController {

	//생략...
      
    @GetMapping("/field")
    @ResponseBody
    public String field(){
        System.out.println( member1.getName() );
        return "field() 호출됨.";
    }

	//<추가된 부분
    //2. 수정자 주입, final 사용불가
    private Member member2;

    @Autowired
    public void setMember(Member member) {  //스프링 역할 : Member member1 = new Member();
        this.member2 = member;
    }

    @GetMapping("/setter")
    @ResponseBody
    public String setter(){

        System.out.println( member2.getName() );
        return "setter() 호출됨.";
    }
	//추가된 부분>
}

수정자 주입은 setMember라는 setter 메서드를 선언해주고, @Autowired를 위에 기술합니다. 스프링에서는 메서드의 매개변수로 @Compont로 등록된 클래스 객체가 들어오면, 자동으로 주입을 해줍니다.

저장 한 후 다시 실행해서 크롬 웹브라우저에서 아래 주소로 서버 요청을 해보겠습니다.

localhost:8090/setter

실행뷰에 사임당이라고 찍히면, 정상적으로 주입이 된 것을 알 수 있습니다.

마지막으로 생성자 주입을 구현해보자. 아래 코드를 추가합니다. 예제 URL : https://bit.ly/3VwSduF

package com.study.springboot;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class MainController {

    //생략...

    @GetMapping("/setter")
    @ResponseBody
    public String setter(){
        System.out.println( member2.getName() );
        return "setter() 호출됨.";
    }

	//<추가된 부분
    //3. 생성자 주입, final 사용가능
    private final Member member3;

    @Autowired
    public MainController(Member member) { //스프링 역할 : Member member1 = new Member();
        System.out.println("생성자 주입!");
        this.member3 = member;
    }

    @GetMapping("/constructor")
    @ResponseBody
    public String constructor(){
        System.out.println( member3.getName() );
        return "constructor() 호출됨.";
    }
    //추가된 부분>
}

코드를 보면 MainController의 생성자에 member 클래스 변수를 넣으면, 스프링은 자동주입을 해줍니다. 이때 생성자는 final Member member3; 선언을 통해 final 예약어를 사용할 수 있습니다.

저장 한 후 다시 실행해서 크롬 웹브라우저에서 아래 주소로 서버 요청을 해보겠습니다.

localhost:8090/constructor

생성자 주입을 final을 사용 가능하므로, 향후 롬복(Lombok) 라이브러리를 통해 @RequiredArgsConstructor을 사용하면 코드를 절약할 수 있습니다. @RequiredArgsConstructor 어노테이션은 final이 붙거나 @NotNull이 붙은 필드의 생성자를 자동 생성해줍니다. 롬복을 다룰 때 함께 살펴볼 예정입니다.

@NotNull은 스프링 유효성(Validation) 라이브러리에서 null값이 입력되지 않도록 제한하는 어노테이션입나다.

스프링에서는 생성자 주입을 주로 권장하고 있습니다.

이제까지 세가지 주입 방법을 살펴보았습니다.

profile
강의하는 개발자

0개의 댓글