
이 글은 스프링을 필요할 때 전체적으로 훑어보기 위해 작성되었으며, 저의 지식이 늘어감에 따라 주기적으로 개편됩니다.
Spring은 다음과 같은 아키텍쳐를 가진다

위처럼 약 20개의 모듈을 가지고 있다.
그 중 웹 애플리케이션을 개발하는 데 사용되는 모듈인 Web의 DispatcherServlet의 요청 처리과정에 MVC 패턴이 적용된 것 때문에 Spring Web MVC(또는 Spring MVC)이라고 불린다.
📌 왜 인스턴스 메서드로 만들까?
👉🏻 가능하지만 public이 권장된다.
1️⃣ 클라이언트가 URL 요청 (/hello)
2️⃣ Tomcat이 요청을 받고 HttpServletRequest 객체 생성
3️⃣ 요청 정보를 request 객체에 담음
4️⃣ Spring DispatcherServlet이 컨트롤러 메서드를 찾아 매핑
5️⃣ 컨트롤러 메서드의 매개변수(HttpServletRequest request)에 request 객체를 전달
6️⃣ 컨트롤러가 데이터를 처리한 후 결과를 반환
7️⃣ Spring의 ViewResolver가 해당 뷰를 찾아 클라이언트에게 응답
즉, 입력(DispatcherServlet) → 처리(Service) → 출력(View)의 흐름.
public void main(HttpServletRequest request) { }
- 원시적인 방식(서블릿 API 활용)
public void main(@RequestParam String year) { }
- @RequestParam을 사용하여 쿼리 파라미터를 개별적으로 매핑
- 예시: /main?year=2025 요청 시 year="2025"가 매핑
public void main(@RequestParam int year, @RequestParam int month, @RequestParam int day) { }
- 여러 개의 데이터 처리
public void main(@ModelAttribute MyData myData) { }
- Spring이 요청 파라미터를 해당 객체의 필드에 자동 매핑
- 객체 단위로 처리할 때 유용함
Spring MVC에서 요청을 처리하는 핵심 흐름은 Controller → Service → Repository 순으로 진행
Spring MVC에서는 지원하는 여러 가지 View 처리 방식이 있다.
예전에는 DispatcherServlet이 Model을 View로 전달하는 ModelAndView라는 방식을 이용했었다.
Model을 사용해 데이터를 추가하고, View 이름(Thymeleaf, JSP 등)을 반환하는 방식
일반적으로 사용되는 방법으로 JSON 응답을 반환하는 방식(Rest API)
해당 글에서는 Spring 애플리케이션이 처음 실행될 때 발생하는 과정과 Spring Web MVC를 구성하고 있는 구성 요소에 대해 설명했으니 참고하면 좋겠다.
1.클라이언트의 요청
2.톰켓(WAS)의 요청 접수
3.요청 라우팅
📌 톰켓의 구조
📌 서블릿의 처리


