스프링 빈 등록, DI, IoC 궁금증 해결
궁금증1
[같이 공부하시는 엘리트 분 답변1. ]
메인 메서드는 실행을 담당하고,
프로그램이 실행된 상태에서 외부로부터 API를 통해 통신이 오면 내부 객체들끼리 협동을 하면서 작업을 수행하는데요.
@Configuration이나 @Bean이나 @Component 등의 어노테이션이 붙어 있으면 (3개 말고도 여러개 있음)
이 과정에서 매번 새로 객체를 생성할 필요 없이 실행될 때 한 번 객체를 미리 생성해 놓고 기다리고 있는거죠.
그러면 객체를 따로 생성하지 않아도 @Autowired로 주입하면 그 객체를 어디서나 사용할 수 있어요. 그래서 싱글톤 패턴이라고 하신 것입니다.
그렇지만, 모든 클래스에서 사용하지 않습니다.
예를들어 회원이나 주문 클래스같은 경우 인스턴스마다 다른 상태(필드 값)를 가지기 때문에 싱글톤으로 쓰기는 어려워요.
물론 @Autowired를 사용하지 않고 그냥 새로 생성자 호출해서도 사용할 수 있습니다.
그러면 왜 굳이 이렇게 하느냐 하면... 의존성을 주입하면 프레임워크가 객체를 대신 생성해주니까 우리는 편리하고,
객체를 생성할 때마다 매번 바뀌는 클래스 상태를 고렿하지 않고 그냥 생성된 채로 사용만 하면 되죠.
그래서 핵심적인 로직에만 집중할 수 있게 되고요.
객체를 직접 생성하지 않게 되면 객체 간 의존성이 낮아집니다.
그래서 다른 클래스의 코드와 관여할 필요가 없어지고, 자신의 로직에만 집중하게 되서 변경에 유연해지게 됩니다.
private final 필드는 아마 @RequiredArgsConstructor와 같이 사용하는 것을 말씀하시는 것인지..? 이 경우에는 @Autowired의 생성자 주입 방식에서 생성자가 하나인 경우 @Autowired가 생략된 것인데요.
private final을 사용하더라도 @Autowired는 필요합니다.
만약 생성자 주입 방식을 사용하지 않는다면 메서드나 필드에 직접 @Autowired를 명시해야 의존성 주입을 할 수 있습니다.
말씀하신대로 새로운 객체를 생성하지 않고 이미 생성되어 있는 하나의 객체를 주입해서 계속 사용하는 것이 맞습니다.
아 그리고 필드에 @Autowired로 주입하는 방식은 지금은 프로덕션 코드에서 잘 사용되지 않는데, 테스트 코드를 작성할 때 주로 사용합니다.
[답변2.]
IoC는 프로그램의 제어 흐름을 역전시키는 개념입니다. 전통적으로 메인 메서드나 애플리케이션 코드에서 프로그램의 흐름을 제어했지만, IoC에서는 프레임워크나 컨테이너가 그 역할을 합니다.
@Configuration이 붙은 클래스는 스프링 설정 정보를 담은 클래스로, 메인 메서드의 역할을 대신하지 않습니다. 대신 스프링의 ApplicationContext(또는 DI 컨테이너, 스프링 컨테이너)가 이 설정 정보를 읽어들여 빈들을 생성하고 관리합니다.
@Component (또는 @Service, @Repository, @Controller 등)를 사용하면 해당 클래스는 스프링 컨테이너가 알아서 빈을오 등록하게 됩니다. 이것은 @Configuration과 함께 사용하거나 별도로 사용할 수 있습니다.
DI는 객체가 직접 의존성을 만들어내는 것이 아니라 외부에서 의존성을 주입받는 방식입니다.
스프링에서 ApplicatinoContext는 DI 컨테이너의 역할을 합니다. 이 컨테이너는 미리 생성된 스프링 빈(객체)을 관리하며, 필요한 곳에 주입합니다.
private final은 필드에 대한 불변성을 표현하는 것이고, 생성자 주입 방식에서 주로 사용됩니다. @Autowired는 스프링이 자동으로 해당 타입의 빈을 찾아 주입해주는 어노테이션입니다. 즉, 객체를 직접 생성하는 것이 아니라, 컨테이너에 이미 생성된 빈을 주입받게 됩니다.
요약하면, 스프링 프레임워크를 사용하면 객체의 생성과 의존 관계 주입을 스프링 컨테이너가 대신 관리해주며, 이를 통해 개발자는 비즈니스 로직에 집중할 수 있게 됩니다.
**추가 궁금증1
IoC는 프로그램의 제어 흐름을 역전시키는 개념입니다. 전통적으로 메인 메서드나 애플리케이션 코드에서 프로그램의 흐름을 제어했지만, IoC에서는 프레임워크나 컨테이너가 그 역할을 합니다.
Q: IoC에서는 그럼 이렇게
@SpringBootApplication
public class YeahnoljaApplication {
public static void main(String[] args) {
SpringApplication.run(YeahnoljaApplication.class, args);
}
}
메인메서드가 있는 프로젝트 실행시키는 Application클래스가 제어한다는 의미가 아니라, 프레임워크나 컨테이너가 제어하는 역할을 하는 것이라는 뜻이라고??
A: 전통적인 애플리케이션 개발에서는 메인 메서드나 특정 코드가 프로그램의 전체 흐름을 제어합니다.
즉 어떤 객체를 생성하고 어떤 메서드를 언제 호출할지 등의 제어 흐름이 개발자의 코드에 의해 결정됩니다.
그러나, IoC를 적용한 프레임워크, 특히 스프링과 같은 프레임워크에서는 이러한 제어 흐름이 역전됩니다.
애플리케이션의 흐름은 스프링 프레임워크나 그 안의 컨테이너에 의해 주도되게 됩니다.
예를 들어, SpringApplication.run(YeahnoljaApplication.class, args);을 실행하면,
이 메서드는 스프링 프레임워크가 제어하는 컨텍스트 내에서 어플리케이션을 실행하게 됩니다. 이후 스프링 컨테이너는 어플리케이션의 빈들을 생성, 초기화, 연결(DI) 등의 작업을 수행하게 됩니다.
따라서, 개발자는 이러한 기본적인 제어 흐름에 대해 걱정할 필요가 없게 되며, 비즈니스 로직에 집중할 수 있습니다.
YeahnoljaApplication의 main 메서드가 직접적으로 어플리케이션의 모든 동작을 제어하는 것이 아니라,
그저 어플리케이션의 시작점일 뿐입니다. 실제로는 스프링 프레임워크와 컨테이너가 어플리케이션의 주요 제어를 담당하게 됩니다.
**추가궁금증2
@Component (또는 @Service, @Repository, @Controller 등)를 사용하면 해당 클래스는 스프링 컨테이너가 알아서 빈을오 등록하게 됩니다. 이것은 @Configuration과 함께 사용하거나 별도로 사용할 수 있습니다.
Q : @Configuration은 @Bean과 같이 따로 설정클래스를 만들어서 보통 쓰이니까
@Component (또는 @Service, @Repository, @Controller 등)와는 별개로 쓰이는 것이고
2가지를 같이 쓰면 안되는거 아니야?
A : @Configuration과 @Component(또는 @Service, @Repository, @Controller 등)는 서로 다른 목적으로 사용되는 어노테이션입니다. 그러나 그들은 동시에 같은 스프링 프로젝트 내에서 사용될 수 있습니다.
1. @Component (또는 @Service, @Repository, @Controller 등) :
클래스 위에 이 어노테이션들 중 하나를 사용하면 해당 클래스를 스프링 빈으로 자동 등록하게 됩니다.
이러한 어노테이션들은 클래스의 종류나 목적에 따라 세분화되어 사용됩니다. 예를들면, @Service는 서비스 계층, @Repository는 데이터 엑세스 계층, @Controller는 웹 계층에서 사용됩니다.
2. @Configuration :
이 어노테이션은 해당 클래스가 스프링 설정 정보를 포함하고 있다는 것을 나타냅니다.
@Bean어노테이션과 함께 사용되어, 스프링 컨테이너에 빈을 수동으로 등록하는 역할을 합니다.
두 어노테이션은 서로 배타적이지 않습니다.
즉, @Component계열의 어노테이션으로 자동 등록되는 빈들과
@Configuration 클래스 내에서 @Bean으로 수동 등록되는 빈들이 동시에 같은 스프링 컨테이너 내에서 함께 존재할 수 있습니다.
그러나, 일반적으로 하나의 클래스에서 @Configuration과 @Component 계열의 어노테이션을 동시에 사용하는 것은 권장되지 않습니다. 각 클래스는 특정 목적에 맞게 설계되어야하며, 이 두 어노테이션을 동시에 사용하는 것은 혼란을 초래할 수 있습니다.
**추가 궁금증3
DI는 객체가 직접 의존성을 만들어내는 것이 아니라 외부에서 의존성을 주입받는 방식입니다.
Q : 위 문장은 무슨 의미야? 객체가 다른 클래스에서도 사용될 수 있도록 @Component나@Service등을 설정해서 의존성을 직접 만들어내는 거 아니야? 근데 외부에서 의존성을 주입받는 방식이라는게 무슨 소리지?
A :
1. 객체가 직접 의존성을 만들어내는 것
예를 들어, Car 클래스가 있고, Car는 Engine을 필요로 합니다.
Car가 직접 Engine 객체를 생성하는 경우
public class Car {
private Engine engine;
public Car() {
this.engine = new Engine(); //Car가 직접 Engine의 인스턴스를 생성
}
}
여기서 Car는 Engine에 직접적으로 의존하고 있습니다. Engine의 구현이 바뀌거나 다른 종류의 Engine을 사용하려면 Car 코드를 수정해야합니다.
2. 외부에서 의존성을 주입받는 방식
예를 들어, Car 클래스가 있고, Car는 Engine을 필요로 합니다.
Car가 Engine 객체를 직접 생성하는 대신, 생성자나 메서드를 통해 외부에서 Engine 객체를 받아 사용하는 경우
public class Car {
private Engine engine;
public Car(Engine engine) { //Engine 객체가 외부에서 주입됨
this.engine = engine;
}
}
이렇게 하면 Car는 Engine의 특정 구현에 의존하지 않게 됩니다.
다양한 Engine객체를 주입받아 사용할 수 있게 됩니다.
스프링에서는 이러한 의존성 주입을 더욱 쉽게 관리하도록 도와주는데,
@Autowired나 생성자 주입 방식 등을 통해 개발자는 객체의 생성과 의존성 주입을 스프링 컨테이너에 맡길 수 있습니다. 이 때, @Component, @Service등의 어노테이션은 클래스를 스프링 빈으로 등록하는 역할을 합니다.
그리고 스프링 컨테이너는 이러한 빈들의 의존성을 관리하고 필요한 곳에 주입해줍니다.
요약하자면, 외부에서 의존성을 주입받는 방식은 객체가 자신의 의존성을 직접 관리하는 대신,
외부(스프링 컨테이너)에서 필요한 의존성을 주입받아 사용하는 방식을 의미합니다.
**추가 궁금증4
@Autowired는 스프링이 자동으로 해당 타입의 빈을 찾아 주입해주는 어노테이션입니다. 즉, 객체를 직접 생성하는 것이 아니라, 컨테이너에 이미 생성된 빈을 주입받게 됩니다.
Q : 보통 private final HotelService hotelService; 이렇게 설정해서 사용하는 것도 생성된 빈을 주입받는 거 아니야? @Autowired랑 다를게 없는데?
A : 맞습니다. private final HotelService hotelService; 형태로 필드에 선언하고, 생성자 주입 방식을 사용하는 경우에도 스프링 컨테이너에 등록된 빈을 주입받게 됩니다.
사실, 최근의 스프링에서는@Autowired를 사용하는 대신 생성자 주입 방식을 권장하고 있습니다. 이유는 다음과 같습니다.
1. 불변성 : 생성자 주입을 사용하면, final 키워드를 붙여 필드의 불변성을 보장할 수 있습니다.
2. 순환 참조 : 생성자 주입을 사용하면 컴파일 시점에 순환 참조를 방지할 수 있습니다.
3. 테스트 용이성 : 생성자 주입을 사용하면 테스트 코드 작성 시 Mock 객체를 주입하기 쉽습니다.
즉, @Autowired와 생성자 주입 모두 스프링 컨테이너에서 관리되는 빈을 주입받는 방식입니다.
그러나 생성자 주입 방식은 위에서 언급한 이유로 더 권장되는 방식입니다.
궁금증2
답변 : 다 맞음.