
빈(Bean)은 Spring 프레임워크에서 관리하는 객체를 의미합니다. Spring은 애플리케이션에서 필요한 객체들을 생성하고, 그 객체들 간의 의존성을 관리합니다. 이 객체들을 빈(Bean)이라고 부릅니다.
@Component, @Service, @Repository, @Controller 등의 어노테이션을 사용하여 클래스가 빈으로 등록될 수 있습니다.비유: 빈은 마치 주방에서 사용하는 요리 도구(예: 냄비, 프라이팬, 주걱 등)라고 생각하면 돼요.
설명: 주방에서 요리를 할 때 필요한 도구들을 싱크대나 서랍에서 꺼내 사용하는 것처럼, Spring 애플리케이션에서 필요한 객체들을 빈(Bean) 컨테이너에서 꺼내 사용하는 거예요. Spring은 요리 도구를 미리 준비해 놓는 주방이라고 생각하면 돼요.
빈에서의 의미: Spring 컨테이너가 관리하는 객체들을 빈이라고 합니다.
@Autowired는 Spring에서 의존성 주입(Dependency Injection)을 하기 위해 사용되는 어노테이션입니다. 의존성 주입이란, 클래스가 필요로 하는 의존 객체를 외부에서 제공(주입)하는 것을 말합니다.
@Component
public class Car {
@Autowired
private Engine engine;
}
Car 클래스는 Engine 객체가 필요합니다. @Autowired 어노테이션을 사용하면 Spring이 자동으로 Engine 객체를 생성하여 Car 클래스에 주입해줍니다.비유:
@Autowired는 마치 주방 로봇이 필요한 요리 도구를 자동으로 찾아서 건네주는 기능이라고 생각하면 돼요.
설명: 우리가 요리할 때 "주방 로봇, 냄비 좀 줘"라고 말하면, 주방 로봇이 알아서 냄비를 찾아서 건네주는 것처럼,
@Autowired는 Spring이 필요한 빈을 자동으로 찾아서 주입(건네주는 것)해주는 기능이에요.
빈에서의 의미:
@Autowired어노테이션은 Spring이 자동으로 객체를 주입해주는 것을 의미합니다.
private final은 Java에서 필드의 값을 한 번만 설정할 수 있도록 하는 접근 제어자입니다. final로 선언된 필드는 한 번 값이 설정되면 변경할 수 없습니다.
private: 해당 필드는 클래스 내부에서만 접근할 수 있습니다.final: 해당 필드는 한 번 초기화된 후에 값을 변경할 수 없습니다.public class Car {
private final Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
}
위 코드에서 Engine 객체는 생성자를 통해 초기화되고, 이후에는 변경할 수 없습니다. 이는 클래스의 불변성을 유지하는 데 도움이 됩니다.
비유:
private final은 마치 비밀 금고에 넣어두고 절대 바꿀 수 없는 보물과 같아요.
설명: 비밀 금고에 넣은 보물은 한 번 넣으면 누구도 바꿀 수 없잖아요?
private final도 마찬가지로, 한 번 값을 설정하면 변경할 수 없도록 하는 거예요. "private"은 금고에 접근할 수 있는 사람을 제한하는 것이고, "final"은 금고 안의 보물을 바꾸지 못하게 하는 거예요.
빈에서의 의미:
private final은 한 번 값이 설정되면 변경할 수 없는 필드를 의미합니다.
생성자 주입은 의존성 주입의 한 방법으로, 객체의 생성자를 통해 의존성을 주입하는 방식입니다.
@Component
public class Car {
private final Engine engine;
@Autowired
public Car(Engine engine) {
this.engine = engine;
}
}
위 코드에서 Car 클래스는 생성자를 통해 Engine 객체를 주입받습니다. 생성자 주입의 장점은 의존성이 필수임을 보장하고, 객체가 생성될 때 의존성이 주입되어 객체의 상태가 일관성을 유지할 수 있다는 점입니다.
비유: 생성자 주입은 마치 새로운 요리 도구 세트를 살 때, 미리 도구들을 세트로 준비하는 것과 같아요.
설명: 새로운 요리 세트를 살 때 프라이팬, 냄비, 주걱이 모두 포함된 세트를 사면 편리하잖아요? 생성자 주입도 비슷해요. 객체를 만들 때 필요한 다른 객체들을 미리 준비해서 같이 만드는 거예요.
빈에서의 의미: 생성자 주입은 객체를 생성할 때 필요한 다른 객체들을 생성자에서 주입받는 것을 의미합니다.
이 코드에서 사용된 각 요소와 그 의미를 설명해드릴게요.
비유: @Component는 마치 "이 클래스는 주방 도구 중 하나예요"라고 알려주는 라벨이에요.
설명: @Component 어노테이션은 Spring에게 이 클래스가 빈(Bean)으로 관리되어야 하는 대상임을 알려줍니다. Spring은 이 어노테이션이 붙은 클래스를 스캔하여 빈으로 등록합니다.
의미: @Component는 해당 클래스가 Spring 컨테이너에 의해 관리되는 빈임을 나타냅니다.
비유: public class Car는 "이것은 Car라는 이름의 요리 도구예요"라고 말하는 것과 같아요.
설명: public class Car는 Car라는 이름의 클래스를 정의합니다. 이 클래스는 객체를 생성할 수 있는 청사진(템플릿) 역할을 합니다.
의미: public class Car는 Car라는 클래스를 정의합니다.
비유: private final Engine engine는 "이 Car는 항상 같은 엔진을 사용해요"라고 말하는 것과 같아요.
설명: private 접근 제어자는 이 필드가 클래스 내부에서만 접근 가능함을 의미합니다. final 키워드는 이 필드가 한 번 초기화된 후에 변경할 수 없음을 의미합니다. 즉, 이 Car 객체가 생성될 때 엔진이 설정되면, 이후에는 그 엔진을 변경할 수 없습니다.
의미: private final Engine engine는 Car 클래스 내에서만 접근 가능한 변경할 수 없는 필드를 정의합니다.
비유: @Autowired는 "Spring 로봇이 자동으로 필요한 엔진을 이 Car에 제공해줘요"라고 말하는 것과 같아요.
설명: @Autowired 어노테이션은 Spring에게 해당 필드나 생성자 또는 메서드에 의존성을 자동으로 주입하라고 지시합니다. Spring은 빈 컨테이너에서 해당 타입의 빈을 찾아서 주입합니다.
의미: @Autowired는 Spring이 자동으로 의존성을 주입하도록 지시합니다.
비유: public Car(Engine engine)는 "이 Car는 엔진을 받아서 만들어져요"라고 말하는 것과 같아요.
설명: 생성자는 객체가 생성될 때 호출되는 특별한 메서드입니다. 이 생성자는 Engine 타입의 파라미터를 받아서 Car 객체를 초기화합니다.
의미: public Car(Engine engine)는 Engine 객체를 파라미터로 받아 Car 객체를 초기화하는 생성자를 정의합니다.
비유: this.engine = engine는 "이 Car는 받아온 엔진을 자신의 엔진으로 설정해요"라고 말하는 것과 같아요.
설명: this.engine은 현재 객체의 엔진 필드를 가리킵니다. engine은 생성자 파라미터로 전달된 엔진 객체입니다. this.engine = engine;은 전달된 엔진 객체를 현재 객체의 엔진 필드에 할당합니다.
의미: this.engine = engine;는 생성자 파라미터로 받은 엔진 객체를 현재 객체의 엔진 필드에 할당합니다.
이 코드는 Car 클래스가 Spring 컨테이너에 의해 관리되는 빈임을 나타내고, 생성자를 통해 Engine 객체를 주입받아 초기화함을 의미합니다. @Autowired 어노테이션을 사용하여 Spring이 자동으로 Engine 객체를 찾아 Car 객체에 주입합니다. 한 번 주입된 Engine 객체는 final 키워드로 인해 변경할 수 없습니다.
순환 의존성(Circular Dependency)은 두 개 이상의 빈이 서로를 참조하는 상황을 말합니다.
예를 들어, ClassA가 ClassB를 참조하고, ClassB가 다시 ClassA를 참조하면 순환 의존성이 발생합니다.
이는 Spring 컨테이너가 빈을 생성하는 과정에서 무한 루프에 빠져서 애플리케이션이 제대로 동작하지 않게 만듭니다.
@Component
public class ClassA {
@Autowired
private ClassB classB;
}
@Component
public class ClassB {
@Autowired
private ClassA classA;
}
비유: 빈 순환 의존성은 마치 두 친구가 서로의 집 열쇠를 가지고 있어야만 집에 들어갈 수 있는 상황과 같아요.
설명: 친구 A와 친구 B가 서로의 집 열쇠를 가지고 있어야만 집에 들어갈 수 있는데, A가 B의 열쇠를 가지고 있고, B가 A의 열쇠를 가지고 있다면, 둘 다 집에 들어갈 수 없겠죠? 빈 순환 의존성도 비슷해요. 두 객체가 서로를 필요로 하면, 어느 것도 먼저 만들어질 수 없어서 문제가 생기는 거예요.
빈에서의 의미: 빈 순환 의존성은 두 개 이상의 빈이 서로를 참조할 때 발생하는 문제입니다.
@Lazy 어노테이션 사용: 주입을 지연시켜 순환 의존성을 완화할 수 있습니다.import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/image")
public class ImageController {
private final UserService userService;
private final S3Service s3Service;
@Autowired
public ImageController(@Lazy UserService userService, S3Service s3Service) {
this.userService = userService;
this.s3Service = s3Service;
}
// 기타 메서드
}
import org.springframework.stereotype.Service;
@Service
public class UserService {
// UserController를 참조하지 않도록 설계 변경
// 기타 메서드
}
하지만 이러한 lazy 의 사용은 가능한 사용하지 않는것을 권장하고 최대한 서로간의 의존성이 부딪치지 않게 설계하는것이 더 중요합니다.
이 경우 서로 순환되지 않게 의존성을 변경하거나 혹은 공동으로 사용되는 클래스를 따로 빼서 지정하는 방법도 유용합니다
이 구조에서는 서로가 서로를 참조하고 있어서, Spring이 어떤 빈을 먼저 생성해야 할지 알 수 없게 됩니다.
UserController ---> UserService
^ |
| v
ImageController <--- ImageService
TokenService를 분리하면, 서로 얽히지 않고 명확한 참조 관계를 유지할 수 있습니다.
이 구조에서는 UserService와 ImageController가 TokenService를 참조하지만, 서로를 참조하지 않기 때문에 순환 의존성이 발생하지 않습니다.
UserController ---> UserService ---> TokenService
^
|
ImageController ---> TokenService
이제 각 개념을 비유와 함께 설명해드렸습니다. 조금 더 이해하기 쉬워졌기를 바랍니다. Spring 프레임워크를 배우는 과정에서 익숙치 않더라도 천천히 익혀가면 더 쉽게 이해하실 수 있을 거예요.