2-4 의존주입 사용하기 - @Autowired 사용법

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

2-4 의존주입 사용하기 - @Autowired 사용법

이번 예제에서는 실제 의존주입을 사용하는 방법을 알아보겠습니다.

Ex06Autowired 프로젝트를 만들어보겠습니다. Group은 com.study, Artifact와 Name은 Ex06Autowired이고, Package name은 com.study.springboot로 지정한 후 zip을 다운로드 합니다. 나머지 과정은 전과 동일합니다.

resources폴더의 application.properties에 다음 내용을 추가하고 저장합니다.

server.port=8090

이번 예제는 인터페이스 파일과 클래스 파일을 만들어야 합니다. 생성된 모습은 아래와 같습니다.

클래스 이름은 MainController, Member, CardA, CardB이고 인터페이스의 이름은 ICard입니다. com.study.springboot폴더에서 우클릭 후 메뉴에서 java 클래스를 생성할 때 클래스 또는 인터페이스를 선택하면 만들 수 있습니다.

먼저 ICard 인터페이스 코드입니다. 예제 URL : https://bit.ly/3s78kl9

package com.study.springboot;

public interface ICard {
    //가상함수(추상화 메서드)
    public void buy(String itemName);
}

인터페이스는 가상함수(추상화 메서드)를 가지고 있습니다. 가상함수란 코드 실행부분은 가지고 있지 않고 선언만 되어 있는 함수 또는 메서드를 말합니다. 이 책에서는 함수와 메서드를 동일한 의미로 사용하겠습니다.

ICard라는 인터페이스는 buy라는 추상화 메서드를 하나 가지고 있지만 실행부분의 코드는 없는 상황입니다.

ICard 인터페이스를 구현하는 CardA와 CardB 클래스를 만들어 보겠습니다. 예제 URL : https://bit.ly/3yVhogK, https://bit.ly/3gn8p1c

package com.study.springboot;

import org.springframework.stereotype.Component;

//@Component 어노테이션 : 클래스를 자바 Bean으로 등록해줌.
//@ComponentScan으로 @Component으로 등록된 클래스를 찾아서 Bean으로 등록.
//Bean 객체의 이름은 cardA이다.
@Component("cardA")
public class CardA implements ICard {

    @Override
    public void buy(String itemName) {
        System.out.println("CardA " + itemName + "을 샀다.");
    }

}
package com.study.springboot;

import org.springframework.stereotype.Component;

//@Component 어노테이션 : 클래스를 자바 Bean으로 등록해줌.
//@ComponentScan으로 @Component으로 등록된 클래스를 찾아서 Bean으로 등록.
//Bean 객체의 이름은 cardB이다.
@Component("cardB")
public class CardB implements ICard {

    @Override
    public void buy(String itemName) {
        System.out.println("CardB " + itemName + "을 샀다.");
    }

}

CardA와 CardB는 동일하게 ICard 인터페이스를 구현하며, 가상함수인 buy를 오버라이드(Overridd - 메소드 재정의)하여 실행코드를 구현했습니다. buy 메서드는 상품이름을 매개변수로 받아 "~을 샀다"라고 출력합니다. @Component("Bean이름")라고 기술하여, 자바 Bean으로 cardA와 cardB라는 이름으로 각각 등록됩니다.

Member 클래스를 추가합니다. 예제 URL : https://bit.ly/3eJ3zuO

package com.study.springboot;

import org.springframework.stereotype.Component;

//회원정보 클래스
//@Component : 클래스를 Bean으로 등록해준다.
//Bean의 이름은 클래스이름의 첫글자를 소문자로 등록된다 : "member"
@Component
public class Member {
    private String name;
    private ICard iCard;

