IT개발및프로그래밍/웹서버프로젝트

스프링 컨텍스트에 등록된 빈을 액세스하고 관리하는 방법

devgodmj 2024. 10. 22. 13:19

1. 구성 클래스에서 정의된 두 빈의 관계 구현 

 1) @Bean 애너테이션을 사용한 메서드 간의 직접 호출

 2) @Bean 메서드의 매개변수를 통한 빈 와이어링

 

구성 클래스:

  • @Configuration 애너테이션을 사용해 빈을 정의하는 클래스입니다.
  • 이 클래스는 @Bean 메서드를 통해 객체를 생성하고, 스프링 컨텍스트에 빈으로 등록합니다.

의존성 주입:

  • 두 객체 간의 관계를 정의하고, 스프링 컨텍스트가 자동으로 의존성을 관리해 줍니다.
  • 예를 들어, Car가 Engine을 의존하는 경우, Car 빈에 Engine 빈을 주입할 수 있습니다.

 

1) @Bean 애너테이션을 사용한 메서드 간의 직접 호출

 

@Bean 애너테이션을 사용하여 스프링 구성 클래스에서 두 빈 간의 관계를 설정하는 방법은 스프링의 의존성 주입(DI)을 활용하는 좋은 예입니다. 이 방법을 사용하면 스프링 컨텍스트 내에서 빈을 생성하고 서로 연결할 수 있습니다. 이러한 관계 설정은 보통 객체 간의 협력(의존성)을 관리하는 데 사용됩니다.


  • 구성클래스 정의 및 직접 메서드 호출을 통한 빈 주입
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class VehicleConfig {

    @Bean
    public Engine engine() {
        Engine engine = new Engine();
        engine.setType("V8 Engine");
        return engine;
    }

    @Bean
    public Car car() {
        Car car = new Car();
        car.setModel("Sports Car");
        // engine() 메서드를 직접 호출하여 Engine 빈 주입
        car.setEngine(engine());
        return car;
    }
}

  • 클래스 정의
public class Engine {
    private String type;

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }
}

public class Car {
    private String model;
    private Engine engine;

    public String getModel() {
        return model;
    }

    public void setModel(String model) {
        this.model = model;
    }

    public Engine getEngine() {
        return engine;
    }

    public void setEngine(Engine engine) {
        this.engine = engine;
    }

    public void drive() {
        System.out.println("Driving a " + model + " with " + engine.getType());
    }
}
  • VehicleConfig 클래스:
    • VehicleConfig 클래스는 스프링의 빈 설정 클래스입니다. @Configuration을 통해 스프링 컨텍스트에서 이 클래스에 정의된 @Bean 메서드들이 스프링이 관리하는 빈으로 등록됩니다.
  • @Bean 메서드 정의:
    • engine() 메서드는 Engine 객체를 생성하고, 그 타입을 "V8 Engine"으로 설정한 후 스프링 컨텍스트에 빈으로 등록합니다.
    • car() 메서드는 Car 객체를 생성하고, 그 모델을 "Sports Car"로 설정합니다. 여기서 engine() 메서드를 호출하여 Engine 객체를 생성 및 주입한 후, Car 빈으로 등록합니다.
  • 직접 메서드 호출을 통한 빈 주입:
    • car() 메서드 안에서 engine() 메서드를 직접 호출하여 Car 객체에 Engine 객체를 주입합니다.
    • engine() 메서드가 Car 메서드 안에서 여러 번 호출되더라도 스프링이 이 메서드를 프록시로 관리하기 때문에 하나의 Engine 빈만 생성됩니다.
  • 스프링의 빈 관리:
    • 스프링은 @Configuration 클래스 내의 @Bean 메서드들을 자동으로 프록시 객체로 감쌉니다. 그래서 engine() 메서드가 여러 번 호출되더라도 매번 새 객체가 만들어지지 않고, 첫 번째 호출 시 생성된 Engine 객체를 반환하게 됩니다. 즉, 하나의 Engine 객체만 생성되고 재사용됩니다.

  • 실행 
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(VehicleConfig.class);

        Car car = context.getBean(Car.class);
        car.drive();  // "Driving a Sports Car with V8 Engine" 출력
    }
}

 

 

  1. 프록시 패턴으로 빈 관리:
    • 스프링이 @Configuration 클래스의 @Bean 메서드들을 프록시로 감싸기 때문에, 동일한 메서드를 여러 번 호출하더라도 새로운 객체를 생성하지 않고, 기존에 생성된 빈을 반환합니다.
  2. 빈 간 의존성 주입:
    • car() 메서드가 engine() 메서드를 호출해 Car 객체에 Engine 객체를 주입하는 방식은, 스프링의 DI(의존성 주입) 개념을 활용한 것입니다. 스프링 컨텍스트에서 관리되는 빈들은 서로 필요에 따라 주입될 수 있습니다.
  3. 빈 재사용:
    • 동일한 타입의 빈이 여러 번 필요할 때도, 스프링 컨텍스트는 처음 생성된 빈을 계속 재사용하여 메모리 효율성을 높입니다.

 


 

