Loading...
태그
    construction-injectiondependency-injectionjavalombokMVCspring-framework

순수 자바 기반 방식과 Spring 생성자 주입 방식의 MVC 구조 비교

최종수정일
2026년 04월 02일
4분걸림
작성자: Techy J
Table of Content (목차)

순수 자바 기반 방식과 Spring 생성자 주입 방식의 MVC 모델

MVC 모델에서는 Controller 객체가 Service 객체를 필드로 가지고, Service 객체는 Repository 객체를 필드로 가진다.
웹 요청이 들어오면 해당 요청을 처리하는 Controller가 실행되고, Controller는 Service를 호출하며 Service는 Repository를 통해 데이터에 접근하고 처리한다.

이 과정에서 객체 간 의존성이 자연스럽게 발생한다. 순수 자바에서도 이런 의존성을 분리하는 코드를 만들 수 있지만, Spring을 사용하면 의존성 주입을 프레임워크가 처리해 주기 때문에 개발이 훨씬 편해진다.

여기서는 순수 자바 기반 방식과 Spring의 생성자 주입 방식에서 MVC 구조의 코드 의존성을 비교해 본다.

  • Repository: 데이터베이스와의 직접적인 통신을 담당한다.

  • Service: 비즈니스 로직을 처리한다.

  • Controller: 사용자 요청을 받아 Service를 호출한다.

순수 자바와 Spring 같 MVC 구조 비교

스프링 없이 구현할 때 (순수 자바 기반)

순수 자바에서도 인터페이스를 사용해 의존성을 분리할 수 있다. 하지만 객체 생성과 의존성 연결은 여전히 개발자가 직접 new 키워드로 관리해야 한다. 이 구조에서는 다음과 같은 특징이 있다.

  • 객체 생성 책임이 클래스 내부에 존재한다.
  • 상위 계층이 하위 계층 구현에 직접 의존한다.
  • 구현체 교체와 테스트가 어렵다.
// 인터페이스 정의  
interface MemberRepository {  
void save(String name);  
}  
  
interface MemberService {  
void join(String name);  
}
 
// Repository: 데이터 저장소
class MemoryMemberRepository implements MemberRepository {
    public void save(String name) { System.out.println(name + " 저장"); }
}
 
// Service: 비즈니스 로직 (Repository를 직접 생성)
class MemberServiceImpl implements MemberService {
    private MemberRepository repository = new MemberRepository(); // 직접 생성
 
    public void join(String name) { repository.save(name); }
}
 
// Controller: 요청 처리 (Service를 직접 생성)
class MemberController {
    private MemberService service = new MemberService(); // 직접 생성
 
    public void create() { service.join("홍길동"); }
}

스프링에서 생성자 주입(DI)을 사용할 때

Spring을 사용하면 클래스에 Annotation을 붙여 Spring Bean으로 등록한다. 객체 생성과 의존성 주입은 Spring IoC Container가 담당한다. 또한 인터페이스를 사용하면 실행 시점에 구현체를 교체할 수 있어 구조가 더 유연해진다. 주요 Annotation의 역할은 다음과 같다.

  • @Repository: 데이터 접근 계층임을 나타내며 데이터 접근 예외를 Spring의 예외로 변환한다.
  • @Service: 비즈니스 로직 계층을 나타낸다.
  • @Controller: 웹 요청을 처리하는 컨트롤러를 등록한다.
  • @Autowired: Spring이 관리하는 Bean을 자동으로 연결한다.

이 구조에서는 객체 생성과 의존성 연결을 Spring이 담당한다. 개발자는 객체 생성 대신 비즈니스 로직 구현에 집중할 수 있다.

// --- 인터페이스 정의 (결합도 낮춤) ---
interface MemberService { void join(String name); }
interface MemberRepository { void save(String name); }
 
// --- 구현체 및 주입 ---
 
@Repository
public class MemoryMemberRepository implements MemberRepository {
    public void save(String name) { System.out.println(name + " 저장"); }
}
 
@Service
public class MemberServiceImpl implements MemberService {
    private final MemberRepository repository;
 