    //기본생성자 안 만들어주면 Bean 등록이 안됨.
    public Member(){

    }
    public Member(String name, ICard iCard) {
        this.name = name;
        this.iCard = iCard;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public ICard getiCard() {
        return iCard;
    }

    public void setiCard(ICard iCard) {
        this.iCard = iCard;
    }
}

Member 클래스는 필드(멤버 변수)로 name과 iCard를 가지고 있습니다. iCard는 클래스가 아니라 인터페이스입니다. 그리고 기본생성자와 필드가 있는 생성자, getter, setter 메서드가 있습니다. 이전처럼 인텔리제이의 생성자, getter, setter 자동생성 메뉴를 이용하셔도 됩니다.

MainController 클래스를 추가하겠습니다. 예제 URL : https://bit.ly/3ER8q81

package com.study.springboot;

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

//@Controller 어노테이션 : 사용자의 요청(http request)을 받는다.
@Controller
public class MainController {

    //@GetMapping 어노테이션 : http Get 요청을 처리한다.
    @GetMapping("/")
    //@ResponseBody 어노테이션 : 문자열로 응답한다.
    @ResponseBody
    public String main() {
        return "Spring Web App입니다.";
    }
}

@Controller 어노테이션은 MainController 클래스가 사용자의 요청 즉 HTTP request를 받을 수 있도록 해줍니다. @GetMapping 어노테이션은 사용자의 요청 중 Get요청에 대한 경로와 응답 메서드를 정의합니다. "/"는 루트 경로를 의미합니다. URL로 말하면, localhost:8090/ 입니다. 맨 뒤의 /는 루트경로 또는 프로젝트 기본 경로를 의미합니다. @ResponseBody 어노테이션은 HTTP 응답을 문자열로 변환하여 반환합니다.

Ex06AutowiredApplication 클래스의 main 메서드를 실행하고 크롬 브라우저로 다음과 같은 URL로 호출해 보겠습니다.

localhost:8090/

아래와 같이 "Spring Web App입니다." 문자열을 출력하면 정상적으로 서버가 작동하고 있다는 뜻입니다.

이제 본격적으로 의존주입을 위해 Autowired 어노테이션을 사용해 보겠습니다.

MainController클래스 아랫줄에 필드 주입을 받고자 하는 Member 클래스 객체를 선언합니다. 그리고 @Autowired 어노테이션을 통해 스프링으로부터 주입(생성된 객체 할당)을 받습니다.

생략...

@Controller
public class MainController {
    //@Autowired 어노테이션 : @Component로 등록된 Bean을 주입(DI)한다. 
    //                      ApplicationContext클래스의  getBean 메서드 역할
    //필드 주입
    @Autowired
    Member member1;
    
    생략...
}

개발자는 직접 Member member1 = new Member(); 이렇게 new를 통해 객체를 생성하지 않고 @Component가 기술된 클래스의 객체를 제공받을 수 있습니다. 이렇게 직접 new해서 사용하는 것을 강한 결합이라고도 합니다. 반면 주입 받는 것을 약한 결합이라고 합니다. 강한 결합에 비해 약한 결합은 향후 클래스의 변경시 서로 영향을 적게 받고 오류가 일어나 경우를 줄여줍니다.

반복적으로 얘기하면 주입의 3가지 형태는 다음과 같습니다.
1.필드(field) 주입 : 가장 기본적인 형식, final을 사용불가
2.수정자(setter) 주입 : final을 사용불가
3.생성자(constructor) 주입 : 최근에 추천되는 방식, final 사용가능

Member 객체를 하나 더 주입 받아 보겠습니다. 이때 입문자가 자주 실수하는 것이 아래와 같이 사용하는 코드가 있습니다.

//필드 주입
@Autowired
Member member1; //주입됨.
Member member2; //null

이렇게 @Autowired를 하나만 선언하면, member1만 주입받고 member2는 주입받지 못하고 null인 상태로 남아있게 됩니다.
@Autowired는 따로따로 선언해 줘야 정상적으로 주입받게 되니 주의하세요!

//필드 주입
@Autowired
Member member1; //주입됨
@Autowired  //@Autowired는 따로따로 써야 됨.
Member member2; //주입됨

@Autowired로 생성된 Bean을 사용해 보겠습니다. 아래 코드를 추가합니다. 예제 URL : https://bit.ly/3ER8q81

생략...

@Controller
public class MainController {
	//필드 주입
    @Autowired
    Member member1;
    @Autowired  //@Autowired는 따로따로 써야 됨.
    Member member2;
    
