티스토리 뷰

study/Spring

chap 04 - 의존 자동 주입

xoxowo 2023. 1. 4. 19:12

초보 웹 개발자를 위한 스프링 5 프로그램 입문을 보고 복습 겸 개인 학습 정리입니다. (windows 기준)

 

 

@Autowired를 이용한 의존 자동 주입

기존 생성자 또는 메서드를 이용해 의존을 주입했었다면, @Autowired 애노테이션을 이용하면 스프링이 자동으로 빈 객체를 찾아 주입해 준다.

 

자동 주입할 대상이 필수가 아닌 경우 @Autowired 애노테이션의 required 속성을 false로 지정한다. @Autowired(required=false)

 

@Autowired 애노테이션은 메서드에도 붙일 수 있다.

 

예제 프로젝트 만들기 (메이븐 기준) 예제 코드

sp5-chap04 프로젝트 폴더 생성

프로젝트(sp5-chap04) 하위 폴더로 src\main\java 생성

sp5-shap04 폴더 내부에 pom.xml 생성

이클립스에서 sp5-chap04 폴더에 생성한 메이븐 프로젝트 import

기존 chap03에서 작성한 예제코드 그대로 사용 (복붙ㅎ)

 

※ 책에 나와있는 pom.xml파일은 메이븐 버전이 달라 프로그램이 실행이 안되니 현재 버전에 맞게 수정이 필요하다.

 

 

@Autowired 애노테이션을 이용한 의존 자동 주입

자동 주입 기능을 사용하면 스프링이 알아서 의존 객체를 찾아 주입한다.

 

사용 방법

의존을 주입할 대상@Autowired 애노테이션을 붙이면 된다.

직접 애노테이션을 붙이면 설정 클래스(AppCtx.java)에서 의존을 주입하지 않아도 된다.

필드에 @Autowired 애노테이션이 붙어 있으면 스프링이 해당 타입의 빈 객체를 찾아 필드에 할당

 

아래 ChangePasswordService 클래스에 직접 애노테이션을 붙였다.

→ sp5-chap04/src/main/java/spring/ChangePasswordService.java 

package spring;

import org.springframework.beans.factory.annotation.Autowired;
// 사용할 애너테이션을 import
public class ChangePasswordService {
	//의존을 주입할 대상에 @Autowired 붙이기
	@Autowired
	private MemberDao memberDao;

	public void changePassword(String email, String oldPwd, String newPwd) {
		Member member = memberDao.selectByEmail(email);
		if (member == null)
			throw new MemberNotFoundException();

		member.changePassword(oldPwd, newPwd);
		memberDao.update(member);
	}
	public void setMemberDao(MemberDao memberDao) {
		this.memberDao = memberDao;
	}

 

설정 클래스(AppCtx.java)에서 ChangePasswordService객체의 setMemberDao(memberDao()); 메서드를 호출하지 않는다.

pwdSvc.setMemberDao(memberDao()); 호출해서 MemberDao 빈 객체를 주입하지 않아도 스프링이 MemberDao 타입의 빈 객체를 주입하기 때문

 

→ sp5-chap04/src/main/java/config/AppCtx.java 

@Configuration
public class AppCtx {
    // @Bean 애노테이션은 해당 메서드가 생성한 객체를 스프링 빈이라고 설정한다.
    @Bean // 이 빈은 각각의 빈 객체를 생성 
    public MemberDao memberDao() {
        return new MemberDao();
    }

    @Bean
    public MemberRegisterService memberRegSvc() {
        return new MemberRegisterService(memberDao());
    }

    @Bean
    public ChangePasswordService changePwdSvc() {
        ChangePasswordService pwdSvc = new ChangePasswordService();
	// pwdSvc.setMemberDao(memberDao());
        return pwdSvc;
    }

 

[Run As] → [Java Application]  기존과 같이 잘 작동하는 것을 볼 수 있다.

※ 만일 @Autowired 애노테이션을 설정한 필드에 알맞은 빈 객체가 주입되지 않았다면 memberDao 필드는 null로 콘솔 실행 시 NullPointerException 발생할 것이다.

 

 

매서드에 애노테이션 붙이기

아래 MemberInfoPrinter클래스의 매개변수로 MemberDao 타입의 파라미터를 필요로 하는 메서드에 @Autowired 애노테이션을 붙였다.

→ sp5-chap04/src/main/java/spring/MemberInfoPrinter.java 

package spring;

import org.springframework.beans.factory.annotation.Autowired;

public class MemberInfoPrinter {
	private MemberDao memDao;
	private MemberPrinter printer;
	
