의존성 자동 주입

말 그대로 의존성을 자동으로 주입하는 방식

 

의존성 자동 주입을 위해서는 

@Autowired

@Resource

등의 어노테이션 사용

 

책에서는 @Autowired에 대한 설명만 진행

 

@Autowired

💡 @Autowired: 자동 주입 기능, 스프링이 알아서 의존 객체를 찾아 주입한다. 의존을 주입할 대상에 애노테이션을 붙이기만 하면 된다.
  • @Autowired 애노테이션을 붙이면 설정 클래스에서 의존을 주입하지 않아도 된다.
  • 스프링이 해당 타입의 빈 객체를 찾아서 필드에 할당

 

간단하게 말해서

 

pulbic class TestClass {

	@Autowired
    private TestObject testObject;
    
    ...
}

스프링 컨텍스트에 등록된 TestObject 의 빈 객체를 가져와 사용하겠다는 의미

 

 

이렇게 하면

public class AppCtx {

	...
    @Bean
    public TestObject testObjet() {
    	return new TestObject();
    }
    
    
    // 아래 부분 필요 X
    @Bean
    public TestClass testClass {
    	TestClass testClass = new TestClass();
        testClass.setTestObject(testObject);
        return testClass;
    }
}

@Configuration 

클래스에서  의존성 주입하는 클래스를 따로 명시하지 않아도 됨

 

 

일치하는 빈이 없는경우

@Autowired 어노테이션을 적용한 대상에 일치하는 빈이 없으면

UnsatisfiedDependencyException : Error creating bean.. 어찌구

Exception 발생

 

 

 

@Qualifier 어노테이션을 이용한 의존 객체 선택

단순하게 설명하자면

같은 클래스의 빈을 두개 이상 만들어서 사용하고 싶다!!

할때 @Qualifier 어노테이션으로 빈 이름을 설정해서 필요할 때 원하는 이름의 빈을 꺼내 사용하는 방법

 

빈 등록 할 때 @Qualifier 따로 명시하지 않으면 빈의 이름을 한정자로 지정

 

 

AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = ac.getBean("memberService", MemberService.class);

 

스프링 컨텍스에서 getBean() 메서드로 빈 조회해서 이름 확인 가능

 

 

상위/하위 타입 관계와 자동 주입

상속관계를 가지는 빈 객체를 @Autowired 로 자동 주입할 때 충돌 발생 가능

 

스프링은 @Autowired로 빈 주입 시, 타입 기준으로 빈을 서치

 

@Autowired
public void setMemberPrinter(MemberPrinter printer) { ... }

 

이 경우 MemberPrinter 타입을 찾게 되는데 

AppCtx 설정에 

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

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

이처럼

MemberPrinter, MemberPrinter의 하위 클래스를 각각 반환하는 빈이 등록되있음

 

즉, MemberPrinter 타입 빈이 두개 존재

@Qualifier 어노테이션으로 한정자 지정해서 해결 가능

 

 

@Autowired 어노테이션 필수 여부

 

@Autowired는 기본적으로 @Autowired 어노테이션을 붙인 타입에 해당하는 빈이 존재하지 않으면 Exception 발생

required 속성을 false로 설정해서 매칭되는 빈이 없으면 자동주입을 하지 않는 방법 가능

 

@Autowired(required = false)
private Optional<MemberPrinter> printer;

이렇게 사용하게 되면 빈이 없을 경우, null 주입하거나 아무 동작도 하지 않음

 

** Optional 과 함께 사용해서 null 에 대해 처리해주는 코드 함께 작성 권장 **

2025.06.25 - [끄적/BE] - Optional

 

 

@Nullable

 

문자 그대로 null 가능하다고 명시적으로 표시하는 용도

목적 해당 변수/파라미터/리턴값이 null일 수 있음을 명시
주요 위치 필드, 메서드 파라미터, 메서드 반환값

 

 

1. 의존성 주입에서

@Component
public class MemberInfoPrinter {

    private MemberPrinter printer;

    @Autowired
    public void setPrinter(@Nullable MemberPrinter printer) {
        this.printer = printer;
    }
}

 

  • MemberPrinter 빈이 없어도 예외 없이 null이 주입됨
  • @Autowired(required = false)와 유사 기능

 

2. 일반 메서드 파라메터

public String getNickname(@Nullable String username) {
    return (username != null) ? username : "Guest";
}

 

 

 

3. return 값

@Nullable
public Member findByEmail(String email) {
    ...
    return null; // 없을 수도 있음
}

 

 

 

 

자동 주입과 명시적 의존 주입 간의 관계

