이전 글에서 살펴본 것처럼 서블릿은 클라이언트의 요청을 처리하고 동적인 응답을 생성할 수 있는 자바 기반의 핵심 기술입니다. 이를 통해 웹 애플리케이션을 직접 구현하는 것도 충분히 가능합니다.
하지만 실제 개발 환경에서 서블릿을 기반으로 기능을 하나씩 구현해보면 점점 한계를 느끼게 됩니다.
요청을 처리하기 위해 매번 HttpServlet을 상속받고, doGet(), doPost() 메서드를 구분하여 작성해야 하며, 요청과 응답 객체를 직접 다루면서 HTTP에 대한 세부적인 처리까지 신경 써야 합니다.
기능이 늘어날수록 비슷한 코드가 반복되고, 결국 코드의 가독성은 떨어지고, 유지보수는 점점 어려워지게 됩니다.
서블릿으로 웹 개발을 진행하게 된다면 규모가 커질수록 아래와 같은 서블릿 코드가 반복되며 개발자의 부담은 증가할 것입니다.
@WebServlet("/idCheck.me")
public class IdCheckController extends HttpServlet {
private static final long serialVersionUID = 1L;
public IdCheckController() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String userId = request.getParameter("userId");
Member m = new Member();
m.setMemId(userId);
Member mem = new MemberServiceImpl().idCheck(m);
response.setContentType("text/html; charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head><title>아이디 중복 확인</title></head>");
out.println("<body>");
if(mem != null) {
out.println("<h3 style='color:red;'>이미 사용 중인 아이디입니다.</h3>");
} else {
out.println("<h3 style='color:green;'>사용 가능한 아이디입니다.</h3>");
}
out.println("</body>");
out.println("</html>");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
이러한 서블릿의 반복으로
- 비즈니스 로직 + HTTP 처리 코드 섞임
- 중복 코드 증가
- 유지보수 어려움
- 테스트 어려움
등과 같은 문제가 발생할 것입니다.
MVC(Model-View-Controller) design pattern
하나의 서블릿에서 요청 처리, 비즈니스 로직, 화면 생성까지 모두 담당하다 보니 코드가 복잡해졌고, 이를 해결하기 위해 각 책임을 분리하기 위해 등장한 개념이 바로 MVC 패턴입니다.

요청을 받아 흐름을 제어하는 Controller, 데이터를 처리하는 Model, 그리고 화면을 담당하는 View로 역할을 나누어 구조를 정리하기 시작했습니다.
이렇게 역할을 분리하며 코드의 가독성이나 유지보수성이 확실히 개선되었습니다.
하지만 이 패턴 또한 각 요소를 연결하고 객체를 생성하는 등 개발자가 직접 처리해야한다는 단점이 있었습니다.
Spring Framework
서블릿과 MVC 패턴만으로는 여전히 객체 생성, 의존성 관리, 트랜잭션 처리와 같은 부분을 개발자가 직접 처리해야 했습니다.
이러한 문제를 해결하기 위해 등장한 것이 Spring Framework 입니다.
Spring은 단순한 기능 추가가 아니라, 개발자가 반복적으로 작성해야 했던 많은 작업들을 자동화하여 자바 엔터프라이즈 애플리케이션 개발을 편하게 해주는 프레임워크입니다.
이를 위해 Spring은 아래와 같은 프로그래밍 모델을 제공합니다.
- 의존성 주입(Dependency Injection)
- 제어의 역전(Inversion of Control)
- 관점 지향 프로그래밍(Aspect Oriented Programming)
이 세 가지를 기반으로 반복 작업을 줄이고, 구조를 강제하며 개발자가 비즈니스 로직에만 집중할 수 있도록 만들어줍니다.
1. 객체 생성 관리 (DI)
객체를 사용할 때마다 new 키워드를 통해 직접 생성해야 했고, 클래스 간의 의존 관계 역시 개발자가 일일이 연결해주어야 했습니다.
MemberService memberService = new MemberServiceImpl();
이러한 구조에서는 객체 간 결합도가 높아지기 때문에, 하나의 변경이 여러 부분에 영향을 미치기 쉽습니다.
이는 객체지향 설계 원칙을 지키기 어려운 구조로 이어지게 됩니다.
@Autowired
MemberService memberService;
2. 역할 분리 강제 (Spring MVC)
Spring은 앞에서 다룬 MVC 패턴을 프레임워크 차원에서 강제하여 설계 고민을 줄이고 정해진 구조 안에서 개발하도록 도와줍니다.
서블릿을 사용할 때는 URL을 직접 매핑하고,
해당 요청이 들어왔을 때 어떤 로직을 실행할지 개발자가 처음부터 끝까지 모두 작성해야 했습니다.
하나의 서블릿 안에서 요청 처리, 비즈니스 로직 호출, 응답 생성까지 모두 담당하게 되면서 자연스럽게 코드가 섞이고 구조가 무너지게 됩니다.
하지만 Spring은 다음과 같이 작성합니다.
@Controller
public class MemberController {
@Autowired
MemberService memberService;
@GetMapping("/idCheck")
public String idCheck(@RequestParam String userId, Model model) {
Member mem = memberService.idCheck(userId);
model.addAttribute("mem", mem);
return "result";
}
}
- 요청 파라미터 추출 → Spring이 자동 처리
- 객체 생성 → Spring이 대신 관리 (DI)
- 요청 흐름 제어 → 내부에서 자동 처리
- View 연결 → 문자열 반환으로 해결
Spring에서는 Controller는 요청 처리만, Service는 비즈니스 로직만, View는 화면만 다루면서 각 역할이 자연스럽게 분리됩니다.
Spring의 모듈인 Spring MVC가 이를 관리해주는데, Spring MVC에선 모든 요청이 하나의 중앙 컨트롤러를 거치게 됩니다.
이 역할을 하는 것이 바로 DispatcherServlet입니다.
3. 트랜잭션 자동 처리
conn.setAutoCommit(false);
try {
// DB 작업
conn.commit();
} catch(Exception e) {
conn.rollback();
}
이처럼 commit, rollback을 직접 처리해야 했기 때문에 코드가 복잡했습니다.
하지만 Spring에서는
@Transactional
public void insertMember() {
// 비즈니스 로직
}
이처럼 트랜잭션을 선언만 하면, 내부에서 자동으로 처리됩니다.
개발자는 데이터 흐름 제어보다 비즈니스 로직에 집중할 수 있게 됩니다.
4. 반복 코드 제거(AOP)
로그, 보안, 트랜잭션 같은 코드는 여러 곳에서 계속 반복됩니다.
하지만 이러한 공통 기능을 각 코드마다 작성하게 되면 중복이 증가하고 유지보수가 어려워집니다.
Spring은 이를 공통 로직을 한 곳에 모아두고 필요한 곳에 자동으로 적용하여 관점 지향 프로그래밍(AOP)으로 해결합니다.
5. 모듈화된 구조
Spring은 하나의 거대한 기술이 아닌, 각 모듈에서 제공하는 기능을 선택해서 쓰는 구조입니다.
- Spring MVC → 웹
- Spring Data → DB
- Spring Security → 인증/인가
- Spring Batch → 대용량 처리
이처럼 필요한 기능을 골라서 사용할 수 있습니다.

스프링은 단순히 기능 몇 개를 제공하는 라이브러리가 아니라, 애플리케이션 전반에 걸쳐 일관된 방식으로 개발할 수 있도록 돕는 프레임워크입니다.
웹 요청 처리, 비즈니스 로직, 데이터 접근과 같은 각 계층은 서로 역할이 다르지만, 스프링은 이러한 모든 영역에서 동일한 방식으로 객체를 관리하고, 의존성을 연결하며, 공통 기능을 적용할 수 있도록 합니다.
예를 들어 객체 생성은 모두 스프링 컨테이너가 담당하고, 클래스 간의 의존 관계는 설정이나 어노테이션을 통해 일관되게 관리됩니다. 또한 트랜잭션 처리나 공통 로직 역시 특정 기술에 종속되지 않고 동일한 방식으로 적용할 수 있습니다.
이처럼 스프링은 특정 기능을 제공하는 데 그치지 않고, 애플리케이션의 모든 계층에서 동일한 개발 방식(프로그래밍 모델)을 유지할 수 있도록 만들어 줍니다.
결국 개발자는 각 기술을 따로 배우고 연결하는 데 시간을 쓰기보다, 정해진 방식 안에서 비즈니스 로직에 집중할 수 있게 됩니다.
학습용으로 작성한 포스팅이므로 오류가 있을 수 있습니다.
잘못된 내용이나 보완이 필요한 부분이 있다면 댓글로 알려주시면 감사하겠습니다.
궁금한 점도 편하게 남겨주세요.
출처
- Introduction to Spring Framework
https://www.geeksforgeeks.org/advance-java/introduction-to-spring-framework/
- MVC Design Pattern
https://www.geeksforgeeks.org/system-design/mvc-design-pattern/