[STS3] Autowired Anotation에 관하여

Shy·2024년 5월 10일

STS

목록 보기
1/8

Autowired란?

@Aurowired는 스프링 프레임워크에서 의존성 주입(Dependency Injection)을 자동화하기 위해 제공되는 어노테이션이다.

스츠링 컨테이너에 의해 관리되는 빈(bean)간의 의존성을 자동으로 해결하고 주입하도록 지시하는 역할을 한다.

xml 파일

src/main/resources 폴더 아래 xml파일을 놓는다.

해당 파일을 생성하는 방법은 아래와 같다.

  1. 프로젝트 탐색기에서 우클릭 → New → Other... 선택
  2. Spring 카테고리를 확장하고 Spring Bean Configuration File 선택
  3. 파일 이름과 경로 설정 후 Finish 클릭

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">

	<!-- 어노테이션 설정된 클래스의 객체 생성을 위한 설정 
		1. 네임스페이스 추가 : context(xmlns:context) 
		2. <context:component-scan> 태그 설정으로 찾을 위치 지정
		    - base-package 속성 : 컴포넌트 어노테이션 찾을 위치 지정
		      (설정된 패키지 포함 + 하위 패키지 모두 검색)
	-->
	<context:component-scan base-package="di_annotation_xml"></context:component-scan>

	<bean id="sonySpeaker" class="di_annotation_xml.SonySpeaker" />

	<!-- <bean id="appleSpeaker" class="di_annotation_xml.AppleSpeaker" /> -->
</beans>

XML 구조 분석

  1. Namespace 정의:
    • xmlns: 스프링 프레임워크의 빈 구성 및 어노테이션 지원을 위한 네임스페이스를 선언한다.
    • xsi:schemaLocation: 네임스페이스가 참조하는 스키마의 위치를 지정한다. 이를 통해 XML 문서의 구조가 해당 스키마에 맞는지 검증할 수 있다.
    • xmlns:context: context 네임스페이스를 통해 어노테이션 기반 설정을 할 수 있게 한다.
  2. 컴포넌트 스캔 설정 (<context:component-scan>)
    • base-package 속성으로 특정 패키지와 그 하위 패키지의 클래스들을 모두 검색하도록 지정한다.
    • 이 속성에 지정된 패키지에서 @Component, @Service, @Repository, @Controller 등의 스테레오타입 어노테이션이 붙은 클래스를 자동으로 스프링 컨테이너에 빈으로 등록한다.
  3. 수동 빈 정의 (<bean> 태그)
    • 스프링 컨테이너에 빈을 직접 정의하는 부분이다.
    • id 속성으로 빈의 이름을 지정하고, class 속성으로 해당 빈을 생성할 클래스의 이름을 지정한다.

주요 특징과 작동 방식

1. 타입 기반 의존성 주입

  • '@Autowired' 는 주입하려는 빈의 타입을 기반으로 자동 주입을 수행한다.
  • 필드, 세터 메서드, 일반 메서드, 생성자에 모두 적용될 수 있다.

2. 필드 주입

  • 클래스의 필드에 '@Autowired' 를 붙이면 스프링이 자동으로 해당 타입의 빈을 찾아 주입한다.
@Autowired
private MyService myService;

필드 주입은 다음과 같은 과정을 통해 이루어진다

  1. 클래스 스캔 및 빈 등록: 스프링은 컴포넌트 스캔을 통해 @Component, @Service, @Repository, @Controller 등의 애노테이션이 붙은 클래스를 찾아 스프링 컨테이너에 빈(bean)으로 등록한다.
  2. 의존성 확인: 스프링은 빈으로 등록된 클래스 내부에서 @Autowired 애노테이션이 붙은 필드를 찾는다.
  3. 의존성 주입: 스프링 컨테이너는 필드의 타입과 일치하는 다른 빈을 찾아 자동으로 해당 필드에 주입한다. 이 과정은 클래스의 생성자가 호출된 후에 이루어진다.

예시

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

@Service
public class MyService {

    // 필드 주입 사용
    @Autowired
    private MyRepository myRepository;

