본문 바로가기
JAVA

Spring Boot 핵심 개념 정리: DI, IoC, AOP를 쉽게 이해해보자

by 요료료룡 2026. 4. 7.
들어가며

Spring Boot를 처음 배울 때 가장 헷갈리는 게 DI, IoC, AOP 같은 개념들이에요. "용어는 알겠는데 왜 쓰는지 모르겠다"는 분들이 많아요. 이번 포스팅에서는 이 개념들을 아주 쉬운 예시로 풀어서 설명하고, 실제 Spring Boot 코드에서 어떻게 등장하는지 보여드릴게요.

IoC: 제어의 역전이 뭔가요?

IoC(Inversion of Control, 제어의 역전)는 쉽게 말해 "객체 생성과 관리를 개발자가 아닌 프레임워크가 한다"는 개념이에요.

일반적으로는 개발자가 new UserService()처럼 직접 객체를 만들어요. IoC에서는 Spring이 알아서 객체를 만들고 관리해 줘요. 이렇게 Spring이 관리하는 객체를 Bean이라고 불러요.

// ❌ IoC 없이 직접 생성
public class OrderController {
    private OrderService orderService = new OrderService();  // 직접 생성
    private UserRepository userRepo = new UserRepository();  // 직접 생성
}

// ✅ Spring IoC 활용
@RestController
public class OrderController {
    // Spring이 알아서 주입해줌 (직접 new 안 해도 됨)
    @Autowired
    private OrderService orderService;
}
DI: 의존성 주입, 왜 좋은가요?

DI(Dependency Injection, 의존성 주입)는 IoC를 구현하는 방법 중 하나예요. 객체가 필요한 의존성을 직접 만들지 않고 외부(Spring)에서 받아오는 방식이에요.

현재는 생성자 주입(Constructor Injection)을 가장 권장해요. 테스트하기 쉽고, 의존성이 명확하게 보이고, final 선언이 가능해서 불변성을 보장할 수 있거든요.

// 방법 1: 필드 주입 (비권장)
@Service
public class OrderService {
    @Autowired
    private OrderRepository orderRepository; // 테스트 어려움
}

// 방법 2: 생성자 주입 (권장 ✅)
@Service
public class OrderService {
    private final OrderRepository orderRepository;

    // @Autowired 생략 가능 (생성자 1개일 때)
    public OrderService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }
}

// Lombok @RequiredArgsConstructor 사용 시 더 간결하게
@Service
@RequiredArgsConstructor
public class OrderService {
    private final OrderRepository orderRepository; // 생성자 자동 생성
}
AOP: 공통 관심사를 분리하는 마법

AOP(Aspect-Oriented Programming, 관점 지향 프로그래밍)는 여러 곳에서 반복되는 코드(로깅, 트랜잭션, 권한 체크 등)를 한 곳에 모아 관리하는 방법이에요.

예를 들어 모든 서비스 메서드에 실행 시간을 로깅하고 싶다면, 메서드마다 로깅 코드를 넣는 게 아니라 AOP Aspect 하나로 전체 처리가 가능해요.

@Aspect
@Component
public class LoggingAspect {

    // com.example.service 패키지의 모든 메서드 실행 시 적용
    @Around("execution(* com.example.service.*.*(..))")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();

        Object result = joinPoint.proceed(); // 실제 메서드 실행

        long time = System.currentTimeMillis() - start;
        System.out.println(joinPoint.getSignature() + " 실행 시간: " + time + "ms");

        return result;
    }
}
@Transactional이 AOP 덕분에 동작해요

Spring에서 가장 많이 쓰는 AOP 활용 사례가 바로 @Transactional이에요. 이 어노테이션 하나로 DB 트랜잭션 시작/커밋/롤백을 자동으로 처리해줘요.

@Service
@RequiredArgsConstructor
public class PaymentService {
    private final OrderRepository orderRepository;
    private final PaymentRepository paymentRepository;

    @Transactional  // 이 메서드 전체가 하나의 트랜잭션으로 묶임
    public void processPayment(Long orderId, int amount) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new RuntimeException("주문 없음"));

        order.setStatus("PAID");
        orderRepository.save(order);

        Payment payment = new Payment(orderId, amount);
        paymentRepository.save(payment);

        // 예외 발생 시 위의 order 저장도 자동으로 롤백됨!
        if (amount <= 0) throw new IllegalArgumentException("금액 오류");
    }
}
Tip💡 @Transactional은 기본적으로 RuntimeException 발생 시에만 롤백해요. Checked Exception(IOException 등)에도 롤백하려면 @Transactional(rollbackFor = Exception.class)로 설정해야 해요.

 

'JAVA' 카테고리의 다른 글

Java 멀티스레딩 입문: Thread, ExecutorService, CompletableFuture  (0) 2026.04.08
JAVA 17 vs 21 vs 25  (0) 2026.03.04
[JAVA] Recode  (0) 2024.04.18
JAVA Collection  (0) 2024.04.18
[SPRING] @Transactional  (0) 2024.04.16