빈(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 프레임워크를 배우는 과정에서 익숙치 않더라도 천천히 익혀가면 더 쉽게 이해하실 수 있을 거예요.