머리글
스프링을 다루다 보면 스프링의 기본 개념과 어떻게 동작하는지 혼동이 올 때가 있습니다. 오늘 포스팅 게시글에서는 스프링의 주요 개념 (직접 언급 :Spring Context, 간접 언급 : IoC, DI, AOP, POJO) 에 대해 알아보고 정리해보도록 하겠습니다.
스프링 컨텍스트
스프링 컨텍스트란 무엇일까요?
스프링은 굉장히 강력한 프레임워크라고 할 수 있습니다. 그렇다면 이 프레임워크는 왜 사용하는걸까요?
하나의 문장으로 정의하자면 " 개발에만 집중할 수 있게 " 라고 정의할 수 있습니다. 이 말인 즉, 필요한 라이브러니나 기술들을 미리 모듈화 해 개발에만 집중할 수 있게끔 만들어진 하나의 뼈대라고 할 수 있습니다.
정의로는 이해가 되었습니다. 하지만, 어떻게 집중할 수 있는데 ? 라는 질문을 던지게 됩니다. 여기서 우리는 IoC 의 개념이 등장하게 됩니다. IoC 는 제어의 역전의 줄임말 입니다. 즉 개발자가 개발한 애플리케이션이 프레임워크에 의해 제어되어 실행 된다고 말할 수 있습니다.
IoC 의 개념을 사용하기 위해 Bean 을 생성해 스프링 컨텍스트에서 객체를 관리하게 됩니다. 개발자가 필요로 하는 기능이나 어떤 로직들을 Bean 으로 생성해 스프링 컨텍스트에 등록해준다고 할 수 있습니다. 이후 스프링 컨텍스트가 필요한 매개변수를 확인하고 연결시키게 되고, 이 과정에서 IoC 개념을 기반으로 한 DI 가 등장하게 됩니다. DI (이 과정에서 주입할 수 있는 방법은 3가지가 있습니다. 이 부분에 대해서는 찾아보고 공부하는 것을 추천드립니다. )는 스프링 컨텍스트에서 필요한 인스턴스를 매개변수 주입해주는 것입니다. 이 과정에서 불필요한 인스턴스의 생성을 방지할 수 있습니다.
정리해보자면, 스프링 컨텍스트는 Bean 으로 등록된 인스턴스를 관리하고, 필요 시 DI 해주는 것입니다. 우리는 이를 IoC 라고 말할 수 있습니다.
Bean을 등록하는 방법
Bean 을 등록하는 방법에는 직접 Bean 으로 애너테이션으로 등록하는 방법이 있고 스트레오타입의 애너테이션을 사용해 등록하는 방법이 있습니다.
@Bean | 스트레오타입의 애너테이션(@Component, @Service 등) |
1. 스프링 컨텍스트에 추가할 인스턴스를 완전히 제어할 수 있다. 2. 동일한 타입의 인스턴스를 컨텍스트에 더 추가할 수 있다. 3.생성하는 각 빈에 대해 별도의 메서드를 작성해야 하므로 앱에 상용구 코드가 추가된다. |
1. 프레임워크가 인스턴스를 생성한 후에만 제어할 수 있다. 2. 스테레오타입 애너테이션은 애플리케이션이 소유한 클래스의 빈을 생성하는 데만 사용할 수 있다.3. 스테레오타입 애너테이션을 사용하여 스프링 컨텍스트에 빈을 추가해도 앱에 상용구 코드가 추가되지 않는다. |
대부분의 앱에서는 스트레오타입의 애너테이션을 많이 사용합니다.
빈 스코프와 생명 주기
빈 스코프란 스프링 컨테스트에서 빈을 생성하는 관리하는 방식입니다.
스프링 컨텍스트의 대표적인 스코프인 싱글톤과 프로토 타입에 대해서 설명하도록 하겠습니다.
우선 시작하기에 앞서 싱글톤 패턴을 생각한다면 보통 단일 타입이 하나의 인스턴스를 가지고 인스턴스를 공유해 쓰는 패턴을 생각합니다. 하지만 스프링 컨텍스트에서의 싱글톤 패턴은 하나의 타입에 여러개의 인스턴스를 가질 수 있습니다. 이 부분은 유의하고 넘어가면 좋겠습니다.
싱글톤 스코프
싱글톤 스코프의 경우에는 하나의 인스턴스를 공유하는 것을 알 수 있습니다. 동일한 빈을 여러 곳에서 주입받더라도 하나의 인스턴스를 공유합니다. 그러므로 메모리의 효율이 좋은 것을 알 수 있습니다. 하지만 경쟁 상태( 작업의 실행 순서나 타이밍에 따라 의도치 않은 결과가 발생하는 상황 )에 빠질 수 있기 때문에 무상태성을 유지해야 합니다.
프로토타입 스코프
프로토타입 스코프의 경우에는 빈을 등록하고 등록된 빈의 인스턴스를 매번 생성하는 스코프 입니다. 그러므로 메모리의 문제가 발생할 수 도 있습니다. 또한 빈에서 생성은 해주지만 삭제는 개발자가 직접해야 하는 문제점이 있습니다.
추상화
스프링 컨텍스트는 추상화를 통해 객체들 간의 결합도를 낮추고 유연성을 제공합니다.
추상화는 무엇이 발생해야 하는지(What) 를 정의하고, 구현체는 그것이 어떻게 발생하는지(How) 를 설명합니다.
이는 인터페이스와 구현체를 분리함으로써, 코드의 유지보수성과 확장성을 크게 향상시킵니다.
간단한 예시 코드를 보겠습니다.
//추상화
public interface PaymentService {
void processPayment(double amount);
}
// 구현체
@Service
public class PaypalPaymentService implements PaymentService {
@Override
public void processPayment(double amount) {
System.out.println("Processing PayPal payment of $" + amount);
}
}
@Service
public class CreditCardPaymentService implements PaymentService {
@Override
public void processPayment(double amount) {
System.out.println("Processing credit card payment of $" + amount);
}
}
// 의존성 주입
@Service
public class OrderService {
private final PaymentService paymentService;
// 알맞은 인스턴스를 주입 받습니다.
@Autowired
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
public void placeOrder(double amount) {
paymentService.processPayment(amount);
}
}
// 간단하게 실행 시켜봅니다.
@RestController
public class PaymentController {
private final OrderService orderService;
@Autowired
public PaymentController(OrderService orderService) {
this.orderService = orderService;
}
@GetMapping("/pay")
public String pay(@RequestParam double amount) {
orderService.placeOrder(amount);
return "Payment processed!";
}
}
스프링 컨텍스트의 추상화는 객체 간의 결합도를 낮추고, 구현체의 교체 및 확장을 쉽게 만들어줍니다. 이를 통해 변화에 유연하고 유지보수하기 쉬운 애플리케이션을 설계할 수 있습니다. 추상화는 스프링의 강력한 기능 중 하나이며, 코드의 품질과 테스트 용이성을 높이는 핵심적인 설계 원칙입니다.
다음 포스팅에서는 AOP 에 관하여 포스팅 하도록 하겠습니다.