@Configuration 클래스에서 의존성 주입했는데, 자동 주입 대상이면??

 

명시적 주입

@Bean
public MemberInfoPrinter infoPrinter() {
    MemberInfoPrinter infoPrinter = new MemberInfoPrinter();
    infoPrinter.setPrinter(memberPrinter2()); // 명시적 주입: SummaryPrinter
    return infoPrinter;
}

 

자동 주입

@Autowired
@Qualifier("printer")
public void setPrinter(MemberPrinter printer) {
    this.printer = printer;
}

 

출력 결과는 **memberPrinter1() (일반 MemberPrinter)**를 사용한 것으로 확인됨.
즉, 명시적으로 주입한 memberPrinter2()가 무시되고, @Autowired에 의해 memberPrinter1()이 주입됨

 

자동 주입이 명시적 주입을 덮어쓰기 때문

Spring에서는 @Bean 메서드로 생성한 객체에 대해 내부적으로 생성 이후 @Autowired를 기반으로 DI 작업을 수행

  1. @Bean 메서드(infoPrinter())가 실행되어 객체 생성 및 명시적 주입 수행
  2. Spring 컨테이너는 해당 객체에 @Autowired가 선언된 세터가 있으면 자동 주입 수행
    >> 이 과정에서 이미 명시적으로 주입된 필드라도 다시 덮어씀

즉, 자동 주입이 명시적 Setter 호출보다 나중에 실행, 자동 주입이 반영됨

'복습 > Spring' 카테고리의 다른 글

[Spring 5 프로그래밍 입문] 스프링 DI  (0) 2025.06.22

 

의존이란?

Dependency Injection

의존 주입

 

예를 들어

public class MemberRegisterService {
	private MemberDao memberDao = new MemberDao();
    
    public void regist(RegisterRequest req) {
    
        Member member = memberDao.selectByEmail(req.getEmail());

        if(member != null) {
            throw new DuplicatedMemberException("dup mem");
        }

        Member newMember = new Member(req.getEmail(), req.getPassword(), req.getName(), LocalDateTime.now());
        memberDao.insert(newMember);
	}
}

 

위와 같은 코드에서 

MemberRegisterService 클래스가 DB 사용을 위해 MemberDao클래스의 메서드를 사용

이렇게 한 클래스가 다른 클래스의 메서드를 실행할 때, 이를 의존한다 라고 표현

 

 

의존하는 대상이 있으면, 그 대상을 구하는 방법이 필요

가장 쉬운 방법은 의존 대상 객체를 직접 생성하는것

 

public class MemberRegisterService {

	private MemberDao memberDao = new MemberDao();
    
   	...
}

 

 

클래스 내부에서 의존 객체 직접 생성은 유지보수 관점에서 좋지 않은 방식

 

 

DI를 통한 의존 처리

 

DI는 의존 객체를 직접 생성하는 대신 의존 객체를 전달받는 방식 사용

 

public class MemberRegisterService {

	...
    private MemberDao memberDao;
    
    public MemberRegisterService (MemberDao memberDao){
    	this.memberDao = memberDao;
	}
    
    ...
}

 

의존 객체를 직접 생성하지 않고

생성자를 통해 의존 객체를 전달받음

 

MemberRegisterService service = new MemberRegisterService(memberDao);

와 같이 생성자 생성 시, 의존 객체 함께 전달

 

 

 

DI와 의존 객체 변경의 유연함

 

의존 객체 직접 생성방식은 필드, 생성자에서 new 연산자로 객체 생성

 

public class MemberRegisterService{
	private MemberDao memberDao = new CachedMemberDao; // MemberDao에서 변경
	...
}

public class ChangePasswordService{
	private MemberDao memberDao = new CachedMemberDao; // MemberDao에서 변경
	...
}

 

이처러머 각각 클래스에서 생성자를 직접 생성하는 경우

memberDao 를 다른 객체로 바꾸고 싶을 때, 모든 클래스의 코드를 수정해야함

 

but

생성자 주입 방식을 사용할 경우

 

// 기존
// MemberDao dao = new MemberDao();
MemberDao dao = new CachedMemberDao();
MemberRegisterService svc = new MemberRegisterService(dao);
ChangePasswordService pwdSvc = new ChangePasswordService(dao);

 

기존 MemberDao 대신 MemberDao 를 상속받아 구현 된 CachedMemberDao 객체를 사용하고 싶은 경우

위처럼 한줄 수정으로 모든 클래스에 적용 가능

 

 

... 예제 프로젝트 import는 생략 ...

 

예제 프로젝트에서도 역시 의존성 생성자 주입을 통한 클래스 구현

