[SpringBoot] IoC, DI, AOP, PSA 에 대해

도톨이·2025년 8월 26일

SpringBoot

목록 보기
2/4

지난 번에 스프링의 핵심 콘셉트인 IoC, DI, AOP, PSA에 대해 간단히 언급했었다.
이번에는 이 네 가지를 더 자세히 정리해보려고 한다.

1. IoC (Inversion of Control)

일반적으로 자바에서 객체를 만들 때는 new 키워드를 사용한다.
예를 들어 OrderServiceOrderRepository에 의존한다면 보통은 이렇게 작성한다.

public class OrderService {
	private OrderRepository orderRepository;
    
    public OrderService() {
    	this.orderRepository = new OrderRepository(); // 여기서 직접 객체 생성
        }
}

-> 여기서 OrderServiceOrderRepository의 생성 시점과 제어권을 모두 갖게 된다.

하지만 스프링에서는 이 제어권(객체 생성과 주입)을 개발자가 아니라 스프링 컨테이너가 담당하게 된다. 즉, 개발자가 "이 클래스가 필요해요"라고만 선언하고, 생성과 연결은 컨테이너가 해준다. 이것을 제어의 역전(IoC)라고 한다.

2. DI

위에서 나온 IoC의 구현 방식 중 하나가 DI이다.
DI는 객체 간의 의존 관계를 외부에서 주입(Injection)해주는 것이다.

스프링에서는 대표적으로 @Autowired 라는 애너테이션 또한 생성자 주입을 통해 DI를 구현한다.

아래 코드를 보면, @Autowired 라는 애너테이션이 스프링 컨테이너에 있는 이라는 것을 주입하는 역할을 한다. 빈은 스프링 컨테이너에서 관리하는 객체이다.
코드 예시에서 살펴보면 OrderRepository스프링 컨테이너에서 관리되는 빈(Bean) 이다.

즉, 스프링이 실행될 때 @Component 붙은 클래스를 스캔해서 객체 만들고 컨테이너에 등록함 -> OrderService가 필요로 할 때 컨테이너가 알아서 OrderRepository 객체를 넣어주는 것
(*스프링 컨테이너 : 스프링의 핵심 엔진으로, 애플리케이션 실행 시 객체(빈)을 생성하고 관리하는 녀석. 개발자가 직접 new로 객체를 만들고 의존성을 연결하지 않아도, 컨테이너가 필요한 객체를 찾아서 넣어준다)

따라서 직접 new OrderRepository()를 호출하지 않아도 된다.

@Component
public class OrderRepository {
    // DB 접근 코드
}

@Service
public class OrderService {

    private final OrderRepository orderRepository;

    @Autowired
    public OrderService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    public void createOrder() {
        // OrderRepository 사용
    }
}

3. AOP (Aspect Oriented Programming, 관점 지향 프로그래밍)

AOP는 프로그래밍에 대한 관심을 핵심 관점, 부가 관점으로 나누어서 관심 기준으로 모듈화하는 것을 의미한다.
우리 프로젝트에는 비즈니스 로직(핵심 관심사) 외에도, 서비스 전반에 공통으로 필요한 기능들이 있다. 예를 들어 로그 기록이나 보안 검증이나 트랜잭션 처리 등이 있다.

이러한 공통 기능을 모든 서비스 클래스에 일일이 작성하려면 코드가 지저분해지고 유지보수가 어려워진다.
따라서, 이런 부가 관점(횡단 관심사)에 해당하는 코드를 핵심 관점에서 분리하여, 프로그래머는 핵심 관점 코드에만 집중하게 해준다.

코드로 살펴보면 아래의 코드에서 @Transactional이라는 애너테이션 하나만 붙이면 스프링이 알아서 트랜잭션을 시작하고, 예외 발생 시 롤백하며, 정상 처리 시 커밋까지 해준다.

@Service
public class PaymentService {

    @Transactional  // AOP가 알아서 트랜잭션 관리
    public void processPayment() {
        // 결제 처리 로직
    }
}

4. PSA (Portable Service Abstraction, 일관된 추상화)

다음은 이식 가능한 서비스 추상화(PSA)이다. 스프링은 여러 기술(JDBC, JPA, MyBatis, 다양한 HTTP 클라이언트 등)을 일관된 추상화 계층으로 감싸서, 개발자가 복잡한 구현체 차이를 신경 쓰지 않고도 동일한 방식으로 사용할 수 있게 한다.

즉, 구현체가 달라도 개발자는 같은 코드 스타일로 쓴다는 것이 PSA의 핵심이다.

예시 1: 데이터 접근 (DB Layer)
스프링에서는 DB 접근 기술이 달라도 Repository 인터페이스를 기준으로 동일하게 동작한다.
아래 예시는 JPA 기반의 Repository인데, 개발자가 직접 SQL을 작성하지 않아도 기본적인 CRUD 기능이 자동으로 제공된다.

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
    // findAll(), save(), deleteById() 등을 바로 사용 가능
}

→ 여기서 JpaRepository 대신 MyBatis를 붙이거나, JDBC Template를 쓰더라도, Controller/Service 계층의 코드 구조는 동일하다.
즉, PSA 덕분에 특정 기술에 종속되지 않고 교체가 용이하다.

예시 2: 웹 요청 처리 (Controller)
스프링 MVC의 @RequestMapping, @GetMapping, @PostMapping 같은 애너테이션도 PSA의 대표적인 예시다.

@RestController
@RequestMapping("/users")
public class UserController {

    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        // 어떤 HTTP 라이브러리를 쓰든 일관된 방식으로 매핑됨
        return new User(id, "길동");
    }
}

내부적으로는 서블릿 API, 톰캣, Jetty, Netty 등 다양한 서버 기술이 쓰이지만
개발자는 오직 @GetMapping 같은 일관된 추상화만 알면 된다.

profile
Kotlin, Flutter, AI | Computer Science

0개의 댓글