    @GetMapping("/")
    @ResponseBody
    public String main() {
        return "Spring Web App입니다.";
    }

	//<추가된 부분
    //호출 URI : http://localhost:8090/member
    @GetMapping("/member")
    @ResponseBody
    public String member() {
        //member1 Bean을 사용하기
        member1.setName("홍길동");
        System.out.println( member1.getName() );

        if( member1 == member2 ) {
            System.out.println("서로 같은 객체임.");
        }

        return "member함수 호출됨.";
    }
    //추가된 부분>
}

저장 후 재실행한 후 아래 URL을 웹브라우저에서 요청해 보겠습니다.

localhost:8090/member

그러면 실행뷰에 아래의 내용이 찍히고 웹브라우저도 정상출력 될 것 입니다.

홍길동
서로 같은 객체임.

member1 Bean을 잘 주입받았고, member1과 member2는 주소값이 동일하므로 "서로 같은 객체임." 이라고 출력하였습니다.

인터페이스 객체를 주입받아 보겠습니다. 아래와 같이 코드를 추가합니다. 예제 URL : https://bit.ly/3ER8q81

생략...
public class MainController {

    생략...
    public String member() {
        생략...

        return "member함수 호출됨.";
    }

	//<추가된 부분    
    @Autowired
    //@Component로 등록된 PrinterA, PrinterB중에 @Qualifier를 이용하여 주입할 객체를 선택
    //선언하지 않으면 어떤 것을 주입해야 할지 몰라서 오류 발생함.
    @Qualifier("cardA")
    ICard iCard;
    //호출 URI : http://localhost:8090/card
    @RequestMapping("/card")
    @ResponseBody
    public String card() {
        //member 빈을 사용하기
        member1.setiCard( iCard );
        member1.getiCard().buy("콩나물");

        return "card함수 호출됨.";
    }
    //추가된 부분>
}

한번 아래와 같이 @Qualifier 부분을 주석처리하고 실행해 보겠습니다.

 	@Autowired
    //@Qualifier("cardA")
    ICard iCard;

그러면 아래와 같이 에러가 발생합니다. 내용을 보면 iCard 구현객체를 주입하려는데 CardA, CardB 중에 어떤 것을 주입해야 할 지 모르겠다는 에러입니다.

Field iCard in com.study.springboot.MainController required a single bean, but 2 were found:
	- cardA: defined in file [생략...\com\study\springboot\CardA.class]
	- cardB: defined in file [생략...\com\study\springboot\CardB.class]

이때 @Qualifier 어노테이션을 통해 주입할 객체이름을 입력해주면, 선택적인 객체 주입이 가능합니다. 주석을 제거하고 다시 실행해 보겠습니다. 요청 URL은 http://localhost:8090/card 입니다.

 	@Autowired
    @Qualifier("cardA")
    ICard iCard;

정상적으로 실행뷰(콘솔)에 아래 문자열이 출력될 것입니다.

생략...
CardA 콩나물을 샀다.

CardA로 콩나물을 샀습니다. CardB 클래스객체로 콩나물을 사고 싶으면, 아래와 같이 코드를 수정합니다.

 	@Autowired
    @Qualifier("cardB")
    ICard iCard;

이제까지 @Autowired를 통한 필드 주입에 대해 살펴보았습니다. 아마 스프링을 처음 접하시는 분들은 표현들이 낯설고 익숙치 않을 것입니다. 자주 코드를 접하면 이런 낯섦도 사라지니 계속해서 노력하시면 될 것 같습니다.

profile
강의하는 개발자

0개의 댓글