2) @Bean 메서드의 매개변수를 통한 빈 와이어링

 

 

 @Bean 메서드의 매개변수로 다른 빈을 주입받아 사용합니다. 스프링은 필요한 빈을 자동으로 생성한 후, 해당 빈을 @Bean 메서드의 매개변수로 전달하여 의존성을 주입합니다. 이 방식은 @Autowired 애너테이션 없이도 명시적으로 의존성을 설정할 수 있어 가독성이 높아집니다.

 

  • 구성 클래스 정의
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class VehicleConfig {

    @Bean
    public Engine engine() {
        // Engine 빈 생성
        Engine engine = new Engine();
        engine.setType("V8 Engine");
        return engine;
    }

    // Engine 빈을 매개변수로 주입받아 Car 빈 생성
    @Bean
    public Car car(Engine engine) {
        Car car = new Car();
        car.setModel("Sports Car");
        car.setEngine(engine); // 매개변수로 받은 Engine 빈 주입
        return car;
    }
}

 


  • 클래스 정의
public class Engine {
    private String type;

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }
}

public class Car {
    private String model;
    private Engine engine;

    public String getModel() {
        return model;
    }

    public void setModel(String model) {
        this.model = model;
    }

    public Engine getEngine() {
        return engine;
    }

    public void setEngine(Engine engine) {
        this.engine = engine;
    }

    public void drive() {
        System.out.println("Driving a " + model + " with " + engine.getType());
    }
}

 


  • 실행
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        // 스프링 컨텍스트 초기화 및 빈 가져오기
        ApplicationContext context = new AnnotationConfigApplicationContext(VehicleConfig.class);

        // Car 빈 가져오기
        Car car = context.getBean(Car.class);

        // Car의 기능 실행
        car.drive();
        // 출력: Driving a Sports Car with V8 Engine
    }
}

 

 

 

  • @Bean 메서드에서 매개변수로 빈 주입
    • VehicleConfig 클래스는 @Configuration을 사용해 스프링 구성 클래스로 설정되며, 스프링 컨텍스트가 해당 클래스 내의 @Bean 메서드를 호출하여 빈을 생성하고 관리합니다.
    • car() 메서드는 Engine 빈을 매개변수로 주입받아 Car 빈을 생성하고, 주입받은 Engine 빈을 Car 객체에 설정합니다.
  • 스프링의 빈 관리
    • 스프링 컨텍스트는 Car 빈을 생성하기 전에 Engine 빈을 먼저 생성합니다. 그리고 car() 메서드가 호출될 때 Engine 빈을 매개변수로 전달하여 Car 빈에 주입합니다.
    • 이 과정을 통해 @Autowired 애너테이션 없이도 스프링 컨텍스트에서 자동으로 의존성을 주입할 수 있습니다.
  • 프록시 패턴을 통한 빈 관리
    • 스프링은 @Configuration 클래스를 프록시 객체로 감싸 관리하기 때문에, 동일한 @Bean 메서드가 여러 번 호출되더라도 동일한 빈을 재사용합니다.
    • Engine 빈은 car() 메서드에서 여러 번 호출되더라도 하나의 인스턴스만 생성되고, 이를 재사용하게 됩니다.

매개변수를 통한 빈 와이어링의 장점

  1. 명확한 의존성 표현:
    • @Bean 메서드의 매개변수로 빈을 주입받으면, 해당 빈이 다른 빈에 의존하고 있음을 명확하게 표현할 수 있습니다. 코드만 봐도 어떤 빈이 필요하고, 어떻게 주입되는지 직관적으로 이해할 수 있습니다.
  2. 자동 관리:
    • 스프링은 자동으로 빈을 관리하며, 의존성 주입 순서도 자동으로 처리해 줍니다. 개발자가 직접 메서드를 호출하거나 객체를 생성하는 번거로움이 없습니다.
  3. 중복 생성 방지:
    • 스프링이 프록시 패턴을 사용해 동일한 빈을 여러 번 호출하더라도 중복 생성하지 않고, 항상 같은 빈을 사용하므로 메모리 효율성이 높습니다.

2. @Autowired 애너테이션을 사용한 빈 주입

 1) 필드 주입

 2) Setter 메서드를 통한 주입

 3) 생성자 주입

 