객체 조립기라는것 사용

 

public class Assembler{
    private MemberDao memberDao;
    private MemberRegisterService regSvc;
    private ChangePasswordService pwdSvc;
    
    public Assembler() {
        memberDao = new MemberDao();
        regSvc = new MemberRegisterService(memberDao);
        pwdSvc = new ChangePasswordService();
        pwdSvc.setMemberDao(memberDao);
    }
	...
}

 

처럼 의존성 연관된 객체들을 조립해주는 클래스를 별도로 만들어

 

Assembler assembler = new Assembler();
ChangePasswordService changePwdSvc = assembler.getChangePasswordService();

 

 

와 같이 간편하게 사용할 수 있도록 도와주는 클래스

Spring 에서는 이 조립기가 내부에 존재해서 별도로 작성 필요 X

 

 

@Configuration
public class AppCtx {
    @Bean
    public MemberDao memberDao(){
        return new MemberDao();
    }

    @Bean
    public MemberRegisterService memberRegisterSvc(){
        // 생성자를 통한 의존주입
        return new MemberRegisterService(memberDao());
    }

    @Bean
    public ChangePasswordService changePasswordSvc(){
        ChangePasswordService pwdSvc = new ChangePasswordService();
        // set메서드를 통한 의존 주입
        pwdSvc.setMemberDao(memberDao());
        return pwdSvc;
    }
    ...
}

 

@Configuration : 스프링 설정 클래스

@Bean : 스프링 빈 객체로 생성

 

직접 객체를 생성하고 등록하고 조립하는 것이 아니라

Spring Context 에 Bean을 등록하고 그 객체를 사용

 

        ctx = new AnnotationConfigApplicationContext(AppCtx.class);
        ...
        ...
        
        
        MemberRegisterService regSvc = ctx.getBean("memberRegisterSvc", MemberRegisterService.class);
    ...

 

이처럼 스프링 컨텍스트에서 memberRegisterSvc 라는 Bean을 꺼내서 사용

 

 

 

DI 방식

 

생성자 주입

private MemberDao membe Dao;

public MemberRegisterService(Membe Dao memberDao) { 
	this.memberDao = memberDao;
}

 

 

이처럼 생성자를 생성할 때, 객체를 전달받는 방식

주입받은 객체가 변하지 않고

생성자 실행 시 1회 실행 보장

 

 

수정자 주입 (Setter 메서드) 주입

 

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("데이터 없음\\n");
            return;
        }
        printer.print(member);
        System.out.println();
    }

    public void setMemberDao(MemberDao memberDao){
        this.memDao = memberDao;
    }

    public void setPrinter(MemberPrinter printer){
        this.printer = printer;
    }
}

 

setXXX 메서드에 의존성 객체를 불러오는 방식

객체를 불러온 이후에도, set메서드를 통해 객체를 변경할 수 있기 때문에 위험요소 존재

 

필드 주입 방식

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;
    @Autowired
    private MemberService memberService;

}

 

책에는 안나와있지만, 클래스에 선언된 private 필드에 직접 주입하는 방식

필드주입은 외부에서 접근이 불가능하기 때문에 TDD에 어울리지 않는 방식

 

 

@Configuration 설정 클래스의 @Bean 설정과 싱글톤

스프링 컨테이너가 생성한 빈은 싱글톤 객체

Bean이 붙은 메서드에 대해 한 개의 객체만 생성하여 사용

>> 싱글톤으로 직접 구현 불필요, 매우 간편!

 

@Configuration
public lcass AppCtx {
	
    @Bean
    public MemberDao memberDao() {
    	return new MemberDao();
    }
	
    ...
}

 

여러 클래스에서 MemberDao 메서드를 실행해도 같은 객체를 리턴

실제 스프링 내부적으로는 복잡하게 구현되어 있음

@Configuration 어노테이션, 빈, @Autowired 어노테이션

 

@Autowired 어노테이션이란?

스프링 빈에 의존하는 다른 빈을 자동 주입하는 어노테이션

 

public class MemberInfoPrinter{
	
    @Autowired
	private MemberDao memberDao;
    ...
}

 

필드에 @Autowired 어노테이션을 사용하면

위 Configuration 클래스를 내부에 하나하나 @Bean 메서드를 생성하지 않아도 의존성 주입 가능

 

 

@Import 어노테이션

 

은.. 넘어가자 

 

 

 

'복습 > Spring' 카테고리의 다른 글

[Spring 5 프로그래밍 입문] 의존성 자동 주입  (1) 2025.06.25

+ Recent posts