본문 바로가기

백엔드/Spring

[Spring] Spring 의존성 주입(Config, 어노테이션, Groovy DSL, Bean 스코프)

728x90

들어가기 앞서

Spring을 사용하면 자바에서 의존성 주입을 다양한 방식으로 할 수 있다.

DI를 위한 Bean을 등록하기 위해서 xml을 사용할 수 있으며 이에 대한 자세한 내용은 블로그 글을 참고하면 된다.

XML을 사용한 설정 말고도 Config를 사용해 순수 Java 코드로 설정이 가능하며, Groovy Bean DSL을 사용하는 방법도 존재하며 이에 대해 알아보도록 하자.

또한, 등록된 빈들의 관계를 주입하는 어노테이션과 등록된 Bean의 범위(Scope)에 대해 알아보겠다.

왜 Bean을 사용할까?

Bean을 등록하고 객체에 주입하는데 사용하는데 Config와 같은 방식을 사용한다.

그렇다면 왜 Bean을 사용하는지 알아야 한다.

그 이유는 아래와 같다.

1. 객체가 필요한 다른 객체를 자동으로 주입 받을 수 있어 의존성을 관리할 수 있다.

2. Bean의 생성 시점, 스코프(singleton, prototype 등)를 프레임워크가 통제 가능해 객체 생명주기 관리에 용이하다.

3. 객체 생성 및 연결을 코드와 분리하여 유지보수와 테스트에 유리해져 설정 분리 및 재사용에 강점을 가진다.

4. 개발자는 비즈니스 로직에 집중하고 Spring이 객체 구성과 생명주기를 담당함으로 단일 책임 원칙을 지킬 수 있다.

5. Bean으로 등록된 객체에 대해 Spring은 부가기능을 쉽게 적용할 수 있어 AOP나 트랜잭션 같은 기능을 사용할 수 있다.

실제로 Bean이 없을경우를 가정하면 객체를 생성할때마다 아래와 같이 new를 사용해야 한다.

UserService userService = new UserService(new UserRepository());

하지만 이 경우에 UserRepository가 다른 의존성을 가진다면 또 생성해야 한다.

또한, 테스트 하기에도 불편하다는 단점을 가진다.

Config

XML을 사용하여 객체 생성 및 의존성을 주입하는 방법이 있었다면, Java에서 Config를 사용하면 자바 코드로 객체 생성 및 의존성을 주입할 수 있다.

XML의 경우에는 타입 체크와 IDE 자동 완성을 미지원하는 불편함이 있었지만, Config를 이용하면 이런 문제를 해결할 수 있다.

Config에서는 자주 사용하는 2가지 어노테이션이 있다.

어노테이션 설명
@Configuration 해당 클래스가 Bean 설정 클래스임을 의미
@Bean 메서드 반환 객체를 스프링 Bean으로 등록
@Configuration
public class AppConfig {

    @Bean
    public Message message() {
        return new HelloMessage();
    }

    @Bean
    public MessagePrinter messagePrinter() {
        return new MessagePrinter(message());
    }
}

위 코드와 같이 클래스 위에 @Configuration 어노테이션을 선언해주면 해당 클래스가 Bean 설정 클래스임을 명시한다.

또한 @Configureation 어노테이션을 통해 Bean 설정 클래스에서 @Bean을 클래스 위에 선언하면 해당 클래스의 반환값(return)을 Bean으로 등록해서 나중에 객체에 주입할때 사용할 수 있다.

값 주입하기

Config를 통해 Bean을 등록했다면 사용하기 위해서 아래와 같은 어노테이션을 주로 사용한다.

어노테이션 사용 목적
@Autowired 다른 Bean 주입 (자동 의존성 주입)
@Value 외부 설정값(String, int 등) 주입
@Qualifier 동일 타입 Bean이 여러 개일 때 특정 Bean 선택 주입
@Component
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Value("${app.version}")
    private String appVersion;

    @Autowired
    @Qualifier("kakaoSender")
    private MessageSender messageSender;
}

@Autowired어노테이션을 사용하면 스프링이 UserRepository를 찾아 자동으로 주입해준다.