@Autowired 애너테이션

 

  • @Autowired 애너테이션은 스프링이 자동으로 의존성을 주입할 수 있도록 도와줍니다.
  • 스프링 컨텍스트에서 관리하는 빈 중에서 해당 클래스에서 필요로 하는 빈을 찾아서 자동으로 주입합니다.
  • @Autowired는 생성자, 필드, setter 메서드에 적용할 수 있으며, 각각의 방식에 따라 의존성 주입이 이뤄집니다.

 1) 필드 주입

  • 필드에 직접 @Autowired를 적용하여 주입할 수 있습니다.
  • 필드 주입 방식은 간단하지만 테스트나 유지보수에서 단점이 있을 수 있어 권장되지 않는 경우가 많습니다.
public class Car {
    @Autowired
    private Engine engine;
    private String model = "Sports Car";

    public void drive() {
        System.out.println("Driving a " + model + " with " + engine.getType());
    }
}

 


 

2) Setter 메서드를 통한 주입

  • @Autowired 애너테이션을 setter 메서드에 적용하여 의존성을 주입할 수도 있습니다. 이 방식은 객체 생성 후에도 의존성을 주입할 수 있는 유연성을 제공합니다.
public class Car {
    private Engine engine;
    private String model = "Sports Car";

    @Autowired
    public void setEngine(Engine engine) {
        this.engine = engine;
    }

    public void drive() {
        System.out.println("Driving a " + model + " with " + engine.getType());
    }
}

 


 

3) 생성자 주입

 

 

  • 생성자 주입 방식은 권장되는 방식입니다. 주입받을 의존성을 명확하게 알 수 있으며, 테스트하기도 쉽습니다.
  • 생성자를 통해 필수적인 의존성을 설정할 수 있고, 객체의 불변성을 보장하는 데 도움을 줍니다.
public class Engine {
    private String type;

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }
}

public class Car {
    private String model = "Sports Car";
    private Engine engine;

    // 생성자 주입: 생성자에서 Engine 객체를 받아 주입
    public Car(Engine engine) {
        this.engine = engine;
    }

    public void drive() {
        System.out.println("Driving a " + model + " with " + engine.getType());
    }
}

@Autowired의 장점

  1. 자동 의존성 해결:
    • @Autowired 애너테이션은 스프링 컨텍스트에서 관리하는 빈을 자동으로 주입해 주므로, 개발자는 객체 간의 관계를 쉽게 설정할 수 있습니다.
  2. 유연한 사용 방식:
    • 생성자, 필드, setter 메서드 등 다양한 방식으로 사용할 수 있어 상황에 맞게 의존성을 설정할 수 있습니다.
  3. 가독성 향상:
    • 의존성이 명시적으로 주입되므로, 코드의 가독성이 높아집니다. 특히 생성자 주입 방식을 사용할 때, 어떤 빈이 필요한지 명확히 알 수 있습니다.

3.  동일한 타입의 빈 의 여러개 일때

스프링에서 매개변수 또는 클래스 필드에 값을 주입해야 할 때, 동일한 타입의 빈이 여러 개 있을 경우 어떤 빈을 선택해서 주입할지를 명확하게 지정해야 할 때가 있습니다. 이때 사용할 수 있는 주요 기법이 @Qualifier 애너테이션입니다. 스프링은 기본적으로 동일한 타입의 여러 빈이 존재할 때 어느 빈을 주입해야 할지 혼란을 겪을 수 있으므로, 명확한 빈 선택을 위해 이 애너테이션을 사용하게 됩니다.

 

1) @Qualifier 애너테이션을 사용하여 빈 선택하기

2) 필드 주입 시 @Qualifier 사용

3) @Primary 애너테이션 사용

4) @Primary와 @Qualifier 혼합 사용

 

 

  • @Qualifier: 동일한 타입의 빈이 여러 개 있을 때, 특정 빈을 선택해 주입할 수 있는 애너테이션입니다. 생성자, 필드, setter 모두에 사용할 수 있습니다.
  • @Primary: 빈 중에서 기본적으로 주입할 빈을 지정하는 애너테이션입니다. @Qualifier로 지정한 빈이 우선하지만, 지정하지 않으면 @Primary가 지정된 빈이 주입됩니다.
  • 필드 주입, 생성자 주입: @Qualifier와 @Primary는 필드 주입, 생성자 주입 등 모든 의존성 주입 방식에서 사용할 수 있습니다.

[기본 문제 상황]

  • 동일한 타입의 빈이 여러 개 정의되어 있는 상황에서, 스프링은 어느 빈을 주입해야 할지 결정할 수 없습니다.
  • 예를 들어, Car라는 타입의 빈이 여러 개 존재할 때, 스프링이 @Autowired를 통해 빈을 주입할 때 어떤 빈을 사용할지 혼란을 겪습니다.
@Bean
public Car carA() {
    return new Car("Model A");
}

@Bean
public Car carB() {
    return new Car("Model B");
}

 

 