    public void performAction() {
        myRepository.doSomething();
    }
}

필드 주입의 장단점

장점
  • 간결성: 세터 메서드나 생성자를 작성할 필요 없이 간단히 필드에 애노테이션을 추가하는 것만으로 의존성 주입이 가능하다.
  • 편리성: 간단한 설정과 더 적은 코드로 빠르게 의존성을 주입할 수 있다.
단점
  • 테스트 어려움: 필드 주입을 사용하면 테스트 코드에서 해당 필드를 직접 주입하거나 모킹하기가 어렵다. 이는 필드가 final이 아니므로 변경될 수 있고, 접근자 없이 주입되므로 테스트 환경 설정이 복잡해질 수 있다.
  • 불변성 부족: 필드에 final 키워드를 사용할 수 없기 때문에 불변 객체를 만들기 어렵다.

필드 주입은 특히 간단하고 변경이 자주 발생하지 않는 의존성에 적합하나, 더 복잡하거나 테스트가 중요한 환경에서는 생성자 주입이 더 권장된다. 이는 생성자를 통해 모든 의존성이 명시적으로 제공되고, 필요한 모든 의존성이 누락되지 않도록 강제하기 때문이다.

3.생성자 주입 (Constructor Injection)

  • 생성자에 @Autowired를 붙이면 스프링이 생성자 호출 시 의존성을 주입한다.
  • 스프링 4.3부터는 클래스에 생성자가 하나만 있을 경우 @Autowired를 생략해도 자동 주입이 이루어진다.
@Autowired
public MyComponent(MyService myService) {
    this.myService = myService;
}

예시: 서비스와 리포지토리를 사용하는 경우

아래의 예제에서 UserService 클래스는 UserRepository에 대한 의존성을 갖고 있다. 이 의존성은 생성자를 통해 주입된다. 이 방식을 사용함으로써, UserService인스턴스가 생성될 때 반드시 UserRepository 인스턴스가 필요함을 보장할 수 있다.

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

@Service
public class UserService {

    private final UserRepository userRepository;

    // 생성자 주입
    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void registerUser(User user) {
        userRepository.save(user);
    }
}

예시: 여러 의존성이 있는 경우

다음 예제에서 NotificationService 클래스는 EmailServiceSMSService라는 두 가지 의존성을 갖고 있다. 이 두 서비스는 모두 알림을 보내는 방법을 제공하지만, 각기 다른 채널을 통해 이루어진다. 생성자를 통해 두 서비스 모두 주입되므로, NotificationService는 필요에 따라 적절한 서비스를 사용할 수 있다.

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

@Service
public class NotificationService {

    private final EmailService emailService;
    private final SMSService smsService;

    // 두 의존성을 생성자를 통해 주입
    @Autowired
    public NotificationService(EmailService emailService, SMSService smsService) {
        this.emailService = emailService;
        this.smsService = smsService;
    }

    public void sendEmailNotification(String message) {
        emailService.sendEmail(message);
    }

    public void sendSMSNotification(String message) {
        smsService.sendSMS(message);
    }
}

주의점

위처럼 Autowired 를 적용했을 경우, 기본 생성자를 통해 선언할 수 없다.

스프링에서 생성자 주입을 사용하면 명시적으로 선언된 생성자를 사용해야 하며, 이 경우 파라미터가 없는 기본 생성자를 통해 객체를 생성할 수 없다. 이는 다음과 같은 이유로 설계적인 이점을 제공한다

  1. 의존성의 명시성과 강제성
    • 생성자 주입을 사용하면, 해당 객체가 올바르게 생성되기 위해 필요한 의존성을 명확하게 제공해야 한다. 이는 객체가 의존하는 다른 컴포넌트들 없이는 인스턴스화될 수 없다는 것을 의미한다. 이로 인해 런타임에 의존성 누락으로 인한 오류를 방지할 수 있다.
  2. 불변성
    • 생성자 주입을 사용하면, 의존성 필드를 final로 선언할 수 있다. 이는 객체가 한 번 생성된 후에는 그 상태가 변경되지 않음을 보장한다. 불변 객체는 멀티스레드 환경에서 안전하며, 사이드 이펙트가 없는 프로그래밍을 촉진한다.