@Value 어노테이션을 사용하면 properties 파일에 있는 설정값을 주입해준다.

참고로 XML에 있는 Bean은 Setter를 사용해 직접 주입해야한다.

@Qualifier 어노테이션을 사용하면 같은 타입의 Bean이 여러개 있다면 그 중 kakaoSender 선택한다.

Groovy Bean DSL

Spring Boot에서 지원하는 Groovy 기반의 Bean 설정 문법으로, XML보다 간결하게 작성할 수 있다.

beans {
    messagePrinter(MessagePrinter) {
        message = ref('helloMessage')
    }

    helloMessage(HelloMessage) {
        greeting = "Hello, Groovy!"
    }
}

위와 같이 사용한다.

문법이 간단하고 함수형 느낌이 들어 가독성이 좋아 Spring Boot 내부나 간단한 설정에 적합하다.

하지만, IDE 자동 완성이나 검증과 같은 툴을 지원하지 않고, 팀 프로젝트일 경우 Groovy 문법에 대한 이해가 필요하다.

무엇보다 Spring Boot는 Groovy Bean DSL을 미지원한다는게 가장 크다.

때문에, 대규모 프로젝트의 경우에는 보통 XML을 보다 더 선호한다.

Bean 스코프

Bean이 어떤 범위로 생성되고 관리될지를 지정하는 속성을 Bean 스코프라고 말한다.

주요 스코프는 아래와 같다.

스코프 설명
singleton (기본값) 애플리케이션당 한 개의 인스턴스 생성
prototype 요청마다 새로운 인스턴스 생성
request HTTP 요청마다 생성 (웹 전용)
session 세션마다 생성 (웹 전용)

여기서 requestsession과 같은 경우에는 웹 애플리케이션에서만 유효하며, Spring Web Application Context가 의존성으로 있어야지 동작한다.

@Configuration
public class AppConfig {

    @Bean
    @Scope("prototype")
    public Message message() {
        return new HelloMessage();
    }

    @Bean
    public MessagePrinter messagePrinter() {
        return new MessagePrinter(message());
    }
}

위와 같이 Config에서도 스코프를 지정할 수 있다.

주의할 점

Bean 스코프를 지정할때 주의할 점이 있다.

@Configuration
public class AppConfig {

    @Bean
    public Message message() {
        System.out.println("message() 실행");
        return new HelloMessage();
    }

    @Bean
    public MessagePrinter messagePrinter() {
        System.out.println("messagePrinter() 실행");
        return new MessagePrinter(message());
    }
}

위 코드는 단순한 메서드 호출이 아니라, Spring 컨테이너에서 해당 Bean을 가져오도록 설계되어 있다.

Spring은 @Configuration 클래스 자체를 프록시(Proxy) 객체로 감싸게 된다.

때문에 message()를 직접 호출하는 아래의 경우에

return new MessagePrinter(message());

실제로는 아래와 같이 된다.

return new MessagePrinter(context.getBean("message"));

그렇다면 스코프로 protype을 지정하면 어떻게 될까?

@Bean
@Scope("prototype")
public Message message() {
    return new HelloMessage();
}

위와 같이 프로토타입으로 스코프를 지정할 경우 message()가 매번 새 객체를 생성해줘야 한다.

하지만, 실제로는 아래 코드처럼 Spring이 내부적으로 캐싱된 singleton 인스턴스를 반환하게된다.

@Bean
public MessagePrinter messagePrinter() {
    return new MessagePrinter(message());
}

물론 이건 @Configuration 어노테이션만 그런것이기 때문에 @Controller@Service와 같은 다른 클래스에서 Bean을 설정한 경우에는 문제가 발생하지 않는다.

728x90

'백엔드 > Spring' 카테고리의 다른 글

[Spring] MyBatis에 대해 알아보자  (0) 2025.06.16
[Spring] @Transactional이란?  (0) 2025.06.12
[Spring] AOP에 대해 알아보자  (0) 2025.06.10
[Spring] ComponentScan 어노테이션  (0) 2025.06.10