	public void printMemberInfo(String email) {
		Member member = memDao.selectByEmail(email);
		if (member == null) {
			System.out.println("데이터 없음");
			return;
		}
		printer.print(member);
		System.out.println();
	}
	@Autowired // 메서드에도 @Autowired를 붙일 수 있다.
	public void setMemberDao(MemberDao memberDao) {
		this.memDao = memberDao;
	}
	@Autowired // 메서드에도 @Autowired를 붙일 수 있다.
	public void setPrinter(MemberPrinter printer) {
		this.printer = printer;
	}
	
}

 

설정 클래스(AppCtx.java)에서 MemberInfoPrinter의 두 개의 세터 메서드를 호출하지 않도록 수정

→ sp5-chap04/src/main/java/config/AppCtx.java 

public class AppCtx {
    // @Bean 애노테이션은 해당 메서드가 생성한 객체를 스프링 빈이라고 설정한다.
    @Bean // 이 빈은 각각의 빈 객체를 생성 
    public MemberDao memberDao() {
        return new MemberDao();
    }

... 생략 ...

    @Bean
    public MemberInfoPrinter infoPrinter() {
    	MemberInfoPrinter infoPrinter = new MemberInfoPrinter();
//    	infoPrinter.setMemberDao(memberDao());
//    	infoPrinter.setPrinter(memberPrinter());
    	return infoPrinter;
    }

 

[Run As] → [Java Application] 누르면 이클립스 콘솔 뷰에 잘 작동하는 것을 볼 수 있다.

 

 

 

 

 

 

 

 

 

 

 

 

MemberRegisterServeice클래스와 MemberListPrinter 클래스에도 @Autowired를 설정하고, 인자가 없는 기본 생성자를 추가한다. AppCtx 클래스에서 기본 생성자를 이용하여 객체를 생성하기 위해서다.

 

→ sp5-chap04/src/main/java/spring/MemberRegisterServeice.java

package spring;

import java.time.LocalDateTime;
import org.springframework.beans.factory.annotation.Autowired;;

public class MemberRegisterService {
	@Autowired
	private MemberDao memberDao;
	// chap04 - 인자가 없는 기본생성자 추가
	public MemberRegisterService(){
	}

	public MemberRegisterService(MemberDao memberDao) {
		this.memberDao = memberDao;
	}
	
	public Long regist(RegisterRequest req) { //주입 받은 의존 객체의 메서드를 사용
		Member member = memberDao.selectByEmail(req.getEmail());
		if (member != null) {
			throw new DuplicateMemberException("dup email" + req.getEmail());
		}
		Member newMember = new Member(
				req.getEmail(),req.getPassword(), req.getName(), LocalDateTime.now());
		memberDao.insert(newMember);
		return newMember.getId();
	}
}

 

→ sp5-chap04/src/main/java/spring/MemberListPrinter.java 

package spring;

import java.util.Collection;
import org.springframework.beans.factory.annotation.Autowired;;

public class MemberListPrinter {
	private MemberDao memberDao;
	private MemberPrinter printer;
        // chap04 - 인자가 없는 기본생성자 추가
        public MemberListPrinter(){
        }
    
	public MemberListPrinter(MemberDao memberDao, MemberPrinter printer) {
		this.memberDao = memberDao;
		this.printer = printer;
	}
	public void printAll() {
		Collection<Member> members = memberDao.selectAll();
		members.forEach(m -> printer.print(m));
	}
	// chap04 - set 메서드 추가하고 애노테이션 붙임
	@Autowired
	public void setMemberDao(MemberDao memberDao) {
		this.memberDao = memberDao;
	}
	@Autowired
	public void setMemberDaoPrinter(MemberPrinter printer) {
		this.printer = printer;
	}
}

 

설정 클래스에서 아래와 같이 의존을 주입하는 코드를 변경한다.

→ sp5-chap04/src/main/java/config/AppCtx.java

package config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import spring.ChangePasswordService;
import spring.MemberDao;
import spring.MemberInfoPrinter;
import spring.MemberRegisterService;
import spring.VersionPrinter;
import spring.MemberPrinter;
import spring.MemberListPrinter;
import spring.VersionPrinter;

//@Configuration은 스프링 설정 클래스를 의미함
@Configuration
public class AppCtx {
    // @Bean 애노테이션은 해당 메서드가 생성한 객체를 스프링 빈이라고 설정한다.
    @Bean // 이 빈은 각각의 빈 객체를 생성 
    public MemberDao memberDao() {
        return new MemberDao();
    }

    @Bean
    public MemberRegisterService memberRegSvc() {
        return new MemberRegisterService();
    }

    @Bean
    public ChangePasswordService changePwdSvc() {
        return new ChangePasswordService();
    }

    @Bean
    public MemberPrinter memberPrinter() {
    	return new MemberPrinter();
    }
    
    @Bean
    public MemberListPrinter listPrinter() {
    	return new MemberListPrinter();
    }

    @Bean
    public MemberInfoPrinter infoPrinter() {
    	return new MemberInfoPrinter();
    }