물론 필요에 따라서 생성하게 만들 수도 있고, 그러면 null이 저장되지만... 그러나 일반적인 스프링 사용에서는 생성자 주입만을 사용하여 모든 의존성을 명시적으로 주입하는 것이 더 권장된다.

4. 세터 메서드 주입

세터 메서드에 '@Autowired' 를 붙여 의존성을 주입할 수 있다.

@Autowired
public void setMyService(MyService myService) {
    this.myService = myService;
}

세터 메서드 주입(setter injection)은 스프링 프레임워크에서 객체의 의존성을 주입하는 또 다른 방법이다. 이 방식은 객체가 생성된 후에도 해당 객체의 의존성을 변경할 수 있게 해준다. (생성자 주입은 변경이 불가능하지만, 세터는 가능!!) 세터 메서드 주입을 사용하면, 의존성 주입을 위한 특정 메서드(세터)에 @Autowired 애노테이션을 붙여 스프링이 자동으로 해당 타입의 빈을 찾아 메서드를 통해 주입하도록 합니다.

세터 메서드 주입의 작동 방식

  1. 메서드 정의: 클래스 내에 하나 이상의 세터 메서드를 정의한다. 각 세터는 주입받을 의존성에 대응한다.
  2. 애노테이션 적용: 각 세터 메서드에 @Autowired 애노테이션을 적용하여 스프링에 의존성을 자동으로 주입하도록 지시한다.
  3. 의존성 주입: 스프링은 해당 타입의 빈을 컨테이너에서 찾아 자동으로 세터 메서드를 호출하여 의존성을 주입한다.

세터 메서드 주입 예시

아래의 코드 예시에서는 EmailService라는 의존성을 가진 NotificationService 클래스를 살펴보겠다. NotificationService는 이메일 알림 기능을 제공하며, EmailService는 이메일을 전송하는 로직을 포함하고 있다.

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

@Service
public class NotificationService {
    private EmailService emailService;

    // 세터 메서드에 @Autowired 적용
    @Autowired
    public void setEmailService(EmailService emailService) {
        this.emailService = emailService;
    }

    public void sendNotification(String message) {
        emailService.sendEmail(message);
    }
}
// EmailService 클래스
import org.springframework.stereotype.Service;

@Service
public class EmailService {
    public void sendEmail(String message) {
        System.out.println("Sending email with message: " + message);
    }
}

세터 메서드 주입의 장단점

  1. 장점

    • 유연성: 의존성 주입 후에도 의존성을 변경할 수 있어, 런타임 중에 객체의 의존성을 조정할 수 있는 유연성을 제공한다.
    • 선택적 의존성: 필요에 따라 일부 의존성을 제공하지 않을 수도 있다. @Autowired(required = false)를 사용하면, 해당 타입의 빈이 없어도 오류가 발생하지 않는다.
  2. 단점

    • 과도한 변경: 객체의 의존성이 변경될 수 있다는 점은 객체의 상태를 예측하기 어렵게 만들 수 있다. 불변성을 유지하기 어렵다.
    • 초기화 순서 문제: 생성자 주입에 비해 초기화 순서와 관련된 문제가 발생할 수 있다. 의존성이 세터를 통해 제공되기 전에는 해당 의존성을 사용하는 메서드들이 올바르게 작동하지 않을 수 있다.

결론

세터 메서드 주입은 스프링에서 의존성 주입을 구현할 때 유용한 옵션 중 하나이다. 그러나 객체의 불변성을 중시하거나, 모든 의존성이 객체 생성 시점에 제공되어야 하는 경우 생성자 주입을 사용하는 것이 더 적합할 수 있다. 선택적 의존성이 필요하거나, 의존성을 변경이 필요한 경우엔 세터 메서드를 쓰는 것의 이점이 더 크다고 할 수 있다.

5. 일반 메서드 주입