위 예시처럼 Car 타입의 빈이 두 개 정의되어 있을 경우, 스프링은 어느 빈을 주입할지 모호해지므로 명시적으로 빈을 지정해야 합니다.


1) @Qualifier 애너테이션을 사용하여 빈 선택하기

 

@Qualifier 애너테이션을 사용하여 어떤 빈을 선택할지 명시할 수 있습니다. 아래 예시는 Car 빈 두 개(carA와 carB)를 정의하고, @Qualifier 애너테이션을 사용해 특정 빈을 주입받는 방법을 보여줍니다.


구성 클래스

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

@Configuration
public class VehicleConfig {

    @Bean
    public Car carA() {
        return new Car("Model A");
    }

    @Bean
    public Car carB() {
        return new Car("Model B");
    }
}

클래스 정의

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
public class CarService {

    private final Car car;

    // @Qualifier로 주입할 빈을 명시적으로 지정
    @Autowired
    public CarService(@Qualifier("carA") Car car) {
        this.car = car;
    }

    public void drive() {
        System.out.println("Driving " + car.getModel());
    }
}

 


실행 클래스

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        // 스프링 컨텍스트 초기화
        ApplicationContext context = new AnnotationConfigApplicationContext(VehicleConfig.class);

        // CarService 빈 가져오기
        CarService carService = context.getBean(CarService.class);

        // 주입된 Car 빈 사용
        carService.drive();
        // 출력: Driving Model A
    }
}

 

 

  1. VehicleConfig 클래스에서 두 개의 Car 빈(carA와 carB)을 정의했습니다. 이 두 빈은 동일한 Car 타입이지만 서로 다른 모델을 가지고 있습니다.
  2. CarService 클래스에서 Car 타입의 빈을 주입받을 때, @Qualifier("carA") 애너테이션을 사용하여 어떤 빈을 주입받을지 명시했습니다. 이 경우 carA 빈이 주입됩니다.
  3. 실행 시 CarService 클래스에서는 carA 빈을 주입받아 "Driving Model A"가 출력됩니다.

2) 필드 주입 시 @Qualifier 사용

 

@Qualifier는 생성자뿐만 아니라 필드 주입(Field Injection)에도 사용할 수 있습니다. 필드에 직접 주입할 때도 동일한 방식으로 사용하여 특정 빈을 선택할 수 있습니다.

@Component
public class CarService {

    @Autowired
    @Qualifier("carB") // carB 빈을 주입
    private Car car;

    public void drive() {
        System.out.println("Driving " + car.getModel());
    }
}

이 경우 carB 빈이 주입되어 "Driving Model B"가 출력됩니다.


3) @Primary 애너테이션 사용

 

스프링은 또 다른 방법으로 @Primary 애너테이션을 통해 기본 빈을 지정할 수 있습니다. 동일한 타입의 빈이 여러 개 있을 때, @Primary 애너테이션을 적용한 빈이 우선적으로 주입됩니다. @Primary를 사용하면 명시적으로 @Qualifier를 지정하지 않더라도 기본으로 사용할 빈을 지정할 수 있습니다.

 

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

@Configuration
public class VehicleConfig {

    @Bean
    @Primary // 기본으로 주입될 빈 지정
    public Car carA() {
        return new Car("Model A");
    }

    @Bean
    public Car carB() {
        return new Car("Model B");
    }
}
@Component
public class CarService {

    @Autowired
    private Car car; // @Qualifier 없이 자동으로 @Primary가 붙은 carA 빈이 주입됨

    public void drive() {
        System.out.println("Driving " + car.getModel());
    }
}

 

  • 위 예제에서는 carA에 @Primary 애너테이션이 붙어 있기 때문에, @Qualifier를 사용하지 않더라도 기본적으로 carA가 주입됩니다.
  • 주입된 Car 빈을 사용하여 carA가 선택되며, "Driving Model A"가 출력됩니다.

4) @Primary와 @Qualifier 혼합 사용

 

@Primary와 @Qualifier를 혼합해서 사용할 수 있습니다. @Primary로 기본 빈을 지정하더라도, @Qualifier를 사용해 특정 빈을 선택하면 해당 빈이 우선적으로 주입됩니다.

 

@Configuration
public class VehicleConfig {

    @Bean
    @Primary
    public Car carA() {
        return new Car("Model A");
    }

    @Bean
    public Car carB() {
        return new Car("Model B");
    }
}
@Component
public class CarService {

    @Autowired
    @Qualifier("carB") // carB를 명시적으로 주입받음
    private Car car;

    public void drive() {
        System.out.println("Driving " + car.getModel());
    }
}

 

  • carA에 @Primary가 지정되어 있지만, @Qualifier("carB")로 명시적으로 carB를 선택했기 때문에 carB 빈이 주입됩니다.
  • 결과적으로 "Driving Model B"가 출력됩니다.