HttpServlet을 상속받아 doGet(), doPost()를 오버라이딩하여 요청 처리
개발자가 요청 URL 매핑, 파라미터 추출, 비즈니스 로직 호출, 뷰 렌더링(HTML/ JSP forward)까지 직접 처리해야 함
코드 예시:
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String name = req.getParameter("name");
resp.setContentType("text/html");
resp.getWriter().write("<h1>Hello, " + name + "</h1>");
}
}
➡ 단점: URL 매핑, 파라미터 처리, 뷰 연결 등 반복적인 코드가 많아 유지보수가 어려움
DispatcherServlet(Front Controller 패턴)이 모든 요청을 받아 처리
개발자는 비즈니스 로직과 요청 처리에만 집중 가능
애노테이션 기반 선언:
@Controller
public class HelloController {
@GetMapping("/hello")
public String hello(@RequestParam String name, Model model) {
model.addAttribute("name", name);
return "hello"; // 뷰 이름 (hello.jsp)
}
}
@Controller: 요청을 처리하는 클래스
@RequestMapping (또는 @GetMapping, @PostMapping): URL 매핑
DispatcherServlet: 요청을 받아 적절한 컨트롤러로 위임
➡ 장점: URL 매핑, 파라미터 바인딩, 뷰 연결, 예외 처리, 공통 기능 등을 프레임워크가 제공 → 개발자는 로직만 집중
Front Controller 패턴 적용: 모든 요청을 DispatcherServlet이 받아 일관된 처리
Annotation 기반 매핑: URL → 메서드 매핑 자동화
파라미터 자동 바인딩: @RequestParam, @ModelAttribute, DTO 변환 지원
뷰 처리 자동화: ViewResolver를 통해 JSP/Thymeleaf 템플릿 연결
AOP 기반 공통 처리 지원: Interceptor, ExceptionHandler, Filter 등 확장성
DI & IoC: Controller, Service, Repository 간 의존성 관리 용이
Spring MVC는 Front Controller(DispatcherServlet)을 중심으로 동작한다.
클라이언트 요청
사용자가 /hello 요청 → DispatcherServlet이 수신
HandlerMapping 조회
요청 URL과 매핑된 컨트롤러(@Controller 메서드) 검색
HandlerAdapter 실행
찾은 핸들러(메서드)를 실행할 수 있도록 어댑터 선택
(ex: RequestMappingHandlerAdapter)
Controller 호출
실제 비즈니스 로직 실행 후 ModelAndView 반환
(데이터 + 뷰 이름 포함)
ViewResolver 호출
뷰 이름(hello) → 물리적 뷰 경로(/WEB-INF/views/hello.jsp)로 변환
View 렌더링
JSP/Thymeleaf 엔진이 실행되어 HTML 응답 생성
클라이언트 응답 반환
최종 HTML이 사용자에게 전달
[Client Request]
|
v
[DispatcherServlet] <-- Front Controller
|
v
[HandlerMapping] --- 요청 URL → 컨트롤러 매핑
|
v
[HandlerAdapter] --- 컨트롤러 실행 준비
|
v
[Controller] --- 비즈니스 로직 수행, ModelAndView 반환
|
v
[ViewResolver] --- 뷰 이름 → JSP/Thymeleaf 등 변환
|
v
[View(Render)] --- 최종 HTML 생성
|
v
[Client Response]
DispatcherServlet과 Controller 사이에서 공통 처리 담당
실행 단계:
preHandle() → Controller 실행 전 로직 (로그인 체크, 권한 확인)
postHandle() → Controller 실행 후, 뷰 렌더링 전 로직 (모델 데이터 가공)
afterCompletion() → 뷰 렌더링 후 로직 (리소스 해제, 로깅)
AOP(Aspect-Oriented Programming, 관점 지향 프로그래밍)은 핵심 로직(Core Concern)과 공통 관심사(Cross-cutting Concern)를 분리하기 위한 프로그래밍 기법이다.
공통 관심사란 여러 모듈에서 반복되는 기능(ex: 로깅, 트랜잭션, 보안, 성능 모니터링 등)을 의미한다.
OOP만으로는 비즈니스 로직과 공통 기능이 얽히면서 코드 중복과 유지보수 어려움이 생김 → AOP로 분리해 해결
구분 | OOP (객체 지향 프로그래밍) | AOP (관점 지향 프로그래밍) |
---|---|---|
초점 | 클래스 단위로 기능 모듈화 | 횡단 관심사를 모듈화 |
중복 문제 | 로깅, 보안, 트랜잭션 같은 공통 기능이 각 클래스에 중복 작성됨 | 공통 기능을 Aspect로 분리, 여러 클래스에 일괄 적용 |
결과 | 코드 중복 증가, 유지보수 어려움 | 핵심 로직과 보조 기능이 분리 → 관심사 분리 (SoC) |
JoinPoint
AOP 적용 가능한 지점 (ex: 메서드 실행 시점, 생성자 호출, 예외 발생)
Spring AOP에서는 메서드 실행 시점만 JoinPoint로 지원
Pointcut
어떤 JoinPoint에 Advice를 적용할지 정의한 표현식
예: execution( com.example.service..*(..))
Advice
실제 수행되는 공통 기능(횡단 관심사) 코드
종류:
@Before: 메서드 실행 전
@After: 메서드 실행 후
@AfterReturning: 정상 반환 후
@AfterThrowing: 예외 발생 후
@Around: 실행 전/후 모두 제어
Aspect
Pointcut + Advice의 결합체
공통 관심사를 모듈화한 클래스 (@Aspect로 선언)
Weaving
핵심 코드 + 횡단 관심사(Advice)를 결합하는 과정
시점에 따라 구분:
컴파일 타임 위빙: 컴파일 시점에 코드 삽입 (AspectJ)
로드 타임 위빙: 클래스 로더가 바이트코드 읽을 때 삽입
런타임 위빙: 프록시 객체를 생성해 실행 시점에 결합 (Spring AOP)
구분 | 런타임 위빙 (Spring AOP) | 컴파일 타임 위빙 (AspectJ) |
---|---|---|
적용 시점 | 실행 시점 | 컴파일 시점 |
방식 | 프록시 객체를 동적으로 생성하여 Advice 적용 | 바이트코드 자체를 수정하여 Advice 삽입 |
특징 | Spring Bean에만 적용 가능 (메서드 실행 시점) | 더 강력, 필드 접근/생성자 호출 등 다양한 JoinPoint 지원 |
장점 | 유연함, Spring IoC와 자연스럽게 통합 | 강력한 기능 제공 |
단점 | 메서드 실행 JoinPoint만 지원 | 설정 복잡, 빌드 툴 필요 |
Spring AOP는 주로 프록시 기반 동작을 사용한다.
예시:
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("메서드 실행 전: " + joinPoint.getSignature().getName());
}
}
동작 원리:
Spring 컨테이너가 @Aspect를 스캔
Pointcut에 매칭되는 메서드에 대해 프록시 객체를 생성
클라이언트가 메서드 호출 시, 실제 객체가 아닌 프록시를 거쳐서 실행됨
프록시가 Advice(공통 기능) → 실제 메서드 실행 → 사후 Advice 순으로 호출
Spring AOP는 내부적으로 프록시 패턴(Proxy Pattern)을 활용한다.
JDK Dynamic Proxy
인터페이스 기반 Proxy 생성
java.lang.reflect.Proxy 활용
CGLIB (Code Generation Library)
클래스 기반 Proxy 생성
상속을 통해 프록시 객체 생성 (final 클래스/메서드 불가)
[Client]
↓ (메서드 호출)
[Proxy 객체] --- Advice 실행
↓ (실제 대상 객체 호출)
[Target Bean]
↓
[결과 반환]