특정 메서드에 '@Autowired' 를 적용해 필요한 의존성을 메서드 매개변수에 전달할 수 있다.

@Autowired
public void configure(MyService myService, MyRepository myRepository) {
    // 주입된 객체 사용
}

일반 메서드 주입(Method Injection)은 스프링에서 특정 메서드에 @Autowired 애노테이션을 사용하여 필요한 의존성을 주입하는 방식dㅣ다. 이 방법은 필드나 생성자 주입과는 다르게, 의존성이 필요한 특정 메서드에서만 의존성을 주입받을 수 있다. 이것은 특정한 설정 또는 초기화 작업이 필요할 때 유용하게 사용될 수 있다.

작동 방식

스프링 컨테이너는 클래스 내에 @Autowired가 적용된 모든 메서드를 찾고, 해당 메서드에 필요한 파라미터 타입의 빈을 찾아 자동으로 주입한다. 이 과정은 객체의 생성과 초기화 이후에 발생한다.

예시

설정 클래스와 사용 클래스 정의
ConfigManager 클래스가 필요한 DatabaseConfig와 NetworkConfig 설정을 받아 초기화하는 과정을 거치는 경우를 생각해보자.

// Config 클래스 정의
@Component
public class DatabaseConfig {
    public String getUrl() {
        return "jdbc:mysql://localhost:3306/mydb";
    }
}

@Component
public class NetworkConfig {
    public String getBaseUrl() {
        return "http://api.example.com";
    }
}
// ConfigManager 클래스 정의
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class ConfigManager {
    private String dbUrl;
    private String baseUrl;

    // 일반 메서드 주입
    @Autowired
    public void setupConfig(DatabaseConfig dbConfig, NetworkConfig netConfig) {
        this.dbUrl = dbConfig.getUrl();
        this.baseUrl = netConfig.getBaseUrl();
        initializeResources();
    }

    private void initializeResources() {
        System.out.println("Database URL: " + dbUrl);
        System.out.println("Base URL: " + baseUrl);
        // 이곳에서 추가적인 초기화 로직 수행
    }
}

메서드 주입의 장점

  1. 유연성: 의존성이 필요한 특정 시점에만 주입할 수 있어, 클래스의 나머지 부분에서는 해당 의존성을 관리할 필요가 없다.
  2. 선택적 초기화: 모든 의존성을 생성자에서 받지 않고, 필요한 시점에 초기화 작업을 수행할 수 있다.

메서드 주입의 단점

  1. 초기화 지연: 메서드 주입을 받는 필드들은 객체 생성 이후 일정 시간 동안 초기화되지 않은 상태로 남아 있을 수 있다.
  2. 메서드 의존성 관리: 복잡한 의존성 구조에서는 어떤 메서드가 어떤 의존성을 요구하는지 추적하기 어려울 수 있다.

결론

일반 메서드 주입은 설정이나 초기화가 복잡한 경우, 또는 특정 조건에서만 의존성이 필요한 경우에 매우 유용하다. 그러나 모든 의존성 관리가 명확히 보장되어야 하는 경우, 생성자 주입이 더 적합할 수 있다. 이를 통해 클래스가 항상 완전히 초기화된 상태로 유지되도록 할 수 있다.

6. 옵션 처리

required 속성을 false로 설정하면 주입하려는 빈이 없을 경우에도 예외를 발생시키지 않고 null로 주입한다.

@Autowired(required = false)
private OptionalService optionalService;

7. '@Qualifier'와 함께 사용

여러 빈이 같은 타입을 가지고 있을 경우, @Qualifier를 함께 사용하여 특정 빈을 명시적으로 지정할 수 있다.

@Autowired
@Qualifier("specificService")
private MyService myService;

8. 결론

@Autowired 는 스프링에서 의존성 주입을 간단하게 처리하기 위해 필수적인 애노테이션이다. 이 애노테이션을 사용하면 코드의 결합도를 줄이고 객체 생성과 관리의 복잡도를 낮출 수 있어 개발자가 생산적으로 코딩할 수 있다.

profile
신입사원...

0개의 댓글