    @Bean
    public VersionPrinter versionPrinter() {
    	VersionPrinter versionPrinter = new VersionPrinter();
    	versionPrinter.setMajorVersion(5);
    	versionPrinter.setMinorVersion(0);
    	return versionPrinter;
    }
}

 

 

Error - 일치하는 빈이 없는 경우

설정 클래스에서 MemberDao빈을 주석처리하고 main 메서드 실행 시 아래와 같이 에러가 발생했다는 내용이 나온다.

 

org.springframework.context.support.AbstractApplicationContext refresh
WARNING: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyExceptio n: Error creating bean with name 'memberRegSvc': Unsatisfied dependency expressed through field 'memberDao'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'spring.MemberDao' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'memberRegSvc': Unsatisfied dependency expressed through field 'memberDao'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'spring.MemberDao' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

 

memberRegisterService 필드에 주입할 MemberDao 빈이 존재하지 않아 에러가 발생했다는 것을 알려준다.

 

 

Error - 빈이 두 개 이상인 경우

설정 클래스에서 MemberPrinter에 해당하는 빈을 여러 개 만들고 main 메서드 실행 시 아래와 같이 에러가 발생했다는 내용이 나온다.

 

Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyExceptio n: Error creating bean with name 'listPrinter': Unsatisfied dependency expressed through method 'setMemberDaoPrinter' parameter 0; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'spring.MemberPrinter' available: expected single matching bean but found 3: memberPrinter, memberPrinter1, memberPrinter2

 

MemberPrinter 타입의 빈을 한정할 수없다고 일치하는 빈이 한 개가 아니라 memberPrinter, memberPrinter1, memberPrinter2인 세 개의 빈을 발견했다는 것을 알려준다.

 

 

@Qualifier 애노테이션 

자동 주입 가능한 빈이 두 개 이상일 때 @Qualifier 애노테이션을 사용한다.

@Qualifier 애노테이션을 사용하면 자동 주입 대상 빈을 한정할 수 있다.

 

 

error 예제를 그대로 사용해서 설정 클래스(AppCtx)에 중복으로 들어간 MemberPrinter부분에 한 개의 빈에 @Qualifier 애노테이션에 ("printer")를 붙여준다.  이 "printer"는 한정 값이 "printer"인 빈을 의존 주입 후보로 사용한다.

 

→ sp5-chap04/src/main/java/config/AppCtx.java

    @Bean
    public MemberPrinter memberPrinter() {
    	return new MemberPrinter();
    }

    @Bean
    @Qualifier("printer")
    public MemberPrinter memberPrinter1() {
    	return new MemberPrinter();
    }

    @Bean
    public MemberPrinter memberPrinter2() {
    	return new MemberPrinter();
    }

MemberListPrinter 클래스와 MemberInfoPrinter 클래스에 설정했던 @Autowired 애너테이션 아래에

@Qualifier 애노테이션을 붙인다.

→ sp5-chap04/src/main/java/spring/MemberListPrinter.java 

	@Autowired
	public void setMemberDao(MemberDao memberDao) {
		this.memberDao = memberDao;
	}
	@Autowired
	@Qualifier("printer") // 추가
	public void setMemberDaoPrinter(MemberPrinter printer) {
		this.printer = printer;

→ sp5-chap04/src/main/java/spring/MemberInfoPrinter.java 

	@Autowired
	public void setMemberDao(MemberDao memberDao) {
		this.memDao = memberDao;
	}
	@Autowired
	@Qualifier("printer") // 추가
	public void setPrinter(MemberPrinter printer) {
		this.printer = printer;

[Run As] → [Java Application] 누르면 이클립스 콘솔 뷰에 잘 작동하는 것을 볼 수 있다.

 

 

 

 

 

 

 

 

 

 

 

 

 

※만약 한 클래스라도 @Qualifier를 설정하지 않으면 NoUniqueBeanDefinitionException 오류가 발생한다.

 

 

빈 이름과 기본 한정자 (?접근 제한자?)

빈 설정에 @Qualifier 애노테이션이 없으면 빈의 이름을 한정자로 지정한다.

 

pinter() 메서드로 정의한 빈의 한정자는 빈의 이름인 "printer"가 되고

printer2() 메서드로 정의한 빈의 한정자는 @Qualifier 애너테이션에 지정된 "mprinter"가 된다. 빈의 이름은 "printer2"

@Autowired 애너테이션 또한 @Qualifier 애너테이션이 없으면 필드나 파라미터 이름을 한정자로 사용한다.

@Configuration
public class AppCtx2 {

    @Bean
    public MemberPrinter printer() { // 빈 이름은 printer
    	return new MemberPrinter();  // 빈 한정자는 빈 이름인 printer
    }
    
    @Bean
    @Qualifier("mprinter")
    public MemberPrinter printer2() { // 빈 이름은 printer2
    	return new MemberPrinter();   // 빈 한정자는 애너테이션에 설정한 mprinter
    }
}

 

빈 이름 @Qualifier  한정자
printer   printer
printer2 mprinter mprinter

 

댓글