위와 같은 경우에
year=2021&month=10&day=1 이렇게 되어 있을때
@ModelAttribute 을 쓰면 쿼리스트링 뒤에 데이터가 Map의 value 값으로 들어간다.
@RequestParam, @RequestMapping 도 마찬가지로 WebDataBinder를 사용하여 매핑.
public String main(@ModelAttribute Mydate date, Model m)
이렇게 쓰면 Model Map에 MyDate를 키로 자동 저장한다. 수동으로 안써주면 첫글자를 소문자로 바꿔서 키에 넣는다
참고내용)
👉DispatcherServlet: 내부적으로 HttpServlet을 상속받은 서블릿.
👉Servlet-Context: 서블릿 컨테이너의 전역 설정과 데이터 저장을 담당하는 객체
1.exact mapping : 특정 URL과 정확히 일치해야 서블릿이 실행된다.
2.path mapping : 특정 패턴을 포함하는 URL과 매핑 가능 ( / 사용 )
3.extension mapping : 특정 확장자를 가진 URL과 매핑 ( .do -> test.do 등의 URL 처리)
4.디폴트: 모든 요청을 특정 서블릿으로 전달하는 매핑 ( / * : 모든 요청을 이 서블릿에서 처리)
각 저장소는 Map 구조를 사용하며, 범위와 생명주기에 따라 다르다.
| 저장소 | 접근 범위 | 생존 기간 | 설명 |
|---|---|---|---|
| pageContetxt | 현재 페이지 | 요청 완료 시 | 페이지 내에서만 사용 가능 (초기화됨) |
| request | 요청 간 | 요청 완료 시 | 요청이 끝나면 삭제된다.(주로 사용) |
| session | 사용자별 저장 | 브라우저 종료 시 | 쿠키 기반 사용자 정보 저장(서버 부담 크다) |
| application | 웹 애플리케이션 전체 | 서버 종료 시 | 전역적으로 공유(보안문제 주의) |
"세션은 지문인식이라서 쿠키를 들고가면 서버에서 인증 가능" 하다고 생각하면된다.
// 세션 획득
HttpSession session = request.getSession();
// Spring MVC에서는 직접 파라미터로 받기 가능
public String method(HttpSession session) { }
쿠키와 세션을 함께 사용하는 경우
Reflection API는 밑에서 설명할 스프링 프레임워크의 핵심 기능들을 구현하는 데 필수적인 역할을 하는 녀석이다.
등 스프링의 핵심인 DI 와 AOP를 구현 가능하게한녀석이다.. 하지만 그렇다고 무분별하게 사용할 수 없다. 가장큰 Reflection API 의 단점은 "객체지향의 설계 원칙을 위반" 한다는 점이다. 그외에도
등의 문제가 있다.
Reflection API를 가능하게 하는 핵심 원리는 JVM이 클래스의 메타데이터를 유지하고, 이를 통해 런타임에 조작이 가능하기 때문이다.
JVM이 클래스 로딩 과정에서 바이트코드를 메모리에 올리고, 이를 통해 Reflection API가 동작한다.
JVM은 클래스 로딩 단계에서
를 거치는데, Reflection API가 런타임에 동작하는 이유는, JVM이 클래스 정보를 메타데이터로 유지하기 때문인 것이다.
Class< ? > 객체를 통해 클래스 정보를 조회하고, 생성자/메서드/필드를 조작한다.
JVM에 대해서 인파님의 글을 참고>하면 좋을 것 같다.
더 자세한 내용으로는 주변에서 "JVM 밑바닥까지 파헤치기"라는 책을 보는것 같은데 내가 느끼기에는 난이도가 상당히 있었다.
1) 이름(key)으로 검색
2) 타입(value)으로 검색
클래스 A 와 클래스 B가 존재할 때 클래스 A안에 클래스 B가 import 되어 있으면 classA는 classB를 의존한다 라고 볼 수 있다.
의존성 주입이란 클래스 A가 필요한 클래스 B의 객체를 스스로 생성하지 않고, 외부에서 주입받는 방식을 말한다. 이를통해 의존성 역전이 가능해진 것. (인터페이스(추상화)에 의존하게 됨)
spring은 IoC를 DI로 지원한다
📌 IoC(Inversion of Control)는 객체의 생성과 의존성 관리를 개발자가 아닌 외부 컨테이너에 맡기는 설계 원칙으로 스프링에서 이를 구현한 객체 관리 시스템을 IoC컨테이너라고 부른다.
스프링은 IoC컨테이너(스프링 컨테이너)의 인터페이스로 ApplicationContext를 제공한다.
ApplicationContext는 BeanFactory(생성정보를 가져옴)를 확장한 인터페이스이다.
AnnotationConfigApplicationContext: 자바 기반 설정(@Configuration)을 사용.ClassPathXmlApplicationContext: XML 파일을 클래스패스에서 로드.WebApplicationContext: Spring MVC와 같은 웹 환경에서 사용.내가 사용하는 클래스를 알아야한다
어떻게 알까?
web.xml을 통해 DispatcherServlet이 로드되면, 이 시점에 ApplicationContext가 초기화@Component, @Bean, XML 설정 을 통해서 빈을 등록.
(@Configuration, @Bean 수동 등록 / @Component로 자동 등록)
사용할 클래스의 정보를 미리 준비해야 한다.
어떻게 미리 준비(Bean을 생성)할까?
전처리부분과 후처리 부분을 앞으로 뺀다(Frilter로) -> 코드가 간결해진다.
AOP개념이 이와 유사하다.
요청 -> Filter1 / 전처리하고 -> Filter2 / 전처리 후 서블릿 호출 -> 서블릿 처리 -> Filter2 후처리 -> Filter1 후처리
요청과 응답을 처리하는데 사용되는 Java 클래스
서블릿, JSP로 가기 전에 요청을 가로채거나 응답이 클라이언트로 전달되기 전에 데이터를 수정, 로깅, 인코딩, 인증, 권한체크 하는데 사용
스프링 시큐리티가 이걸 활용한다.
서로 다른 모듈, 계층에서 공통적으로 쓰이는 부분을 분리하여 적용하는 프로그래밍 기법
핵심 기능(target) 외의 부가 기능(advice 등)을 동적으로 추가 (직접 삽입하는 것이 아니라 “남이 넣어줌”)
메서드의 시작 또는 끝에 자동으로 코드(advice, 부가 기능)를 추가하는 기술
즉, 공통 기능을 분리하여 동적으로 적용할 수 있도록 하는 것이 AOP
begin이나 commit 코드가 필요 없음@Service
public class UserService {
@Transactional
public void saveUser(User user) {
userRepository.save(user);
// 예외 발생 시 자동 롤백
}
}
📌 트랜잭션은 1개의 Connection에서 이루어져야 한다.
@Transactional(
readOnly = true,
rollbackFor = Exception.class,
propagation = Propagation.REQUIRED
)
트랜잭션의 경계를 명확히 정의함으로써, 데이터의 일관성과 무결성을 보장한다.
| 격리 수준 | 설명 | 문제 방지 |
|---|---|---|
DEFAULT | 기본 데이터베이스 격리 수준 사용 (DBMS에 따라 다름). | DB 기본 설정에 의존. |
READ_UNCOMMITTED | 커밋되지 않은 데이터를 읽을 수 있음. Dirty Read, Non-Repeatable Read, Phantom Read 발생 가능. | 없음 |
READ_COMMITTED | 커밋된 데이터만 읽음. Dirty Read 방지, 그러나 Non-Repeatable Read와 Phantom Read는 발생 가능. | Dirty Read 방지 |
REPEATABLE_READ | 트랜잭션 동안 읽은 데이터가 변경되지 않음. Non-Repeatable Read 방지, 그러나 Phantom Read는 발생 가능. | Dirty Read, Non-Repeatable Read 방지 |
SERIALIZABLE | 트랜잭션 간 완전한 격리. 동시성 성능 저하. Dirty Read, Non-Repeatable Read, Phantom Read 모두 방지. | 모든 문제 방지 |
📌 격리 수준을 SERIALIZABLE로 두는 것 외에 팬텀리드를 방지하는 방법
| 문제 유형 | 설명 | 해결 격리 수준 |
|---|---|---|
| Dirty Read | 트랜잭션이 다른 트랜잭션에서 커밋되지 않은 데이터를 읽음. | READ_COMMITTED 이상 |
| Non-Repeatable Read | 트랜잭션 중간에 다른 트랜잭션이 데이터를 수정하여 동일 데이터를 다시 읽을 때 결과가 달라짐. | REPEATABLE_READ 이상 |
| Phantom Read | 트랜잭션 중간에 다른 트랜잭션이 데이터를 삽입하거나 삭제하여 쿼리 결과의 행 수가 달라짐. | SERIALIZABLE |
사용 사례:
예시 코드
@Controller
public class UserController {
@ExceptionHandler(NullPointerException.class)
public String handleNullPointerException(Exception ex, Model model) {
model.addAttribute("error", "잘못된 요청입니다.");
return "error"; // error.jsp로 이동
}
}
사용 사례:
예시 코드
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
public String handleRuntimeException(RuntimeException ex, Model model) {
model.addAttribute("error", "서버에서 오류가 발생했습니다.");
return "globalError"; // globalError.jsp로 이동
}
}
사용 사례:
200 OK로 반환하므로, 이를 적절한 상태 코드로 변경할 필요가 있음예시코드
@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
@Controller
public class ProductController {
@GetMapping("/product/{id}")
public String getProduct(@PathVariable String id) {
if (id.equals("0")) {
throw new ResourceNotFoundException("Product not found");
}
return "productDetail";
}
}
사용 사례:
코드 예시
@RestControllerAdvice
public class ApiExceptionHandler {
@ExceptionHandler( IllegalArgumentException.class)
public ResponseEntity<Map<String, String>> handleIllegalArgumentException(IllegalArgumentException ex) {
Map<String, String> response = new HashMap<>();
response.put("error", ex.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
@ControllerAdvice는 뷰 기반의 MVC 애플리케이션에서 전역 예외 처리를 지원한다.
@RestControllerAdvice는 REST API에서 JSON 또는 XML과 같은 응답을 생성하는 데 사용된다.
사진 출처
https://docs.spring.io/spring-framework/docs/3.2.x/spring-framework-reference/html/overview.html