    // @Autowired // 생성자가 하나면 스프링이 자동으로 붙여줌 (생략 가능)
    public MemberServiceImpl(MemberRepository repository) {
        this.repository = repository;
    }
 
    public void join(String name) { repository.save(name); }
}
 
@Controller
public class MemberController {
    private final MemberService memberService;
 
    // @Autowired // 이 어노테이션이 있으면 스프링이 컨테이너에서 빈을 찾아 주입함
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }
 
    public void create() { memberService.join("홍길동"); }
}
 

요약 비교

  • 스프링 미사용
    인터페이스를 사용해 구조를 분리할 수 있지만 객체 생성과 연결을 new로 직접 관리해야 한다.

  • 스프링 생성자 주입
    객체 생성과 의존성 연결을 Spring이 담당한다. 이를 통해 제어의 역전(IoC)이 발생하며 구조가 유연해진다. 또한 필드를 final로 선언할 수 있어 객체 불변성을 유지할 수 있고 테스트 코드 작성도 쉬워진다.

Lombok(@RequiredArgsConstructor)을 적용한 Spring 코드

Lombok의 @RequiredArgsConstructor를 사용하면 final이 붙은 필드를 기반으로 생성자를 자동 생성한다. 따라서 개발자가 직접 생성자를 작성할 필요가 없어 코드가 간결해진다.

 
interface MemberRepository { void save(String name); }
interface MemberService { void join(String name); }
 
// 1. Repository (Lombok 필요 없음)
@Repository
public class MemoryMemberRepository implements MemberRepository {
    public void save(String name) { System.out.println(name + " 저장"); }
}
 
// 2. Service (Lombok 적용)
@Service
@RequiredArgsConstructor // final 필드에 대한 생성자를 자동으로 생성
public class MemberServiceImpl implements MemberService {
    private final MemberRepository repository;  // 생성자 주입
 
    // @RequiredArgsConstructor가 아래 생성자를 대신 만들어줌:
    // public MemberService(MemberRepository repository) { this.repository = repository; }
 
    public void join(String name) { repository.save(name); }
}
 
// 3. Controller (Lombok 적용)
@Controller
@RequiredArgsConstructor // final 필드인 service를 주입하는 생성자 자동 생성
public class MemberController {
    private final MemberService service; // 생성자 주입
 
    public void create() { service.join("홍길동"); }
}

Lombok을 사용하면 다음과 같은 장점이 있다.

  • 코드 간결화
    필드가 늘어나도 생성자를 직접 수정할 필요가 없다.

  • 가독성 향상
    반복적인 생성자 코드가 사라져 클래스 구조가 더 명확하게 보인다.

  • 생성자 주입 유지
    코드가 짧아지더라도 내부적으로는 동일한 생성자 주입 구조이므로 객체 불변성과 테스트 용이성을 유지할 수 있다.

  • 컴파일 타임 체크
    final 필드에 값이 전달되지 않으면 컴파일 에러가 발생한다.

  • 수정 용이성
    새로운 의존성이 추가될 때 필드에 final만 추가하면 된다.

  • 의존성 가시성
    클래스 상단의 final 필드를 통해 필요한 의존성을 한눈에 확인할 수 있다.

  • 테스트 용이성
    Spring 없이도 new OrderController(new MockService()) 형태로 테스트할 수 있다.

(끝)

이 글은 ' 출처: 변호사 전정숙 '과 ' 원본링크: https://www.korean-lawer.com/articles/spring-framework/spring-mvc-constructor-injection-vs-pure-java'를 명시하는 조건으로 인용가능 합니다.
무단 복제, AI 학습 목적으로의 사용과 Google, Naver의 Indexing 외 크롤링 금지합니다
About
전정숙 변호사
법무법인 정맥 파트너 변호사부산파산법원 파산관재인전) 부산변호사회 부회장전) 전국여성변호사회 부회장전) 부산 가정법원 조정위원
Contact

(82) 051-916-8582 , 051-916-8583

부산광역시 연제구 법원로 12 (거제동)

로윈타워빌딩 2층 법무법인정맥

변호사 전정숙

© 2005-2026 전정숙 변호사.

All Rights Reserved.