2. Servlet 프로그래밍
Servlet은 서버에서 실제로 요청을 처리하는 핵심 기술이다.
앞에서 웹이HTTP요청과 응답으로 움직인다는 큰 구조를 이해했다면, 이제는 그 요청을 서버 안에서 누가 처리하는지로 시선을 옮기면 된다.
Servlet은 브라우저가 보낸 요청을 받아 필요한 작업을 수행하고, 그 결과를 다시 브라우저에 돌려주는 서버 쪽Java프로그램이다.
이 단원에서는Servlet이 왜 필요한지, 어떻게 만들고 등록하는지, 요청과 응답을 어떤 객체로 다루는지, 그리고 입력값 전송과 상태 유지까지 어떤 흐름으로 이어지는지를 한 번에 정리한다.
초보자 기준에서는 문법을 따로 외우기보다, 요청이 들어온 뒤 서버 안에서 어떤 객체가 만들어지고 어떤 메서드가 호출되며 어떤 결과가 나가는지를 흐름으로 이해하는 것이 더 중요하다.
Servlet은 단순히 화면을 보여 주는 기술이 아니라, 브라우저 요청을 받아 서버 쪽에서 실제 동작을 수행하는 실행 주체라는 점이 가장 중요하다.
2-1. Servlet이란 무엇인가
Servlet은 서버에서 실행되는Java기반 웹 프로그램이다.
브라우저가 어떤 주소를 요청하면 서버는 그 요청을 받아 처리해야 하는데, 그 처리를 담당하는 대표 기술이Servlet이다.
쉽게 말하면Servlet은 서버 안에서 요청을 처리하는 프로그램이다.
정적인HTML파일은 이미 만들어 둔 문서를 그대로 보내 주면 된다.
하지만 사용자가 입력한 값을 읽어야 하거나, 조건에 따라 다른 결과를 만들어야 하거나, 데이터베이스 값을 읽어 와야 하는 경우에는 단순 파일 전달만으로는 부족하다.
이럴 때 요청을 받아 실제 처리를 수행하는 쪽이Servlet이다.
예를 들어 로그인 요청이 들어오면 아이디와 비밀번호를 읽어야 한다.
검색 요청이 들어오면 검색어를 확인해서 결과를 만들어야 한다.
방문 횟수를 세려면 이전 상태도 함께 고려해야 한다.
이런 작업은 요청이 들어온 뒤 서버에서 실행되는 프로그램이 담당해야 하므로Servlet이 필요하다.
즉, 지금 단계에서는Servlet을 “브라우저 요청을 받아 서버에서 필요한 처리를 수행하는Java프로그램”이라고 이해하면 된다.
이 한 줄이 이 단원의 출발점이다.
2-2. Servlet의 장점과 동작 방식
Servlet의 큰 장점은 요청이 들어올 때마다 무거운 프로그램을 매번 새로 시작하는 방식이 아니라, 한 번 준비된 실행 구조 안에서 요청을 처리할 수 있다는 점이다.
이 구조 덕분에 웹 환경에서 비교적 효율적으로 동작한다.
이전 기술인CGI는 요청마다 새로운 프로세스를 만들어 처리하는 방식에 가깝다.
반면Servlet은 하나의 객체를 두고 요청마다 스레드 단위로 처리하는 구조에 가깝다.
초보자 기준에서는 “CGI는 요청마다 다시 새로 시작되는 느낌이고,Servlet은 이미 준비된 객체가 요청을 받아 처리하는 느낌” 정도로 이해하면 충분하다.
또Servlet은 한 번 만들어진 객체가 계속 유지될 수 있기 때문에, 요청이 들어올 때마다 같은 객체를 다시 활용한다.
이 점은 성능 면에서도 의미가 있고, 뒤에서 배우는 멤버변수와 지역변수 차이, 세션 예제를 이해할 때도 중요하다.
다만 객체가 유지된다고 해서 모든 값이 자동으로 안전하게 유지되는 것은 아니다.
지역변수처럼 요청이 끝나면 사라지는 값도 있고, 멤버변수처럼 객체가 살아 있는 동안 남는 값도 있고,HttpSession처럼 브라우저별로 구분되어 유지되는 값도 있다.
그래서 객체가 유지된다는 점과, 값이 어디에 저장되느냐는 별개로 봐야 한다.
2-3. 처음 생성한 Servlet 코드 구조와 요청 처리 메서드처음
Servlet을 만들면 기본 코드가 자동으로 생성된다.
이 코드는 아직 기능을 많이 넣지 않은 시작 상태이지만,Servlet이 어떤 구조로 만들어지고 어떤 메서드에서 요청을 처리하는지 한 번에 보여준다.
그래서 이 구간은 코드를 한 줄씩 외우는 식으로 보기보다, 어떤 역할의 덩어리로 나뉘는지 먼저 이해하는 것이 중요하다.
특히@WebServlet,HttpServlet상속,doGet(),doPost(),HttpServletRequest,HttpServletResponse이 다섯 부분은 처음Servlet을 볼 때 가장 먼저 익혀야 하는 핵심이다.
처음에는 코드가 길어 보여도 겁먹을 필요는 없다.
이 기본 코드는 결국 어떤 주소 요청을 받을지 정하고, 어떤 클래스가 그 요청을 처리할지 정하고,GET과POST요청을 어떤 메서드에서 처리할지 나눈 구조이다.
이 큰 흐름만 먼저 잡으면 나머지 코드는 훨씬 덜 복잡하게 보인다.
처음 생성되는 기본 코드아래 코드는
Servlet을 처음 만들었을 때 볼 수 있는 대표적인 기본 형태다.// Test.java package lab; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; // /Test 주소 요청을 처리하는 Servlet 클래스 @WebServlet("/Test") public class Test extends HttpServlet { private static final long serialVersionUID = 1L; // GET 요청을 처리하는 메서드 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 브라우저로 문자열을 응답으로 보낸다 response.getWriter().append("Served at: ").append(request.getContextPath()); } // POST 요청을 처리하는 메서드 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 지금은 doGet()을 다시 호출해서 같은 방식으로 처리한다 doGet(request, response); } }// 출력결과 // Served at: /edu이 코드는 처음 보면 길어 보이지만, 실제로는 몇 가지 핵심 덩어리로 나눠서 보면 된다.
주소와 클래스를 연결하는 부분이 있고,Servlet클래스를 선언하는 부분이 있고, 요청을 처리하는 메서드 부분이 있다.
즉, 이 코드는 복잡한 기능을 보여 주는 예제가 아니라Servlet의 기본 뼈대를 보여 주는 예제라고 보면 된다.
처음 코드에서 지금은 이렇게 보면 되는 줄들처음 생성된 코드에는 당장 핵심 흐름과 직접 연결되지 않는 줄도 함께 보인다.
하지만 이런 줄까지 처음부터 전부 같은 무게로 읽으면 오히려 더 복잡하게 느껴질 수 있다.
그래서 지금 단계에서는 어떤 줄은 핵심으로 보고, 어떤 줄은 가볍게 넘겨도 되는지 구분해서 읽는 것이 좋다.
먼저private static final long serialVersionUID = 1L;는Servlet을 만들면 자동으로 자주 따라오는 보조 코드다.
이 줄은 객체 직렬화와 관련된 식별값인데, 초보자 기준에서는 지금 당장 요청 처리 흐름을 이해하는 데 꼭 필요한 문법은 아니다.
즉, 지금은 자동으로 들어오는 부가 코드 정도로 보고 넘어가도 된다.
또 메서드 선언 끝에 붙은throws ServletException, IOException도 처음 보면 낯설 수 있다.
이 부분은 이 메서드를 실행하는 동안 예외 상황이 발생할 수 있다는 점을 메서드 선언부에서 미리 표시한 것이다.
여기서 중요한 것은 예외 문법을 깊게 파고드는 것이 아니라, 요청 처리 중 문제가 생길 수 있으므로 그 가능성을 선언해 둔 것 정도로 이해하는 것이다.
그리고response.getWriter().append("Served at: ").append(request.getContextPath());에서 보이는append()는 뒤에 내용을 이어 붙이는 메서드다.
지금 단계에서는print()와 완전히 다른 특별한 개념으로 보기보다, 문자열을 응답 뒤에 이어서 붙여 보내는 방식 정도로 이해하면 충분하다.
즉, 이 예제에서는append()를 외우는 것보다 응답 객체를 이용해 브라우저로 문자열을 보낼 수 있다는 점을 먼저 잡는 것이 더 중요하다.
@WebServlet("/Test")는 무엇인가
@WebServlet("/Test")는 이 클래스가 어떤 요청 주소를 담당할지 정하는 부분이다.
브라우저에서/Test라는 주소로 요청이 들어오면 이Servlet이 실행된다.
쉽게 말하면 이 설정은 요청 주소와 자바 클래스 연결표 같은 역할을 한다.
주소만 있다고 바로 실행되는 것이 아니다.
서버는 어떤 요청이 들어왔을 때 어느 클래스에게 일을 맡겨야 하는지 알아야 한다.
그래서@WebServlet("/Test")처럼 주소와 클래스를 연결하는 정보가 필요하다.
여기서 초보자가 헷갈리기 쉬운 점은 주소와 클래스 이름을 같은 것으로 보는 것이다.
둘은 비슷해 보여도 다르다.
주소는 사용자가 요청하는 경로이고, 클래스 이름은 그 요청을 처리하는 자바 코드의 이름이다.
이 차이를 먼저 구분해야 이후 흐름도 헷갈리지 않는다.
HttpServlet을 상속받는 이유
Test extends HttpServlet은Test클래스가HttpServlet이라는 부모 클래스를 상속받는다는 뜻이다.
상속은 이미 만들어진 기능을 물려받아 사용할 수 있게 하는 방식이다.
여기서HttpServlet은HTTP요청을 처리할 수 있는 기본 기능을 이미 가지고 있는 클래스다.
그래서 우리가Servlet을 만들 때는 처음부터 모든 기능을 직접 작성하지 않고,HttpServlet을 상속받아서 필요한 메서드만 골라서 구현하면 된다.
즉,Servlet클래스는 아무Java클래스처럼 만드는 것이 아니라, 웹 요청을 처리할 수 있는 기본 틀을 가진 부모 클래스를 이어받아서 만드는 구조라고 이해하면 된다.
이 구조 덕분에doGet()이나doPost()같은 메서드를 사용할 수 있다.
doGet()과doPost()는 어떤 메서드인가
doGet()은GET방식 요청이 들어왔을 때 실행되는 메서드다.
GET방식은 보통 데이터를 조회하거나 페이지를 요청할 때 많이 사용한다.
예를 들어 주소창에 직접 주소를 입력하거나, 일반 링크를 눌러 페이지를 이동하는 경우는 대부분GET요청에 가깝다.
즉, 서버에게 무엇인가를 저장하라고 보내는 경우보다 일단 보여 달라고 요청하는 쪽에 더 가깝다.
반면doPost()는POST방식 요청이 들어왔을 때 실행되는 메서드다.
POST방식은 보통 폼에 값을 입력해서 서버로 전달할 때 많이 사용한다.
즉,GET이 주로 조회 쪽에 가깝다면,POST는 데이터를 서버에 보내는 쪽에 더 가깝다.
완전히 이렇게만 나뉘는 것은 아니지만, 처음에는 이 정도 흐름으로 이해하면 된다.
지금 코드에서는doPost()안에서 직접 새로운 동작을 만들지 않고doGet(request, response);를 호출하고 있다.
이것은POST요청이 들어와도 일단doGet()과 같은 방식으로 처리하겠다는 뜻이다.
이 부분은 학습용 초기 코드에서 자주 보이는 형태다.
아직POST요청에서 따로 처리할 내용이 없기 때문에, 임시로 같은 결과를 보이게 해 둔 것이다.
하지만 실제로 입력값 처리나 저장 기능이 들어가면doPost()안에 별도 로직을 작성하는 경우가 많다.
HttpServletRequest와HttpServletResponse는 무엇인가
doGet()과doPost()의 매개변수를 보면HttpServletRequest request,HttpServletResponse response형태로 되어 있다.
여기서 먼저 구분해야 할 것은 앞부분과 뒷부분의 역할이다.
HttpServletRequest,HttpServletResponse는 객체의 종류를 나타내는 타입 이름이다.
쉽게 말하면 어떤 역할의 객체인지를 알려 주는 클래스 이름이다.
반면request,response는 실제로 그 객체를 사용할 때 붙인 변수 이름이다.
즉, 앞부분은 객체의 종류이고, 뒷부분은 그 객체를 코드 안에서 부를 이름이다.
HttpServletRequest는 브라우저가 서버로 보낸 요청 정보를 담는 요청 객체의 타입이다.
사용자가 어떤 방식으로 요청했는지, 어떤 주소로 들어왔는지, 어떤 값을 입력해서 보냈는지 같은 정보를 이 객체를 통해 읽는다.
즉,request는 들어온 요청을 읽는 객체라고 이해하면 된다.
HttpServletResponse는 서버가 브라우저로 돌려줄 응답 정보를 다루는 응답 객체의 타입이다.
문자열을 출력하거나, 응답 형식을 정하거나, 다른 주소로 이동시키는 작업을 이 객체를 통해 처리한다.
즉,response는 나갈 응답을 만드는 객체라고 이해하면 된다.
여기서 중요한 점은request와response를 단순한 변수 이름으로만 보면 안 된다는 것이다.
이 둘은 서버가 요청을 처리할 때 꼭 필요한 정보를 담고 있는 도구다.
request는 들어온 것을 읽고,response는 나가는 것을 만든다.
이 역할 구분이 잡혀야 이후Servlet코드도 훨씬 쉽게 읽힌다.
response.getWriter()는 무엇을 하는가
response.getWriter()는 브라우저로 문자열을 출력할 수 있게 해 주는 도구다.
쉽게 말하면 서버 쪽 자바 코드에서 웹 브라우저 화면으로 글자를 보내는 통로라고 보면 된다.
지금 코드는 아래 내용을 응답으로 보내고 있다.
"Served at: "request.getContextPath()로 가져온 현재Context Path
request.getContextPath()는 현재 웹 애플리케이션의 기본 경로를 가져오는 메서드다.
예를 들어 프로젝트가/edu경로로 실행되고 있다면 이 메서드는/edu를 돌려준다.
즉, 지금 코드는 단순히 문자열을 출력하는 것이 아니라, 현재 애플리케이션이 어떤 기본 경로에서 동작하는지도 함께 보여 주고 있다.
즉, 현재 애플리케이션 경로를 문자열 뒤에 붙여서 브라우저에 보여 주는 아주 단순한 예제다.
기능 자체가 중요한 예제라기보다, 응답 객체를 이용하면 브라우저에 내용을 보낼 수 있다는 점을 보여주는 예제로 보는 것이 맞다.
여기서 중요한 점은request와response의 역할을 구분해서 보는 것이다.
request는 들어온 요청을 담고 있고,response는 나가는 응답을 만든다.
이 차이를 이해해야 뒤에서 요청 객체와 응답 객체를 볼 때 덜 헷갈린다.
이 코드에서 먼저 읽어야 하는 흐름처음 보는 사람은 코드 줄마다 의미를 전부 해석하려고 하면 더 복잡하게 느껴질 수 있다.
그래서 아래 순서로 읽는 것이 좋다.
1.@WebServlet("/Test")를 보고 어떤 주소 요청인지 확인한다.
2.extends HttpServlet을 보고 웹 요청을 처리하는 클래스라는 점을 이해한다.
3.doGet()과doPost()를 보고 요청 방식에 따라 메서드가 나뉜다는 점을 이해한다.
4.HttpServletRequest request,HttpServletResponse response를 보고 요청 객체와 응답 객체가 함께 전달된다는 점을 확인한다.
5.response.getWriter()를 보고 응답을 브라우저에 보낼 수 있다는 점을 이해한다.
이 흐름만 잡혀도 처음 생성된Servlet코드는 훨씬 덜 복잡하게 보인다.
정리하면, 이 코드는 단순한 자동 생성 코드가 아니라 주소와 연결된Servlet클래스 안에서GET,POST요청을 나누어 처리하고, 요청 객체와 응답 객체를 받아 브라우저로 결과를 보내는 기본 구조를 보여주는 코드다.
이 구조가 잡히면 다음에 나오는 요청 객체와 응답 객체 설명도 훨씬 자연스럽게 이어진다.
즉, 이 기본 예제는/Test라는 주소 요청을 받아 현재 웹 애플리케이션 경로를 브라우저에 출력하는Servlet이다.
브라우저가
Servlet을 요청하면 서버는 메서드만 실행하고 끝내지 않는다.
요청을 처리하는 데 필요한 정보도 함께 준비하고, 처리 결과를 브라우저로 돌려보내기 위한 도구도 함께 준비해서Servlet에 넘겨 준다.
이때 전달되는 객체가HttpServletRequest와HttpServletResponse이다.
처음 보면 이름이 길어서 어렵게 느껴질 수 있다.
하지만 핵심은 단순하다.
request는 브라우저가 보낸 것을 읽는 도구이다.
response는 서버가 브라우저로 보낼 것을 만드는 도구이다.
쉽게 말하면request는 입력 확인용,response는 출력 작성용이다.
request는 브라우저가 서버로 보낸 입력 정보를 읽는 객체이고,response는 서버가 브라우저로 돌려줄 출력 결과를 만드는 객체이다.
이 둘은 따로 끊어서 외우는 것이 아니라, 요청을 받고 응답을 보내는 한 흐름으로 같이 이해해야 한다.
위 이미지는 요청이 들어왔을 때 컨테이너가
request객체와response객체를 만든 뒤Servlet에 전달하는 흐름을 보여 준다.
즉,Servlet은 혼자 동작하는 것이 아니라, 요청을 읽을 도구와 응답을 만들 도구를 함께 받아서 작업을 시작한다.
이 장면에서 가장 중요한 점은 하나다.
Servlet은 빈손으로 시작하지 않는다.
이미 준비된request와response를 전달받고, 그 안에서 필요한 정보를 읽고 결과를 만들어 낸다.
request는 왜 필요한가
브라우저가 서버에 요청을 보낼 때는 주소만 보내는 것이 아니다.
어떤 방식으로 요청했는지, 사용자가 어떤 값을 입력했는지, 어떤 주소로 들어왔는지 같은 정보도 함께 보낸다.
서버는 이런 정보를 알아야 올바르게 처리할 수 있다.
그래서 요청 정보를 담아 두는request객체가 필요하다.
예를 들어 사용자가 로그인form에 아이디와 비밀번호를 입력했다면, 서버는 그 값을 읽어야 로그인 처리를 할 수 있다.
또 어떤 주소로 들어왔는지 알아야 지금 어떤 기능을 실행해야 하는지도 판단할 수 있다.
즉,request는 브라우저가 보낸 요청 내용을 서버가 읽을 수 있게 정리해 둔 도구라고 보면 된다.
request는 무엇을 읽는 객체인가
request는 브라우저가 서버로 보낸 정보를 담고 있는 객체이다.
여기서 객체는 기능을 모아 둔 도구 정도로 이해하면 된다.
즉,request는 요청과 관련된 여러 정보를 꺼내 볼 수 있는 도구이다.
이 객체 안에는 사용자가 입력한 값도 들어 있고, 요청 방식도 들어 있고, 요청 주소도 들어 있고,Header같은 추가 정보도 들어 있다.
그래서 서버는request를 보고 “브라우저가 지금 무엇을 보내 왔는가”를 확인한다.
여기서 가장 먼저 기준을 잡아야 하는 부분이 있다.
request는 화면에 무언가를 출력하는 객체가 아니다.
역할은 어디까지나 들어온 요청을 읽는 것이다.
이 기준이 잡혀야 뒤에서response와 덜 헷갈린다.
request에서 자주 보는 메서드
처음에는 메서드 이름이 많아 보여서 복잡하게 느껴질 수 있다.
하지만 지금 단계에서는 이름을 외우는 것보다, 어떤 정보를 읽는 메서드인지 연결해서 이해하는 것이 더 중요하다.request.getMethod()
request.getMethod()는 요청 방식이 무엇인지 알려 주는 메서드다.
즉, 지금 들어온 요청이GET인지POST인지 확인할 때 사용한다.
이 정보가 왜 필요하냐면, 같은 주소라도 요청 방식에 따라 처리 목적이 달라질 수 있기 때문이다.
예를 들어 단순 조회는GET으로 보내고, 값을 서버에 저장하는 요청은POST로 보내는 경우가 많다.
그래서 이 메서드는 지금 어떤 방식으로 요청이 들어왔는지 확인하는 도구라고 이해하면 된다.
request.getRequestURI()
request.getRequestURI()는 지금 어떤 주소로 요청이 들어왔는지 알려 주는 메서드다.
쉽게 말하면 서버가 현재 처리 중인 요청 경로를 확인하는 도구이다.
예를 들어/edu/queryget으로 요청이 들어왔다면, 이 메서드로 그 주소를 확인할 수 있다.
서버 입장에서는 “지금 어떤 주소 요청을 처리하고 있는가”를 확인하는 데 도움이 된다.
request.getHeader()
request.getHeader()는 요청에 함께 들어온 부가 정보를 읽는 메서드다.
여기서Header는 요청 본문과는 별도로 따라오는 추가 정보라고 이해하면 된다.
예를 들어 어떤 브라우저를 사용했는지, 이전 페이지가 어디였는지 같은 정보가 여기에 들어올 수 있다.
즉, 이 메서드는 요청에 붙어 온 추가 정보까지 확인할 수 있게 해 주는 도구이다.
여기서 예제로 자주 보는 것이request.getHeader("referer")다.
이 코드는 현재 페이지로 오기 직전 페이지 주소를 읽을 때 자주 사용한다.
즉, 사용자가 방금 전에 어느 화면에 있었는지 참고해서 “입력 화면으로 돌아가기” 같은 링크를 만들 때 활용할 수 있다.
다만 이것도 결국Header정보 하나를 읽는 예시일 뿐이라는 점까지 함께 이해하면 된다.
여기서는 어떻게 정리하면 되는가
이 세 메서드는 각각 읽는 정보가 다르다.
하지만 묶어서 보면 어렵지 않다.
getMethod()는 요청 방식을 확인한다.getRequestURI()는 요청 주소를 확인한다.getHeader()는 요청에 붙은 추가 정보를 확인한다.즉,
request는 브라우저가 보낸 요청 안에서 필요한 정보를 꺼내는 객체라고 정리하면 된다.
response는 왜 필요한가
서버가 요청을 읽기만 하고 끝나면 브라우저는 결과를 받을 수 없다.
서버는 처리한 결과를 다시 브라우저에게 보내 줘야 한다.
그래서 응답을 만들기 위한response객체가 필요하다.
예를 들어 화면에 글자를 출력할 수도 있고, 다른 주소로 이동시키라고 보낼 수도 있고, 오류가 났다고 알려 줄 수도 있다.
즉,response는 서버가 브라우저에게 “이게 처리 결과다”라고 답장을 보내는 도구이다.
response는 무엇을 만드는 객체인가
response는 서버가 브라우저로 돌려줄 결과를 만드는 객체이다.
이 객체를 사용하면 응답 형식을 정할 수 있고, 실제 내용을 출력할 수도 있고, 다른 주소로 이동시키거나 오류 상태를 전달할 수도 있다.
여기서도 핵심은 분명하다.
response는 요청을 읽는 객체가 아니다.
response의 역할은 나갈 응답을 만드는 것이다.
그래서request와response는 비슷해 보여도 역할이 완전히 다르다.
하나는 읽고, 다른 하나는 보낸다.
response에서 자주 보는 메서드
response.setContentType()
response.setContentType()은 브라우저에게 지금 보내는 데이터의 형식을 알려 주는 메서드다.
예를 들어 이 응답이HTML인지, 문자 인코딩은 무엇인지 설정할 수 있다.
이 설정이 중요한 이유는 브라우저가 응답을 어떤 방식으로 해석해야 하는지 알아야 하기 때문이다.
예를 들어text/html;charset=utf-8로 지정하면 브라우저는 이 응답을HTML문서로 읽고, 한글도utf-8기준으로 올바르게 처리한다.
그래서 이 메서드는 브라우저가 응답을 제대로 읽게 만드는 기본 설정이라고 보면 된다.
response.getWriter()
response.getWriter()는 브라우저로 문자 데이터를 보내기 위한 출력 도구를 얻는 메서드다.
쉽게 말하면 서버에서 화면으로 글자를 보내는 통로를 만드는 것이다.
이 메서드를 통해 얻은 출력 도구를 사용하면HTML문자열이나 일반 텍스트를 브라우저에 보낼 수 있다.
즉, 응답 내용을 실제로 써 내려가는 시작점이라고 이해하면 된다.
response.sendRedirect()
response.sendRedirect()는 브라우저를 다른 주소로 다시 이동시키는 메서드다.
여기서 중요한 점은 서버 내부에서 조용히 넘기는 것이 아니라, 브라우저가 새 주소로 다시 요청하게 만든다는 점이다.
그래서 이 메서드를 사용하면 주소창이 바뀐다.
이 특징 때문에 뒤에서 배우는forward와 구분된다.
redirect는 브라우저가 다시 움직이는 방식이라고 기억하면 된다.
response.sendError()
response.sendError()는 오류 상태를 브라우저에 전달하는 메서드다.
예를 들어 잘못된 요청이 들어왔거나, 찾는 자원이 없을 때 사용할 수 있다.
이 메서드는 단순히 글자를 출력하는 것이 아니라, 응답 상태 자체를 오류로 보내는 역할을 한다.
즉, 브라우저에게 “정상 처리된 결과가 아니라 오류 응답이다”라고 알려 주는 도구이다.
여기서는 어떻게 정리하면 되는가
response의 주요 메서드도 역할별로 묶어서 보면 쉽다.
setContentType()은 응답 형식을 정한다.getWriter()는 응답 내용을 출력한다.sendRedirect()는 다른 주소로 다시 요청하게 만든다.sendError()는 오류 상태를 전달한다.즉,
response는 브라우저에 무엇을 어떤 방식으로 보낼지 정하는 객체라고 보면 된다.
request와 response는 왜 같이 이해해야 하는가
Servlet의 기본 흐름은 결국 하나이다.
브라우저가 요청을 보낸다.
서버는 그 요청 내용을request에 담는다.
그리고 결과를 만들기 위해response도 함께 준비한다.
그다음Servlet은request에서 필요한 정보를 읽고, 처리 결과를response에 담아 브라우저로 보낸다.
즉, 실제 동작은 항상 읽기와 보내기가 한 세트로 이어진다.
그래서request만 따로 외우고response를 따로 외우면 흐름이 끊겨 보일 수 있다.
하지만 실제로는 들어오는 것과 나가는 것이 한 번의 요청 처리 안에서 같이 움직인다.
정리하면 이렇게 이해하면 된다.
request는 요청을 읽는다.response는 응답을 만든다.이 역할 구분이 잡혀야 뒤에서 배우는 파라미터 처리,
GET과POST,forward,redirect, 세션 같은 개념도 자연스럽게 이어진다.
코드로 보면 어떻게 동작하는가
아래 예제는
request로 요청 정보를 읽고,response로 그 결과를 브라우저에 출력하는 가장 기본적인 흐름을 보여 준다.// QueryServlet.java package core; import java.io.IOException; import java.io.PrintWriter; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @WebServlet({"/queryget", "/querypost"}) public class QueryServlet extends HttpServlet { private static final long serialVersionUID = 1L; @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 응답 형식과 문자 인코딩을 설정한다 response.setContentType("text/html;charset=utf-8"); // 브라우저에 글자를 보내기 위한 출력 도구를 만든다 PrintWriter out = response.getWriter(); // 현재 요청 방식을 읽어서 출력한다 out.print("<h2>요청 방식 : " + request.getMethod() + "</h2>"); // 현재 요청 주소를 읽어서 출력한다 out.print("<h2>요청 URI : " + request.getRequestURI() + "</h2>"); // 출력 도구 사용을 마친다 out.close(); } }이 코드는 짧지만
request와response의 역할 차이를 아주 분명하게 보여 준다.
request.getMethod()와request.getRequestURI()는 브라우저가 보낸 요청 정보를 읽는 부분이다.
반대로response.setContentType()과response.getWriter()는 서버가 브라우저로 돌려줄 응답을 만드는 부분이다.
코드를 한 줄씩 보면
response.setContentType("text/html;charset=utf-8");
이 줄은 브라우저에게 지금부터 보내는 내용이
HTML이라는 점을 알려 주는 코드다.
동시에 한글을utf-8방식으로 읽으라고 설정하는 부분이기도 하다.
이 설정이 있어야 브라우저가 내용을 올바르게 해석할 수 있다.
PrintWriter out = response.getWriter();
이 줄은 브라우저로 글자를 보내기 위한 출력 도구를 만드는 코드다.
이 도구가 있어야 서버가 만든 결과를 실제 화면으로 보낼 수 있다.
즉, 응답을 쓰기 시작하기 위한 준비 단계라고 보면 된다.
request.getMethod()
이 코드는 현재 요청 방식이 무엇인지 읽는다.
즉, 지금 요청이GET인지POST인지 확인하는 부분이다.
서버 입장에서는 “지금 받은 요청이 어떤 방식인가”를 판단하는 코드이다.
request.getRequestURI()
이 코드는 지금 어떤 주소로 요청이 들어왔는지 읽는다.
즉, 현재 서버가 처리 중인 요청 경로를 확인하는 부분이다.
요청 대상을 확인하는 코드라고 생각하면 된다.
out.close()
이 줄은 출력 도구 사용을 마쳤다는 뜻이다.
응답 작성을 끝내는 마무리 단계라고 이해하면 된다.
이 예제에서 꼭 봐야 하는 핵심
이 예제는 단순히 결과를 화면에 출력하는 코드가 아니다.
request는 읽고,response는 쓴다는 역할 차이를 가장 기본 형태로 보여 주는 예제이다.
이 단계에서는 메서드 이름을 많이 외우는 것보다 아래 흐름을 먼저 잡는 것이 중요하다.
- 브라우저가 요청을 보낸다.
- 서버는
request와response를 만든다.Servlet은request에서 필요한 정보를 읽는다.- 처리 결과를
response에 담아 브라우저로 보낸다.이 흐름이 이해되면 요청 객체와 응답 객체는 따로 외워야 하는 개념이 아니라, 한 번의 요청 처리 안에서 함께 움직이는 기본 도구라는 점이 분명해진다.
Servlet 코드는 어떤 순서로 읽으면 쉬운가
처음
Servlet코드를 보면import부터 한 줄씩 다 읽으려고 하기 쉽다.
하지만 초보자 기준에서는 그렇게 보는 것보다, 이 코드가 어디서 요청을 받고 어떻게 응답하는지를 먼저 보는 편이 훨씬 쉽다.
다른 사람이 만든Servlet코드를 볼 때는 보통 아래 순서로 보면 된다.
@WebServlet()을 먼저 본다.
어떤 주소 요청을 처리하는지 확인한다.
doGet(),doPost(),service()중 어디가 시작점인지 본다.
어떤 요청 방식에서 실행되는지 확인한다.
request.getParameter()나getParameterValues()가 있는지 본다.
사용자가 보낸 값을 읽는 부분인지 확인한다.
response.getWriter(),sendRedirect(),forward()같은 코드를 본다.
응답을 어떻게 처리하는지 확인한다.
getSession(),setAttribute(),getAttribute()가 있는지 본다.
상태 유지와 관련된 코드인지 확인한다.
- 마지막으로 조건문, 반복문, 계산, 출력 부분을 본다.
이Servlet이 실제로 무슨 일을 하는지 정리한다.즉,
Servlet코드는 처음부터 끝까지 다 읽기보다 주소 → 요청 메서드 → 입력값 → 응답 방식 → 상태 유지 → 실제 처리 내용 순서로 보면 훨씬 덜 헷갈린다.
여기까지 이해되면 요청 객체와 응답 객체는 더 이상 따로 떨어진 개념이 아니다.
브라우저의 요청을 읽고, 서버의 결과를 다시 보내는 한 흐름 안에서 함께 움직이는 도구로 이해하면 된다.
2-4-1. 요청 객체와 응답 객체 주요 메서드 더보기
request와response는 기본 역할만 이해하고 넘어가면 뒤 예제에서 다시 막힐 수 있다.
뒤에서 값 전달, 상태 유지, 화면 이동, 객체 공유를 다룰 때 같은 객체의 메서드가 계속 등장하기 때문이다.
그래서 여기서는 앞에서 본 메서드 외에 자주 나오는 주요 메서드를 한 번 더 정리한다.
중요한 기준은 단순하다.
request는 들어온 요청과 요청 안의 값을 읽거나 저장할 때 사용한다.
response는 브라우저로 나갈 응답의 형식, 내용, 이동, 상태를 정할 때 사용한다.
메서드 이름만 외우기보다, 어떤 객체 앞에 붙는지와 무엇을 다루는지를 함께 봐야 한다.
request.getParameter()
request.getParameter()는 브라우저가 보낸 값 하나를 읽는 메서드다.
form의name속성이나Query String의 이름을 기준으로 값을 꺼낸다.
기본 문법// ParameterExample.java String 변수명 = request.getParameter("파라미터이름");
문법 해석
request에서"파라미터이름"으로 전달된 값 하나를 읽는다.
읽은 값은 문자열이므로String변수에 저장한다.
예제// ParameterExample.java String userName = request.getParameter("guestName"); // guestName 이름으로 전달된 값 하나를 읽는다
핵심 정리서버는 입력창의 모양이 아니라
name을 기준으로 값을 찾는다.
getParameter()는 브라우저가 보낸 값 하나를 읽을 때 사용한다.
request.getParameterValues()
request.getParameterValues()는 같은 이름으로 전달된 여러 값을 읽는 메서드다.
체크박스처럼 여러 개를 선택할 수 있는 입력값을 읽을 때 사용한다.
기본 문법// ParameterValuesExample.java String[] 변수명 = request.getParameterValues("파라미터이름");
문법 해석
request에서 같은 이름으로 전달된 여러 값을 읽는다.
여러 값이 올 수 있으므로 결과는String[]배열로 받는다.
예제// ParameterValuesExample.java String[] foods = request.getParameterValues("food"); // food 이름으로 전달된 여러 값을 배열로 읽는다
핵심 정리체크박스는 하나만 선택할 수도 있지만, 구조상 여러 값이 올 수 있다.
그래서 여러 선택값을 제대로 처리하려면getParameterValues()로 읽어야 한다.
request.setCharacterEncoding()
request.setCharacterEncoding()은 요청으로 들어온 문자 데이터를 어떤 인코딩으로 읽을지 정하는 메서드다.
특히POST방식으로 한글을 받을 때 중요하다.
기본 문법// EncodingExample.java request.setCharacterEncoding("문자인코딩");
문법 해석브라우저가 보낸 요청 데이터를 지정한 문자 인코딩 방식으로 읽겠다고 설정한다.
보통 한글 처리를 위해"utf-8"을 사용한다.
예제// EncodingExample.java request.setCharacterEncoding("utf-8"); // 값을 읽기 전에 한글 인코딩을 맞춘다 String name = request.getParameter("name"); // 인코딩 설정 후 값을 읽는다
핵심 정리이 메서드는 값을 읽기 전에 실행해야 한다.
POST방식에서 한글 입력값을 처리할 때는getParameter()보다setCharacterEncoding()을 먼저 실행해야 한다.
request.setAttribute()
request.setAttribute()는 현재 요청 객체에 값을 저장하는 메서드다.
이 값은 주로 같은 요청 안에서 다른 자원으로 넘길 때 사용한다.
기본 문법// AttributeExample.java request.setAttribute("이름", 저장할값);
문법 해석현재
request객체 안에"이름"이라는 이름표를 붙여 값을 저장한다.
나중에 같은 요청 안에서"이름"을 기준으로 다시 꺼낼 수 있다.
예제// AttributeExample.java request.setAttribute("msg", "처리 결과입니다."); // 현재 요청에 msg 이름으로 값을 저장한다
핵심 정리
request에 저장한 값은 현재 요청이 유지되는 동안만 사용할 수 있다.
즉,forward처럼 같은 요청을 이어서 넘기는 경우에는 유지되지만,redirect처럼 새 요청이 시작되면 사라진다.
request.getAttribute()
request.getAttribute()는setAttribute()로 저장해 둔 값을 다시 꺼내는 메서드다.
사용자가form으로 보낸 값을 읽는getParameter()와 헷갈리면 안 된다.
기본 문법// AttributeExample.java Object 변수명 = request.getAttribute("이름");
문법 해석현재
request객체 안에"이름"으로 저장된 값을 꺼낸다.
꺼낸 값은 기본적으로Object타입으로 나오므로, 필요하면 원래 저장했던 타입으로 형변환해서 사용한다.
예제// AttributeExample.java Object msg = request.getAttribute("msg"); // request에 저장된 msg 값을 꺼낸다
핵심 정리
getParameter()는 브라우저가 보낸 입력값을 읽는다.
getAttribute()는 서버 코드가 직접 저장해 둔 객체를 꺼낸다.
getParameter()와getAttribute()는 이름이 비슷해도 읽는 대상이 완전히 다르다.
request.removeAttribute()
request.removeAttribute()는setAttribute()로 저장해 둔 값을 삭제하는 메서드다.
현재 요청 안에서 더 이상 필요 없는 값을 지울 때 사용한다.
기본 문법// AttributeExample.java request.removeAttribute("이름");
문법 해석현재
request객체 안에"이름"으로 저장된 값을 삭제한다.
삭제한 뒤에는 같은 이름으로getAttribute()를 해도 기존 값을 꺼낼 수 없다.
예제// AttributeExample.java request.removeAttribute("msg"); // request에 저장된 msg 값을 삭제한다
핵심 정리실습에서는
setAttribute()와getAttribute()를 더 자주 보지만, 저장한 값을 지우는 메서드도 함께 알아 두면scope를 이해할 때 도움이 된다.
request.getContextPath()
request.getContextPath()는 현재 웹 애플리케이션의 기본 경로를 가져오는 메서드다.
예를 들어 프로젝트가/edu경로로 실행되고 있다면 이 메서드는/edu를 반환한다.
기본 문법// ContextPathExample.java String 변수명 = request.getContextPath();
문법 해석현재 웹 애플리케이션의 기본 경로를 문자열로 가져온다.
링크나 이동 경로를 만들 때 기준 경로로 사용할 수 있다.
예제// ContextPathExample.java String contextPath = request.getContextPath(); // 현재 웹 애플리케이션 기본 경로를 가져온다
핵심 정리
getContextPath()는 현재 프로젝트가 어떤 기본 경로에서 실행되는지 확인할 때 사용한다.
프로젝트 경로가 바뀔 수 있는 상황에서는 경로를 직접 쓰는 것보다Context Path를 기준으로 잡는 편이 더 안전하다.
request.getRequestURI()
request.getRequestURI()는 현재 요청의 경로를 가져오는 메서드다.
서버가 지금 어떤 요청 주소를 처리하고 있는지 확인할 때 사용한다.
기본 문법// UriExample.java String 변수명 = request.getRequestURI();
문법 해석현재 요청의
URI를 문자열로 가져온다.
여기서URI는 서버 안에서 요청된 경로라고 이해하면 된다.
예제// UriExample.java String uri = request.getRequestURI(); // 현재 요청 URI를 가져온다
핵심 정리예를 들어
/edu/queryget으로 요청이 들어오면 그 요청 경로를 확인할 수 있다.
즉, 현재 요청 대상을 확인하는 메서드다.
request.getRequestURL()
request.getRequestURL()은 현재 요청의 전체 주소를 가져오는 메서드다.
getRequestURI()가 경로 중심이라면,getRequestURL()은 프로토콜, 서버 주소, 포트, 경로까지 포함한다.
기본 문법// UrlExample.java StringBuffer 변수명 = request.getRequestURL();
문법 해석현재 요청의 전체
URL을 가져온다.
반환 타입은 문자열처럼 보이지만 정확히는StringBuffer이다.
예제// UrlExample.java StringBuffer url = request.getRequestURL(); // 현재 요청의 전체 URL을 가져온다
핵심 정리
getRequestURI()가/edu/queryget처럼 경로를 보여 준다면,getRequestURL()은http://localhost:8088/edu/queryget처럼 전체 주소를 보여 줄 수 있다.
request.getQueryString()
request.getQueryString()은 주소 뒤에 붙은Query String전체를 가져오는 메서드다.
Query String은 주소에서?뒤에 붙는name=value형태의 문자열이다.
기본 문법// QueryStringExample.java String 변수명 = request.getQueryString();
문법 해석주소 뒤에 붙은
Query String전체를 문자열로 가져온다.
개별 값을 나눠서 읽는 것이 아니라?뒤 전체 문자열을 확인한다.
예제// QueryStringExample.java String queryString = request.getQueryString(); // 주소 뒤 Query String 전체를 가져온다
핵심 정리예를 들어 주소가
/edu/queryget?guestName=둘리&num=11이라면guestName=둘리&num=11부분을 가져온다.
개별 값을 읽을 때는getParameter()를 쓰고, 전체 문자열을 확인할 때는getQueryString()을 쓴다.
request.getHeader()
request.getHeader()는 요청에 포함된Header정보를 읽는 메서드다.
Header는 요청에 딸려 오는 부가 정보다.
기본 문법// HeaderExample.java String 변수명 = request.getHeader("헤더이름");
문법 해석요청에 포함된
Header중에서"헤더이름"에 해당하는 값을 읽는다.
괄호 안에 어떤 이름을 넣느냐에 따라 읽는 정보가 달라진다.
예제// HeaderExample.java String referer = request.getHeader("referer"); // 이전 페이지 주소를 읽는다
핵심 정리
referer는 사용자가 현재 페이지로 오기 직전에 있었던 주소를 나타낸다.
즉,getHeader()는 요청 본문 값이 아니라 요청에 붙은 추가 정보를 읽는 메서드다.
request.getSession()
request.getSession()은 현재 요청과 연결된 세션 객체를 가져오는 메서드다.
이미 세션이 있으면 기존 세션을 가져오고, 없으면 새 세션을 만들어 가져온다.
기본 문법// SessionExample.java HttpSession 변수명 = request.getSession();
문법 해석현재 요청을 보낸 브라우저와 연결된
HttpSession객체를 가져온다.
세션은 브라우저별 상태를 저장할 때 사용한다.
예제// SessionExample.java HttpSession session = request.getSession(); // 현재 요청과 연결된 세션을 가져온다
핵심 정리로그인 정보, 장바구니, 방문 횟수처럼 여러 요청에 걸쳐 이어져야 하는 값을 다룰 때 필요하다.
session은 브라우저별로 구분된 상태를 서버에 저장할 때 사용하는 객체다.
response.setContentType()
response.setContentType()은 브라우저에게 지금 보내는 데이터의 형식을 알려 주는 메서드다.
예를 들어 응답이HTML인지, 한글은 어떤 문자 인코딩으로 읽어야 하는지 알려 줄 수 있다.
기본 문법// ResponseExample.java response.setContentType("응답형식;charset=문자인코딩");
문법 해석브라우저에게 응답 데이터의 종류와 문자 인코딩을 알려 준다.
실습에서는HTML응답과 한글 처리를 위해"text/html;charset=utf-8"을 자주 사용한다.
예제// ResponseExample.java response.setContentType("text/html;charset=utf-8"); // HTML 응답과 한글 인코딩을 설정한다
핵심 정리이 설정이 있어야 브라우저가 서버의 응답을 올바르게 해석할 수 있다.
한글이 깨지지 않도록charset=utf-8을 함께 적는 경우가 많다.
response.getWriter()
response.getWriter()는 브라우저로 문자 데이터를 보낼 출력 도구를 가져오는 메서드다.
이 메서드의 결과로 받는 객체가PrintWriter다.
기본 문법// WriterExample.java PrintWriter 변수명 = response.getWriter();
문법 해석
response에서 브라우저로 문자열을 보낼 수 있는 출력 도구를 꺼낸다.
이 도구를 변수에 저장한 뒤print()같은 메서드로 응답 내용을 작성한다.
예제// WriterExample.java PrintWriter out = response.getWriter(); // 브라우저에 출력할 도구를 가져온다
핵심 정리
response.getWriter()는 화면에 바로 출력하는 코드가 아니라, 브라우저로 문자열을 보낼 준비를 하는 코드다.
실제 출력은out.print()같은 메서드로 수행한다.
out.print()
out.print()는 브라우저 화면에 문자열을 출력하는 메서드다.
문자열 안에HTML태그를 넣으면 브라우저가 그 태그를 해석해서 화면에 보여 준다.
기본 문법// PrintExample.java out.print("출력할문자열");
문법 해석
PrintWriter출력 도구를 사용해서 브라우저로 문자열을 보낸다.
문자열 안에HTML태그가 있으면 브라우저는 그 태그를 화면 구조로 해석한다.
예제// PrintExample.java out.print("<h2>요청 처리 결과입니다.</h2>"); // 브라우저에 HTML 문자열을 출력한다
핵심 정리이 방식은
Servlet에서 직접 화면을 만들 때 사용한다.
다만 코드 안에HTML문자열이 많아지면 복잡해질 수 있기 때문에, 나중에는 화면 표현을JSP로 넘기는 흐름도 사용한다.
out.close()
out.close()는 출력 도구 사용을 끝내는 메서드다.
응답 내용을 다 작성했다는 의미로 볼 수 있다.
기본 문법// CloseExample.java out.close();
문법 해석브라우저로 응답을 작성하던 출력 도구를 닫는다.
출력 작업이 끝났다는 의미로 볼 수 있다.
예제// CloseExample.java out.close(); // 출력 작업을 마친다
핵심 정리간단한 실습에서는 마지막에 자주 붙는다.
출력 도구를 닫으면 그 이후에는 같은 출력 도구로 내용을 더 쓰지 않는다고 이해하면 된다.
response.sendRedirect()
response.sendRedirect()는 브라우저에게 다른 주소로 다시 요청하라고 알려 주는 메서드다.
즉, 서버 내부에서 조용히 넘기는 것이 아니라 브라우저가 새 주소로 다시 이동하게 만든다.
기본 문법// RedirectExample.java response.sendRedirect("이동할주소");
문법 해석브라우저에게
"이동할주소"로 새 요청을 다시 보내라고 응답한다.
그래서 주소창이 바뀌고 새로운 요청이 시작된다.
예제// RedirectExample.java response.sendRedirect("/edu/first.html"); // 브라우저가 새 주소로 다시 요청하게 만든다
핵심 정리
sendRedirect()를 사용하면 새 요청이 시작되고 주소창도 바뀐다.
그래서 기존request에 저장한 값은 그대로 이어지지 않는다.
response.sendError()
response.sendError()는 브라우저에게 오류 상태를 응답으로 보내는 메서드다.
정상 화면을 만드는 것이 아니라, 요청 처리에 문제가 있다는 상태를 전달한다.
기본 문법// ErrorExample.java response.sendError(상태코드);
문법 해석브라우저에게 지정한 상태 코드의 오류 응답을 보낸다.
예를 들어404는 찾을 수 없음 상태를 의미한다.
예제// ErrorExample.java response.sendError(404); // 찾을 수 없음 오류 상태를 보낸다
핵심 정리잘못된 주소나 처리할 수 없는 요청이 들어왔을 때 오류 응답을 보낼 수 있다.
단순히 글자를 출력하는 것이 아니라 응답 상태 자체를 오류로 보내는 메서드다.
response.setStatus()
response.setStatus()는 응답 상태 코드를 직접 설정하는 메서드다.
상태 코드는 응답이 정상인지, 오류인지, 이동이 필요한지를 숫자로 나타낸다.
기본 문법// StatusExample.java response.setStatus(상태코드);
문법 해석브라우저에게 보낼 응답의 상태 코드를 설정한다.
오류 화면을 바로 보내는 것보다는 응답 상태 자체를 지정하는 역할에 가깝다.
예제// StatusExample.java response.setStatus(200); // 정상 처리 상태 코드를 설정한다
핵심 정리초보자 단계에서는
200은 정상,404는 찾을 수 없음,500은 서버 오류 정도로 먼저 이해하면 된다.
다만 실제 오류 응답을 분명하게 보내고 싶을 때는sendError()를 사용하는 경우가 더 자연스럽다.
메서드 더보기 핵심 정리
request와response메서드는 많아 보이지만 기준은 단순하다.
request는 브라우저가 보낸 값, 요청 정보, 저장된 객체, 세션을 다룬다.response는 브라우저로 보낼 형식, 내용, 이동, 상태를 다룬다.특히 아래 차이는 반드시 구분해야 한다.
getParameter()는 브라우저가 보낸 입력값을 읽는다.getAttribute()는 서버가 저장해 둔 객체를 꺼낸다.sendRedirect()는 새 요청을 만들고 주소창이 바뀐다.forward()는 같은 요청을 유지하고 주소창이 바뀌지 않는다.결국 메서드를 외우는 기준은 이름이 아니라,
request앞에 붙는지response앞에 붙는지, 그리고 입력을 읽는지 응답을 만드는지이다.
2-5. Servlet 생명주기와 객체 재사용
Servlet은 요청이 들어올 때마다 매번 새로 만들어지는 구조가 아니다.
처음 요청이 들어왔을 때 객체가 한 번 생성되고, 필요한 준비 작업까지 끝난 뒤에 요청을 처리한다.
그다음 요청부터는 이미 만들어져 있는 같은 객체가 다시 사용된다.
이 전체 흐름을Servlet의 생명주기라고 한다.
처음 이 개념을 보면init(),service(),destroy()라는 메서드 이름부터 외우려고 하기 쉽다.
하지만 이 단계에서는 이름을 먼저 외우는 것보다, 객체가 언제 만들어지고 언제 다시 사용되는지를 먼저 이해하는 것이 훨씬 중요하다.
이 흐름이 잡혀야 메서드 역할도 자연스럽게 이해된다.
생명주기를 이해할 때 먼저 잡아야 하는 메서드는init(),service(),destroy()다.
init()은Servlet이 처음 준비될 때 한 번 호출되는 초기화 메서드다.service()는 요청이 들어올 때마다 실행되는 처리 중심 메서드다.destroy()는Servlet이 종료되거나 메모리에서 해제될 때 호출되는 마무리 메서드다.
즉, 흐름은 이렇게 정리하면 된다.
처음 요청이 들어오면 객체가 생성되고init()이 호출된 뒤service()가 실행된다.
그다음 요청부터는 이미 있는 객체를 다시 사용하면서service()만 반복된다.
마지막에는destroy()가 호출되면서 생명주기가 끝난다.
중요한 점은Servlet이 요청마다 새로 만들어지는 구조가 아니라, 한 번 만들어진 객체가 여러 요청을 처리하는 구조라는 점이다.
즉, 서버는 매 요청마다 새Servlet을 만드는 것이 아니라, 이미 만들어 둔Servlet에게 계속 일을 맡긴다.
이 구조를 이해해야 뒤에서 멤버변수와 지역변수의 차이도 자연스럽게 연결된다.
왜냐하면 같은 객체를 계속 쓰기 때문에, 객체 안에 저장한 값은 남을 수 있고 메서드 안에서 잠깐 만든 값은 다시 사라질 수 있기 때문이다.
생명주기 흐름 먼저 보기
Servlet의 생명주기를 글로만 보면 추상적으로 느껴질 수 있다.
특히 “객체가 한 번 만들어진다”는 말이 초보자에게는 바로 와닿지 않을 수 있다.
그래서 먼저 그림으로 보면 첫 요청과 그다음 요청의 차이가 훨씬 잘 보인다.
Servlet은 처음 요청될 때 생성되고 초기화된 뒤 요청을 처리한다.
이미 만들어진
Servlet객체는 다음 요청에서도 다시 사용된다.
여기서 먼저 봐야 할 부분은 처음 요청과 이후 요청의 흐름이 같지 않다는 점이다.
처음 요청에서는 객체 생성과 초기화가 함께 일어난다.
즉, 아직 준비된Servlet이 없기 때문에 먼저 객체를 만들고 사용할 준비를 해야 한다.
반면 다음 요청부터는 이미 준비된 객체가 있기 때문에, 새로 만들지 않고 바로 요청을 처리할 수 있다.
이 차이가 바로 객체 재사용이다.
즉, 생명주기의 핵심은 메서드 이름을 외우는 것이 아니라 객체가 언제 만들어지고, 언제 재사용되고, 언제 끝나는지를 이해하는 데 있다.
이 흐름이 잡혀야 뒤에서 “왜 어떤 값은 계속 남고 어떤 값은 다시 시작되는가”도 자연스럽게 이해된다.
예제 1. FlowServlet으로 생명주기 확인아래 예제는
Servlet객체가 언제 생성되고, 언제 초기화되고, 요청마다 어떤 메서드가 호출되는지를Console에서 확인하는 예제다.
이 예제는 브라우저 화면에 무엇이 보이는지보다, 서버 쪽 출력 순서가 어떻게 찍히는지를 보는 것이 더 중요하다.
왜냐하면 생명주기는 화면 결과보다 서버 내부에서 어떤 순서로 동작하는가를 확인하는 개념이기 때문이다.// FlowServlet.java package core; import java.io.IOException; import jakarta.servlet.ServletConfig; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @WebServlet("/flow") public class FlowServlet extends HttpServlet { private static final long serialVersionUID = 1L; public FlowServlet() { super(); // 객체가 생성되는 시점을 확인한다 System.out.println("FlowServlet 객체 생성...."); } public void init(ServletConfig config) throws ServletException { // 초기화가 한 번 실행되는지 확인한다 System.out.println("init() 메서드 호출....."); } public void destroy() { // 종료 시점에 호출되는지 확인한다 System.out.println("destroy() 메서드 호출...."); } protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 요청이 들어올 때마다 실행된다 System.out.println("service() 메서드 호출...."); } }이 코드에서 먼저 눈에 띄는 줄이 두 개 더 있다.
생성자 안의super();와init(ServletConfig config)의ServletConfig부분이다.
super();는 부모 클래스 생성자를 호출하는 코드다.
즉, 지금 만든FlowServlet만 따로 준비하는 것이 아니라, 먼저 부모인HttpServlet쪽 기본 준비도 함께 실행한다는 뜻으로 보면 된다.
초보자 기준에서는 부모 쪽 기본 기능도 같이 준비한다는 표시 정도로 이해하면 충분하다.
ServletConfig는Servlet이 처음 준비될 때 전달되는 초기 설정 정보 객체다.
이 예제에서는 그 안의 값을 직접 사용하지는 않지만,init()메서드가 이런 설정 정보를 함께 받을 수 있다는 점을 코드 형태로 보여 주고 있다.
즉, 지금은init()호출 흐름을 보는 예제이므로ServletConfig자체를 깊게 외우기보다 초기화 단계에서 함께 전달될 수 있는 설정 정보 정도로 이해하면 된다.
이 코드에서 봐야 하는 핵심은 세 부분이다.
생성자는 객체가 실제로 만들어질 때 실행된다.
즉,Servlet이 처음 준비되는 순간을 보여 준다.
init()은 객체가 준비되는 처음 단계에서 한 번 실행된다.
여기서는 “이제 이Servlet을 사용할 준비가 끝났는가”를 보여 주는 메서드라고 이해하면 된다.
즉, 객체를 만들기만 한 것이 아니라 실제 사용 가능한 상태로 올리는 단계이다.
service()는 요청이 들어올 때마다 반복 실행된다.
그래서 처음 요청에서도 실행되고, 두 번째 요청에서도 실행되고, 그다음 요청에서도 계속 실행된다.
이 메서드는 실제 요청 처리가 반복된다는 사실을 가장 직접적으로 보여 주는 부분이다.
즉, 이 예제는 화면 결과를 만드는 예제가 아니라 생명주기 메서드의 호출 순서를 확인하는 예제다.
그래서 출력 결과도 브라우저가 아니라Console기준으로 보는 것이 맞다.
이 출력 흐름을 보면 처음 요청에서만 객체 생성과init()이 실행되고, 그다음 요청부터는service()만 반복된다는 점을 확인할 수 있다.
즉,Servlet객체는 한 번 만들어 둔 뒤 계속 재사용된다는 사실이 출력 순서로 드러난다.
첫 요청 시 객체 생성,
init(),service()호출 흐름과 종료 시destroy()호출 흐름을 확인할 수 있다.
(destroy()가 안보이는 이유는 너무 빨리 없어져서 글에 표시가 되지 않는다!)
이 예제에서 꼭 기억해야 할 것은service()가 반복된다는 점이다.
브라우저가 다시 요청할 때마다 서버는 새 객체를 만드는 것이 아니라, 이미 만들어진Servlet에게 다시 일을 맡긴다.
즉, 두 번째 요청부터는 “객체 생성 → 초기화”를 다시 하는 것이 아니라, 준비된 객체가 바로 요청을 처리한다.
이 흐름이 바로 객체 재사용이다.
이 개념은 단순히 “아, 재사용되는구나” 정도로 끝나면 안 된다.
같은 객체가 계속 쓰인다는 말은, 그 객체 안에 들어 있는 값이나 상태가 다음 요청에도 영향을 줄 수 있다는 뜻이기 때문이다.
그래서 생명주기는 단순한 실행 순서가 아니라, 뒤에서 배우는 변수와 상태 유지 개념의 출발점이 된다.
정리하면 같은Servlet객체가 재사용되기 때문에 멤버변수는 값이 누적될 수 있고, 메서드 안 지역변수는 요청마다 다시 초기화된다.
예제 2. 객체 재사용이 변수 값에 미치는 영향 (MemberLocalServlet)생명주기 흐름을 확인했다면, 이제 객체가 재사용된다는 사실이 값 저장 방식에 어떤 차이를 만드는지 볼 수 있다.
같은Servlet객체가 계속 사용된다면, 어디에 저장한 값이 남고 어디에 저장한 값이 사라지는지도 달라진다.
즉, “같은 객체를 다시 쓴다”는 사실은 단순히 성능 이야기가 아니라, 변수 값이 유지되느냐 다시 시작되느냐와도 연결된다.
아래 예제는 멤버변수와 지역변수의 차이를 통해 그 부분을 보여 준다.
멤버변수는 객체 안에 만들어지는 변수이고, 지역변수는 메서드가 실행될 때마다 새로 만들어지는 변수다.
둘 다 변수이지만 저장되는 위치와 유지되는 시간이 다르다.
즉, 이름은 둘 다 변수이지만 살아 있는 범위와 유지되는 시간이 다르다.// MemberLocalServlet.java package core; import java.io.IOException; import java.io.PrintWriter; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @WebServlet("/memberlocal") public class MemberLocalServlet extends HttpServlet { private static final long serialVersionUID = 1L; int member_v = 0; // 객체가 살아 있는 동안 유지되는 멤버변수 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 응답 형식을 설정한다 response.setContentType("text/html; charset=utf-8"); // 브라우저에 출력할 도구를 만든다 PrintWriter out = response.getWriter(); int local_v = 0; // 요청마다 새로 만들어지는 지역변수 // 두 변수의 값을 1씩 증가시킨다 member_v++; local_v++; // 현재 값을 브라우저에 출력한다 out.print("<h2>member_v(멤버변수) : " + member_v + "</h2>"); out.print("<h2>local_v(지역변수) : " + local_v + "</h2>"); out.close(); } }이 코드에서 중요한 것은
member_v와local_v가 선언된 위치다.
member_v는 클래스 안에 있으므로 객체가 살아 있는 동안 값이 유지될 수 있다.
즉, 같은Servlet객체가 계속 남아 있는 동안에는 이 변수도 그 객체 안에 함께 남아 있을 수 있다.
반면local_v는doGet()안에서 만들어지므로 요청이 들어올 때마다 새로 시작한다.
메서드가 실행될 때 잠깐 만들어졌다가, 메서드 실행이 끝나면 그 요청에서 쓰던 값도 함께 끝난다고 이해하면 된다.
즉, 다음 요청이 들어오면 이전 요청의local_v를 다시 쓰는 것이 아니라, 새로운local_v가 또 만들어진다.
아래 결과를 보면 멤버변수는 요청이 반복될수록 계속 누적된다.
왜냐하면 같은 객체가 계속 사용되고 있고, 그 객체 안의 멤버변수도 함께 유지되기 때문이다.
반면 지역변수는 매 요청마다 다시0에서 시작해서1이 된다.
왜냐하면 지역변수는 요청 처리 메서드가 실행될 때마다 새로 만들어지기 때문이다.
즉, 같은 코드처럼 보여도 변수의 선언 위치에 따라 결과가 달라진다.
멤버변수는 누적되고 지역변수는 요청마다 다시 시작된다는 차이를 확인할 수 있다.
이 예제의 핵심은 분명하다.
객체가 유지되기 때문에 멤버변수 값은 누적될 수 있지만, 메서드 안에서 새로 만드는 지역변수는 요청마다 다시 시작된다.
이 차이를 이해해야 왜 웹에서 상태 유지가 따로 필요한지도 자연스럽게 이어진다.
왜냐하면 모든 값이 자동으로 계속 유지되는 것이 아니기 때문이다.
어떤 값은 객체 재사용 때문에 남을 수 있고, 어떤 값은 요청이 끝나면 사라진다.
그래서 웹에서는 값을 어디에 저장하느냐가 중요해진다.
정리하면Servlet생명주기는 생성 → 초기화 → 요청 처리 반복 → 종료 흐름으로 이해하면 된다.
그리고 객체 재사용은 단순히 성능 이야기로 끝나지 않는다.
값을 어디에 저장했느냐에 따라 다음 요청에서도 값이 남을 수 있고, 매번 새로 시작될 수도 있다는 점까지 함께 연결해서 봐야 한다.
즉, 생명주기를 이해한다는 것은 메서드 이름 세 개를 외우는 것이 아니라, 같은 객체가 계속 사용된다는 사실이 실제 코드와 변수 값에 어떤 영향을 주는지 이해하는 것이다.
2-6. 브라우저에서 값 보내기와 서버에서 값 읽기브라우저에서 값을 보내고 서버에서 그 값을 읽는 과정은 따로 떨어진 개념이 아니다.
브라우저는GET이나POST방식으로 요청을 보내고, 서버는request객체를 통해 그 안에 들어 있는 값을 읽는다.
즉, 이 구간은 브라우저가 값을 보내는 방식과 서버가 값을 꺼내는 방식이 하나의 흐름으로 연결되어 있다는 점을 이해하는 것이 가장 중요하다.
처음 이 부분을 배우면GET,POST,form,action,method,getParameter(),getParameterValues()처럼 용어가 한꺼번에 많이 나와서 복잡하게 느껴질 수 있다.
하지만 실제 흐름은 생각보다 단순하다.
브라우저가 값을 보낸다.
서버가 그 값을 읽는다.
필요하면 숫자로 바꾸거나, 여러 개를 묶어서 처리하거나, 한글이 안 깨지게 인코딩을 맞춘다.
즉, 이 구간은 새로운 문법을 따로따로 외우는 것이 아니라 값이 어떻게 이동하는가를 이해하는 구간이다.
여기서는GET과POST차이,form의action과method,getParameter()와getParameterValues(), 그리고POST한글 처리까지 함께 본다.
이 내용들은 따로 외우는 것이 아니라 값이 어떻게 이동하고, 서버가 그것을 어떻게 읽는지라는 한 흐름으로 보면 훨씬 덜 헷갈린다.
브라우저는 어떤 방식으로 값을 보내는가브라우저는 여러 방식으로 서버에 요청을 보낸다.
주소창에 직접 주소를 입력하거나 링크를 클릭하면 보통GET요청이 발생한다.
반면 사용자가 입력한 값을 서버로 보내는form은 설정에 따라GET도 될 수 있고POST도 될 수 있다.
초보자 기준에서는 두 방식을 이렇게 먼저 구분하면 된다.
GET은 주소를 통해 값을 보내는 방식이다.POST는 요청 본문에 값을 담아 보내는 방식이다.즉, 테스트할 때 가장 먼저 보이는 차이는 주소창에 값이 보이는지 여부다.
GET은 주소 뒤에 값이 붙어서 보이고,POST는 주소창에 값이 직접 보이지 않는다.
둘 다 서버에 값을 보내는 방식이지만, 값이 들어가는 위치가 다르다.
그래서 서버 쪽에서도 그 흐름을 이해하고 있어야 한다.
즉,GET과POST차이는 단순히 문법 차이가 아니라 브라우저가 요청을 보내는 방식의 차이라고 보면 된다.
여기서 중요한 점은 둘 다 결국 서버로 값을 보낸다는 사실이다.
즉, “GET은 조회용,POST는 전송용”처럼 너무 단순하게만 외우기보다, 둘 다 값을 보낼 수 있지만 전달 위치가 다르다고 이해하는 것이 더 정확하다.
이 기준이 잡혀야 주소창에서 값이 보이는 경우와 보이지 않는 경우를 자연스럽게 구분할 수 있다.
form은 무엇을 정하는 태그인가사용자 입력을 서버로 보내는 기본 도구가
form태그다.
form은 입력값을 어떤 주소로, 어떤 방식으로 보낼지 정하는 역할을 한다.
즉, 단순히 입력창을 묶어 두는 태그가 아니라 입력값 전송 규칙 전체를 정하는 태그다.
여기서 가장 먼저 봐야 할 속성은 두 가지다.
action은 데이터를 어디로 보낼지 정한다.method는 데이터를 어떤 방식으로 보낼지 정한다.예를 들어
action="/edu/queryget"이면/edu/queryget으로 데이터를 보내고,method="get"이면GET방식으로 보낸다는 뜻이다.
입력값을 서버에 전달할 때는 보통name=value형태가 된다.
그리고 서버는 이때의name을 기준으로 값을 찾는다.
즉, 서버는 입력창의 모양이 아니라 이름(name)을 기준으로 값을 읽는다.
이 점이 중요하다.
같은 입력창처럼 보여도name이 다르면 서버는 다른 값으로 인식한다.
반대로 체크박스처럼 여러 입력 요소가 같은name을 가지면, 서버는 그것을 같은 이름으로 전달된 여러 값으로 본다.
즉,form을 이해할 때는 화면에 보이는 입력창보다 서버로 넘어갈 때 어떤 이름으로 전달되는가를 먼저 봐야 한다.
초보자는 입력창의type이나 모양에 먼저 눈이 가기 쉽지만, 서버 입장에서는 그보다name이 더 중요하다.
왜냐하면 서버는 “텍스트창이었다”, “체크박스였다”보다 무슨 이름으로 어떤 값이 왔는가를 기준으로 값을 꺼내기 때문이다.
Query String은 무엇인가브라우저가 주소 뒤에 값을 붙여 보내는 방식을
Query String, 즉쿼리 문자열이라고 한다.
보통 주소 뒤에?가 붙고, 그 뒤에name=value형식으로 값이 이어진다.
값이 여러 개면&로 연결된다.
예를 들어 아래와 같은 주소가 있다.
?guestName=둘리&num=11이 주소에서는
guestName값이둘리이고,num값이11이라는 뜻이다.
이처럼 브라우저가 이름과 값을 붙여 보내면, 서버는 그 이름을 기준으로 값을 읽는다.
즉, 브라우저가 값을 보내는 규칙과 서버가 값을 찾는 방식은 처음부터 연결되어 있다.
여기서 초보자가 꼭 잡아야 할 점은Query String도 결국 “이름과 값의 묶음”이라는 것이다.
복잡한 문법처럼 보이지만, 사실은 단순히 이름표가 붙은 값들을 주소 뒤에 이어 붙인 것이다.
그래서 주소에guestName=둘리가 보이면, 서버는guestName이라는 이름으로둘리를 찾는다고 이해하면 된다.
서버는 값을 어떻게 읽는가서버에서는
request객체를 사용해서 브라우저가 보낸 값을 읽는다.
브라우저가 값을 보내면 서버는 그 값을 자동으로 꺼내 쓰는 것이 아니라,request안에서 이름을 기준으로 직접 찾아야 한다.
이때 가장 자주 쓰는 메서드가getParameter()와getParameterValues()다.
두 메서드는 이름이 비슷해서 처음에는 같은 기능처럼 보일 수 있다.
하지만 실제로는 읽는 대상이 다르다.
getParameter()는 값 하나를 읽을 때 사용한다.getParameterValues()는 같은 이름으로 전달된 여러 값을 읽을 때 사용한다.예를 들어 이름이나 숫자처럼 하나만 전달되는 값은
getParameter()로 읽으면 된다.
반면 체크박스처럼 여러 값이 함께 전달될 수 있으면getParameterValues()로 읽어야 한다.
여기서 중요한 점은getParameterValues()가 여러 값을 돌려주는 메서드라는 것이다.
즉, 결과도 값 하나가 아니라 여러 값을 담을 수 있는 형태로 넘어온다.
그래서 이 메서드로 읽은 값은String[]같은 배열 형태로 받아서 처리해야 한다.
체크박스를 하나만 선택했더라도, 구조 자체는 여전히 여러 값이 올 수 있는 항목으로 본다.
반대로 체크박스처럼 여러 값이 올 수 있는 항목을getParameter()로 읽으면 전달된 값 전체를 다루기 어렵다.
그래서 여러 선택값을 제대로 처리하려면 처음부터getParameterValues()로 읽는 기준을 잡아야 한다.
즉, 서버는 값을 무조건 같은 방식으로 읽는 것이 아니라, 브라우저가 어떻게 보냈는지에 맞춰 읽는 방식도 달라져야 한다.
값 하나를 보냈다면 하나로 읽고, 같은 이름으로 여러 값을 보냈다면 여러 개로 읽어야 한다.
이 부분이 중요한 이유는, 브라우저에서 값을 보낸 방식과 서버에서 값을 읽는 방식이 서로 맞아야 하기 때문이다.
예를 들어 체크박스 여러 개를 같은 이름으로 보냈는데 서버에서 값 하나만 읽으려고 하면, 전달된 구조를 제대로 반영하지 못한다.
반대로 하나만 오는 값을 배열처럼 읽으려고 하면 필요 이상으로 복잡해질 수 있다.
즉, 보내는 구조와 읽는 구조가 맞아야 한다는 점이 핵심이다.
예제 1. query.html로 링크 요청과 GET, POST 폼 보기아래
HTML은 링크 요청,GET폼,POST폼을 한 화면에 모아 둔 예제다.
즉, 브라우저가 값을 보내는 여러 방식을 한 번에 비교해서 볼 수 있다.<!-- query.html --> <!doctype html> <html> <head> <meta charset="UTF-8" /> <title>Insert title here</title> </head> <body> <h1>링크로 서블릿 요청하기</h1> <a href="/edu/queryget?guestName=둘리&num=11&food=갈비&food=떡볶이">둘리</a> <br /> <a href="/edu/queryget?guestName=또치&num=22">또치</a> <br /> <a href="/edu/queryget?guestname=듀크&num=33&food=쌀국수"> <!-- 듀크만 guestname으로 되어 있다 --> <img src="/edu/images/duke.png" width="100" height="100" /> </a> <hr /> <h1>이름과 좋아하는 숫자 전달하기(GET)</h1> <form method="get" action="/edu/queryget"> <input type="text" name="guestName" placeholder="이름을 입력하세요" required /><br /> <input type="number" name="num" placeholder="좋아하는 숫자를 입력하세요" required /><br /> <input type="checkbox" name="food" value="볶음밥" />볶음밥 <input type="checkbox" name="food" value="탕수육" />탕수육 <input type="checkbox" name="food" value="피자" />피자 <input type="checkbox" name="food" value="파스타" />파스타<br /> <input type="submit" value="요청(GET)" /> </form> <br /> <hr /> <h1>이름과 좋아하는 숫자 전달하기(POST)</h1> <form method="post" action="/edu/querypost"> <input type="text" name="guestName" placeholder="이름을 입력하세요" required /><br /> <input type="number" name="num" placeholder="좋아하는 숫자를 입력하세요" required /><br /> <input type="checkbox" name="food" value="볶음밥" />볶음밥 <input type="checkbox" name="food" value="탕수육" />탕수육 <input type="checkbox" name="food" value="피자" />피자 <input type="checkbox" name="food" value="파스타" />파스타<br /> <input type="submit" value="요청(POST)" /> </form> </body> </html>이 코드에서 먼저 봐야 할 것은 세 가지다.
- 링크는 주소 안에 값을 직접 붙여서 요청한다.
GET폼은 입력값을 주소에 실어 보낸다.POST폼은 입력값을 요청 본문에 담아 보낸다.즉, 겉보기에는 모두 값을 보내는 것처럼 보여도 실제 전달 방식은 서로 다르다.
또food처럼 같은name을 여러 번 사용하면 값도 여러 개 전달될 수 있다.
그래서 서버에서는 이 값을 하나가 아니라 여러 개로 읽어야 한다.
그리고 파라미터 이름은 정확해야 한다.
위 코드의 세 번째 링크를 보면guestName이 아니라guestname으로 되어 있다.
이처럼 이름이 다르면 서버는 같은 값으로 인식하지 못한다.
파라미터 이름은 비슷하면 되는 것이 아니라 정확히 일치해야 한다.
이 부분은 초보자가 특히 많이 놓치는 부분이다.
화면에서는 글자 하나 차이처럼 보이지만, 서버 입장에서는 완전히 다른 이름이다.
즉,guestName과guestname은 사람이 보기에는 비슷해도 서버는 같은 것으로 보지 않는다.
그래서 값을 못 읽거나null이 나올 수 있다.
즉, 서버에서 값이 안 읽힌다면 코드가 복잡해서가 아니라 이름이 다르기 때문일 수도 있다는 점을 먼저 떠올릴 수 있어야 한다.
둘리,또치,듀크링크에 따라 전달되는 파라미터와 결과가 어떻게 달라지는지 확인할 수 있다.
예제 1-1. QueryServlet으로 전달된 값을 읽기이제 서버 쪽에서는 브라우저가 보낸 값을 실제로 읽어야 한다.
아래Servlet은query.html에서 전달된 값을 읽고, 요청 방식과 요청 주소까지 함께 출력하는 예제다.// QueryServlet.java package core; import java.io.IOException; import java.io.PrintWriter; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @WebServlet({"/queryget", "/querypost"}) public class QueryServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 응답 형식과 인코딩을 설정한다 response.setContentType("text/html;charset=utf-8"); // 브라우저에 출력할 도구를 만든다 PrintWriter out = response.getWriter(); // 이름은 하나의 값으로 읽는다 String userName = request.getParameter("guestName"); // 숫자는 문자열로 전달되므로 정수로 변환한다 int number = Integer.parseInt(request.getParameter("num")); // 음식은 여러 값이 올 수 있으므로 배열로 읽는다 String[] food = request.getParameterValues("food"); if (food != null && food.length != 0) { out.print("<h2>좋아하는 음식</h2>"); out.print("<ul>"); for (String f : food) { out.print("<li>" + f + "</li>"); } out.print("</ul>"); } else { out.print("<h2>좋아하는 음식이 없군요</h2>"); } // 요청 방식과 요청 주소를 함께 확인한다 out.print("<h2>요청 방식 : " + request.getMethod() + "</h2>"); out.print("<h2>요청 URI : " + request.getRequestURI() + "</h2>"); // 전달받은 값을 화면에 출력한다 out.print("<h2>당신의 이름은 " + userName + "이군요!</h2>"); out.print("<h2>당신이 좋아하는 숫자는 " + number + "이군요!</h2>"); // 이전 입력 화면으로 돌아가는 링크를 만든다 out.print("<a href='" + request.getHeader("referer") + "'>입력화면으로 가기</a>"); out.close(); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // POST 방식은 값을 읽기 전에 인코딩을 먼저 맞춘다 request.setCharacterEncoding("utf-8"); // 실제 처리 내용은 doGet()으로 넘긴다 doGet(request, response); } }이 코드에서 가장 먼저 봐야 할 부분은
getParameter()와getParameterValues()의 차이다.
guestName,num처럼 값이 하나만 오는 항목은getParameter()로 읽는다.
반면food처럼 여러 값이 함께 올 수 있는 항목은getParameterValues()로 읽는다.
getParameterValues()는 여러 값을 담아 돌려주는 메서드이기 때문에 반환 결과도 배열 형태가 된다.
그래서food처럼 같은 이름으로 값이 여러 개 전달될 수 있는 항목은String[]로 받아야 한다.
또num은 숫자 입력칸에서 보낸 값이지만, 서버가 처음 받는 순간에는 문자열이다.
그래서Integer.parseInt()를 사용해서 문자열을 실제 숫자형으로 바꾸고 있다.
즉, 서버는 브라우저가 보낸 값을 있는 그대로 쓰는 것이 아니라, 필요하면 원하는 형태로 바꿔서 사용해야 한다.
여기서Integer.parseInt()는 문자열을int형 숫자로 바꾸는 메서드다.
예를 들어"11"같은 문자열을 숫자11로 바꿔 주는 역할을 한다.
즉, 브라우저가 문자 형태로 보낸 값을 서버에서 계산이나 비교에 쓸 숫자로 바꾸는 도구라고 이해하면 된다.
이 부분은 초보자에게 꽤 중요하다.
브라우저에서 숫자 입력칸을 썼다고 해서 서버가 처음부터 숫자로 받는 것은 아니다.
브라우저가 서버에 보내는 값은 기본적으로 문자 형태라고 이해하는 편이 쉽다.
그래서 서버 쪽에서는 “이건 숫자로 써야 하는 값이구나”라고 판단해서 직접 변환해 줘야 한다.
즉, 입력창이 숫자처럼 보여도 서버에서는 바로 숫자가 되는 것이 아니다.
그리고request.getMethod()와request.getRequestURI()를 함께 출력하고 있기 때문에, 지금 어떤 요청 방식으로 어떤 주소에 접근했는지도 바로 확인할 수 있다.
즉, 이 예제는 단순히 입력값만 읽는 예제가 아니라 전달된 값과 요청 자체의 정보까지 함께 확인하는 예제다.
마지막의request.getHeader("referer")는 현재 페이지로 오기 직전의 페이지 주소를 읽는 코드다.
여기서는 사용자가 값을 입력했던 화면으로 다시 돌아가는 링크를 만들 때 사용하고 있다.
즉, 브라우저가 지금 오기 전에 어디에 있었는지도 요청 정보 안에서 읽어 올 수 있다는 뜻이다.
즉, 여기서referer는 메서드 이름이 아니라 읽고 싶은Header의 이름이다.
getHeader()라는 같은 메서드를 사용하더라도, 괄호 안에 어떤 이름을 넣느냐에 따라 읽는 정보가 달라진다.
이 예제에서는 직전 페이지 주소가 필요하므로referer를 넣은 것이다.
이 코드에서 특히 중요한 부분은doPost()다.
POST방식에서는request.setCharacterEncoding("utf-8")을 먼저 호출한 뒤 값을 읽어야 한글이 깨지지 않는다.
즉, 값을 먼저 읽고 나중에 인코딩을 맞추는 것이 아니라, 읽기 전에 먼저 인코딩을 설정해야 한다.
이 순서가 중요한 이유는, 값을 한 번 읽고 나면 그 뒤에는 이미 읽는 과정이 시작된 상태이기 때문이다.
그래서 한글 처리 설정은 “나중에 보정하는 작업”이 아니라 읽기 전에 미리 맞춰 두어야 하는 준비 작업이라고 이해하면 된다.
GET은 주소창에 값이 보이고,POST는 요청 본문에 값을 담아 전달된다.
GET방식은 주소창에 값이 붙어서 전달되고, 결과 화면에도 요청 방식이GET으로 표시된다.
POST방식은 주소창에 값이 직접 드러나지 않고, 결과 화면에는 요청 방식이POST로 표시된다.
GET과 POST는 무엇이 다른가두 방식의 차이는 단순히 이름 차이가 아니다.
가장 먼저 봐야 할 차이는 값이 이동하는 위치다.
GET은 값이 주소에 붙는다.POST는 값이 요청 본문에 들어간다.그래서
GET은 주소창만 봐도 어떤 값이 전달되었는지 눈으로 확인할 수 있다.
반면POST는 주소창만 봐서는 전달값이 바로 보이지 않는다.
즉,GET과POST는 단순한 문법 차이가 아니라 값이 이동하는 위치 자체가 다른 방식이라고 이해하면 된다.
여기서 중요한 것은 “어느 쪽이 무조건 더 좋다”가 아니라, 값이 어디에 실려 가는가가 다르다는 점이다.
즉, 지금 보고 있는 예제에서는GET과POST를 우열로 비교하는 것이 아니라, 브라우저에서 값이 이동하는 방식을 비교하는 데 의미가 있다.
이 기준이 잡혀야 주소창 변화, 요청 방식 출력, 한글 처리 같은 뒤 설명도 자연스럽게 연결된다.
예제 2. visitorForm.html과 VisitorServlet으로 POST 한글 처리 보기이번에는 조금 더 단순한
POST예제를 보면 흐름이 더 분명해진다.
아래HTML은 이름과 글 내용을 입력해서 서버로 보내는 입력 화면이다.
action="/edu/visitor"로 보낼 대상을 정하고,method="post"로 전송 방식을 정한다.<!-- visitorForm.html --> <!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Insert title here</title> </head> <body> <h1>글을 남겨주세요</h1> <hr /> <form method="post" action="/edu/visitor"> <input type="text" name="name" placeholder="이름을 입력하세요" /> <br /> <textarea name="content" placeholder="내용을 입력하세요"></textarea> <br /> <button type="submit">등록</button> <button type="reset" value="재작성">재작성</button> </form> </body> </html>이 화면에서 중요한 것은 입력창 모양보다
name이다.
이름 입력칸은name="name"이고, 내용 입력칸은name="content"다.
서버는 바로 이 이름을 기준으로 값을 꺼낸다.
즉, 서버가 값을 읽을 수 있게 해 주는 기준은 입력창의 이름이다.
이 부분은 앞 예제와도 연결된다.
브라우저가 어떤 화면을 보여 주는지는 사용자 입장에서 중요하지만, 서버는 화면을 직접 보는 것이 아니다.
서버는 전달된 이름과 값을 읽는다.
그래서 입력창이 예쁘게 생겼는지보다, 어떤name으로 보내는지가 더 중요하다.
이제 서버 쪽 코드를 보면 흐름이 더 분명해진다.// VisitorServlet.java package lab; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.time.LocalDate; import java.time.format.DateTimeFormatter; @WebServlet("/visitor") public class VisitorServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // POST 방식 한글 처리를 먼저 설정한다 request.setCharacterEncoding("utf-8"); // 응답 형식과 인코딩을 설정한다 response.setContentType("text/html;charset=utf-8"); // 브라우저에 출력할 도구를 만든다 PrintWriter out = response.getWriter(); // 입력 화면에서 보낸 이름과 내용을 읽는다 String userName = request.getParameter("name"); String content = request.getParameter("content"); // 현재 날짜를 보기 좋은 형식으로 만든다 LocalDate currentDate = LocalDate.now(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일"); String formattedDate = currentDate.format(formatter); // 읽어 온 값으로 결과 화면을 만든다 out.print("<h1>" + userName + "님이 " + formattedDate + "에 남긴 글 입니다.</h1><hr />"); out.print("<h2>" + content + "</h2>"); } }이 예제는 구조가 단순해서 핵심이 더 잘 보인다.
먼저request.setCharacterEncoding("utf-8")으로 인코딩을 맞춘다.
그다음request.getParameter("name"),request.getParameter("content")로 값을 읽는다.
마지막으로 읽어 온 값을 이용해서 결과 화면을 만든다.
즉, 이 예제는POST방식에서는 값을 읽기 전에 인코딩을 먼저 설정해야 한다는 점을 가장 직접적으로 보여 준다.
또request에서 읽은 값을response로 바로 화면에 출력한다는 점에서도, 요청과 응답이 하나의 흐름으로 이어진다는 것을 확인할 수 있다.
이 예제는 앞에서 본query예제보다 구조가 단순하기 때문에,POST처리 흐름만 집중해서 보기 좋다.
브라우저가name과content를 보낸다.
서버가 인코딩을 먼저 맞춘다.
그다음 이름과 내용을 읽는다.
마지막으로 읽은 값으로 결과 화면을 만든다.
즉, 값 전송 → 값 읽기 → 화면 응답 흐름이 한눈에 보이는 예제라고 할 수 있다.
방명록 예제는 입력값을
POST로 전송한 뒤 서버가 결과 화면을 만들어 주는 구조를 보여 준다.
이 구간에서 꼭 잡아야 하는 핵심정리하면 이 구간의 핵심은 아래와 같다.
- 브라우저는
GET이나POST방식으로 값을 보낸다.form은 그 값이 어떤 주소로 어떤 방식으로 전달될지 정한다.- 서버는
request객체에서 값을 이름 기준으로 읽는다.- 값이 하나인지 여러 개인지에 따라
getParameter()와getParameterValues()를 구분해서 사용한다.POST방식에서는 값을 읽기 전에 인코딩을 먼저 맞춰야 한다.즉, 이 구간은 따로따로 외울 내용이 아니다.
브라우저에서 값 보내기 → 서버에서 값 읽기 → 필요한 경우 인코딩 맞추기라는 한 흐름으로 이해하면 된다.
이 흐름이 잡혀야 뒤에서 상태 유지와HttpSession도 자연스럽게 이어진다.
2-7. 상태 유지가 필요한 이유와 HttpSession웹은 기본적으로 상태를 오래 붙잡고 있는 구조가 아니다.
한 번의 요청이 끝나면 그다음 요청은 다시 새롭게 시작된다.
그래서 이전 요청의 결과를 다음 요청에서도 이어서 사용하려면, 값을 따로 저장해 둘 방법이 필요하다.
예를 들어 로그인 상태, 장바구니, 방문 횟수처럼 이전 값이 계속 이어져야 하는 기능은 단순한 요청과 응답만으로는 처리하기 어렵다.
이전 요청이 끝나면 그 흐름도 함께 끝나기 때문이다.
즉, 웹에서는 이전 요청을 자동으로 기억해 주지 않기 때문에, 상태를 유지하려면 값을 어디에 어떻게 저장할지 따로 정해 줘야 한다.
왜 상태 유지가 따로 필요한가이 사실은 방문 횟수 예제를 보면 가장 쉽게 이해할 수 있다.
겉으로는 모두 “방문 횟수를 센다”는 같은 기능처럼 보여도, 값을 어디에 저장했는지에 따라 결과가 완전히 달라진다.
메서드 안 지역변수에 저장하면 요청이 끝날 때마다 값이 사라진다.
멤버변수에 저장하면Servlet객체가 살아 있는 동안은 값이 남을 수 있다.
하지만 이 방식은 브라우저마다 상태를 따로 구분해서 관리하는 방법은 아니다.
그래서 브라우저별로 독립적인 상태를 유지하려면HttpSession이 필요하다.
즉, 이 구간의 핵심은 단순히 “값이 남는다, 안 남는다”가 아니다.
왜 지역변수와 멤버변수만으로는 웹 상태 유지를 제대로 해결할 수 없는지를 이해하는 것이 더 중요하다.
예제 1. 지역변수에 저장하면 왜 누적되지 않는가 (CountServlet1)아래 예제는 방문 횟수를 지역변수에 저장한 경우다.
지역변수는doGet()이 실행될 때마다 새로 만들어진다.
그래서 요청이 끝나면 값도 함께 사라진다.// CountServlet1.java package core; import java.io.IOException; import java.io.PrintWriter; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @WebServlet("/count1") public class CountServlet1 extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 응답 형식을 설정한다 response.setContentType("text/html; charset=utf-8"); // 브라우저에 출력할 도구를 만든다 PrintWriter out = response.getWriter(); // 요청마다 새로 만들어지는 지역변수 배열이다 int[] count = new int[1]; count[0]++; // 현재 방문 횟수를 출력한다 out.print("<h1>지역변수</h1><hr>"); out.print("<h3>당신은 " + count[0] + "번째 방문입니다.</h3>"); out.close(); } }이 코드에서 중요한 것은
count가doGet()안에서 선언되었다는 점이다.
즉, 브라우저가 다시 요청하면 이전 배열을 이어서 쓰는 것이 아니라, 새 배열을 다시 만들어서 사용한다.
그래서 방문할 때마다 결과는 계속1부터 다시 시작된다.
지역변수에 저장한 값은 요청이 끝날 때마다 사라져 방문 횟수가 누적되지 않는다.
이 예제는 웹이 이전 요청을 자동으로 기억하지 않는다는 사실을 아주 단순하게 보여 준다.
메서드 안에서 만든 값은 요청이 끝나면 함께 끝난다.
예제 2. 멤버변수에 저장하면 왜 계속 증가하는가 (CountServlet2)이번에는 같은 방문 횟수를 멤버변수에 저장한 예제다.
멤버변수는 메서드 안이 아니라 클래스 안에 선언된다.
그래서Servlet객체가 살아 있는 동안 값이 유지될 수 있다.// CountServlet2.java package core; import java.io.IOException; import java.io.PrintWriter; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @WebServlet("/count2") public class CountServlet2 extends HttpServlet { private static final long serialVersionUID = 1L; int[] count = new int[1]; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 응답 형식을 설정한다 response.setContentType("text/html; charset=utf-8"); // 브라우저에 출력할 도구를 만든다 PrintWriter out = response.getWriter(); // 멤버변수 값을 증가시킨다 count[0]++; // 현재 방문 횟수를 출력한다 out.print("<h1>멤버변수</h1><hr>"); out.print("<h3>당신은 " + count[0] + "번째 방문입니다.</h3>"); out.close(); } }이 코드는 지역변수 예제와 비슷해 보이지만 결과는 다르다.
count가 클래스 안에 있으므로, 이미 만들어진Servlet객체가 다시 사용되는 동안 값이 계속 남을 수 있다.
그래서 요청할 때마다1,2,3처럼 값이 누적된다.
멤버변수에 저장한 값은
Servlet객체가 유지되는 동안 계속 증가한다.
하지만 여기서 바로 상태 유지가 해결됐다고 보면 안 된다.
멤버변수는 객체 수준에서 남는 값일 뿐이고, 브라우저마다 따로 구분되는 값이 아니다.
즉, 사용자별 상태를 안전하게 관리하는 방식으로 보기에는 부족하다.
왜 멤버변수만으로는 부족한가멤버변수는
Servlet객체 안에 저장된다.
그런데Servlet객체는 특정 사용자 한 명만을 위해 따로 만들어지는 구조가 아니다.
이미 만들어진 하나의 객체가 여러 요청을 처리하는 구조에 가깝다.
그래서 멤버변수에 저장한 값은 “내 브라우저의 방문 횟수”라기보다, 그Servlet객체가 처리한 요청 전체 흐름의 값처럼 움직일 수 있다.
즉, 사용자마다 따로 관리해야 하는 로그인 상태나 장바구니 같은 정보는 멤버변수에 저장하는 방식으로 해결하기 어렵다.
웹에서 필요한 상태 유지는 단순히 값이 남는 것만으로는 부족하다. 중요한 것은 그 값이 누구 기준으로 구분되어 저장되느냐이다.
request.getSession()은 무엇인가세션 예제에서 가장 먼저 눈에 들어오는 코드는
HttpSession session = request.getSession();이다.
이 코드는 현재 요청과 연결된 세션 객체를 가져오는 시작점이다.
여기서 중요한 점은session객체를 그냥 만드는 것이 아니라,request를 통해 꺼낸다는 점이다.
즉, 지금 들어온 요청이 어느 브라우저 흐름에서 온 것인지 보고, 그 요청과 연결된 세션을 찾아오는 구조라고 이해하면 된다.
이 메서드는 이미 세션이 있으면 기존 세션을 가져오고, 없으면 새 세션을 만들어서 돌려준다.
그래서 초보자 기준에서는 세션을 찾거나 필요하면 새로 준비해서 돌려주는 메서드라고 이해하면 가장 자연스럽다.
예제 3. HttpSession으로 브라우저별 상태를 유지하기 (CountServlet3)이제
HttpSession을 사용하면 왜 결과가 달라지는지 볼 수 있다.
세션은 브라우저별로 구분되는 서버 저장 공간이다.
그래서 같은 브라우저에서는 값을 이어서 쓸 수 있고, 다른 브라우저나 새 세션에서는 다시 시작할 수 있다.// CountServlet3.java package core; import java.io.IOException; import java.io.PrintWriter; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; @WebServlet("/count3") public class CountServlet3 extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 응답 형식을 설정한다 response.setContentType("text/html; charset=utf-8"); // 브라우저에 출력할 도구를 만든다 PrintWriter out = response.getWriter(); // 현재 요청과 연결된 세션을 가져온다 HttpSession session = request.getSession(); // 세션에 방문 횟수 값이 없으면 처음 생성한다 if (session.getAttribute("cnt") == null) { session.setAttribute("cnt", new int[1]); } // 세션에 저장된 방문 횟수를 꺼내서 증가시킨다 int[] count = (int[]) session.getAttribute("cnt"); count[0]++; // 현재 방문 횟수를 출력한다 out.print("<h1>세션객체</h1><hr>"); out.print("<h3>당신은 " + count[0] + "번째 방문입니다.</h3>"); out.close(); } }이 코드에서 가장 중요한 부분은
request.getSession()이다.
이 메서드는 현재 브라우저와 연결된 세션을 가져온다.
그리고setAttribute()로 값을 저장하고,getAttribute()로 다시 꺼내 쓴다.
즉, 방문 횟수를 메서드 안이나Servlet객체 안에 두는 것이 아니라 세션 안에 저장하는 구조다.
여기서int[] count = (int[]) session.getAttribute("cnt");처럼 앞에(int[])가 붙은 이유도 같이 볼 필요가 있다.
getAttribute()는 저장된 값을 객체 형태로 꺼내 오기 때문에, 다시 배열로 쓰려면 원래 저장했던 형태인int[]로 형변환해야 한다.
즉, 세션에서 값을 꺼냈다고 바로 배열처럼 쓸 수 있는 것이 아니라, 원래 어떤 형태로 넣어 두었는지에 맞춰 다시 꺼내 써야 한다는 뜻이다.
같은 브라우저 증가 흐름과 새 세션 시작 흐름까지 함께 확인할 수 있다.
이 예제에서 꼭 봐야 할 핵심은 두 가지다.
- 같은 브라우저에서는 세션 안 값이 유지되므로 방문 횟수가 계속 증가한다.
- 새 세션이 시작되면 이전 세션 값과 분리되므로 다시 처음부터 시작된다.
즉,
HttpSession의 핵심은 단순히 값이 남는다는 뜻이 아니다.
브라우저별로 구분된 상태를 서버에 저장한다는 점이 가장 중요하다.
HttpSession은 어떤 객체인가
HttpSession은 서버에 상태 정보를 저장하는 객체이다.
브라우저는 세션ID를 통해 자기 세션을 다시 찾고, 서버는 그 세션 안에 저장된 값을 이어서 사용한다.
즉, 세션은 브라우저별로 구분되는 서버 저장 공간이라고 이해하면 쉽다.
세션은 단순히 값을 저장하는 기능만 있는 것이 아니다.
값을 넣고, 꺼내고, 지우는 기능과 함께 현재 세션 상태를 확인하는 기능도 제공한다.
자주 보는 메서드는 아래와 같다.
getSession(): 현재 요청과 연결된 세션을 가져온다.setAttribute(): 세션에 값을 저장한다.getAttribute(): 세션에 저장된 값을 꺼낸다.removeAttribute(): 세션에 저장된 특정 값을 삭제한다.invalidate(): 현재 세션 자체를 무효화한다.즉, 세션은 단순한 메모장이 아니라 상태를 저장하고 관리하는 객체라고 보면 된다.
또 세션 객체에서는 세션ID, 세션 생성 시각, 마지막 접근 시간, 새 세션인지 여부 같은 정보도 확인할 수 있다.
즉, 세션은 상태를 담는 역할과 현재 상태를 설명하는 역할을 함께 가진다.
세션 정보는 서버에 저장되고, 브라우저는 세션
ID를 통해 자기 세션을 다시 찾는다.
예제 4. SessionTestServlet로 세션 정보 확인하기아래 예제는 세션에 값을 저장하는 기능보다, 세션 객체가 어떤 정보를 가지고 있고 어떤 방식으로 생성되고 삭제되는지를 확인하는 예제다.
comm=view로 요청하면 현재 세션 정보를 보여 주고,comm=delete로 요청하면 현재 세션을 무효화한다.
즉, 이 예제는HttpSession이 단순한 저장 공간이 아니라 생성, 조회, 삭제까지 관리되는 객체라는 점을 보여 준다.// SessionTestServlet.java package core; import java.io.IOException; import java.io.PrintWriter; import java.util.Date; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; @WebServlet("/sessiontest") public class SessionTestServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 응답 형식을 설정한다 response.setContentType("text/html; charset=utf-8"); // 브라우저에 출력할 도구를 만든다 PrintWriter out = response.getWriter(); // Query 문자열로 전달된 명령을 읽는다 String command = request.getParameter("comm"); // 현재 세션을 가져온다 HttpSession session = request.getSession(); String msg = ""; long time = session.getCreationTime(); String id = session.getId(); if (command != null && command.equals("view")) { // 새 세션인지 기존 세션인지 구분한다 if (session.isNew()) { msg = "세션 객체 생성 : "; } else { msg = "세션 객체 추출 : "; } // 세션 id와 생성 시간을 문자열로 만든다 msg += "<br>id : " + id + " <br>time : " + new Date(time); } else if (command != null && command.equals("delete")) { // 현재 세션을 무효화한다 session.invalidate(); msg = id + "을 id로 갖는 세션 객체 삭제!!"; } else { // 올바른 요청 방법을 안내한다 msg = "요청시 Query 문자열로 comm=view 또는 comm=delete 를 전달해주세요!!"; } // 결과 메시지를 브라우저에 출력한다 out.print("<h2>" + msg + "</h2>"); out.close(); } }이 코드에서 먼저 봐야 할 부분은
getId(),getCreationTime(),isNew(),invalidate()다.
이 메서드들을 보면 세션은 단순히 값을 저장하는 상자가 아니라, 현재 세션 상태를 설명하고 필요할 때 끝낼 수도 있는 관리 대상이라는 점이 분명하게 보인다.
특히comm=view에서는 지금 세션이 새로 만들어진 세션인지, 이미 존재하던 세션인지에 따라 출력 문구가 달라진다.
그리고comm=delete에서는invalidate()를 호출해서 현재 세션 자체를 없앤다.
즉, 이 예제는 같은 브라우저에서 세션을 다시 꺼내 쓰는 흐름과 세션을 삭제한 뒤 새로 시작하는 흐름을 함께 보여 준다.
comm값을 주지 않으면 사용 방법 안내 문구가 출력된다.
처음
comm=view로 요청하면 새 세션이 만들어지고 세션id와 생성 시간이 함께 출력된다.
같은 브라우저에서 다시
comm=view로 요청하면 기존 세션을 다시 추출하므로 같은id가 그대로 유지된다.
comm=delete로 요청하면 현재 세션이 무효화되고 해당id를 가진 세션 객체가 삭제된다.
세션 삭제 후 다시
comm=view로 요청하면 새로운 세션이 다시 생성되고 이전과 다른 새id가 출력된다.
이 결과 흐름을 보면 같은 브라우저에서는 기존 세션을 계속 이어서 사용할 수 있지만, 세션을 무효화하면 그 연결이 끊어진다는 점이 보인다.
즉, 세션은 한 번 만들어 두면 영원히 남는 값이 아니라, 필요할 때 다시 만들 수도 있고 삭제할 수도 있는 상태 관리 객체다.
이 구간에서 꼭 잡아야 하는 핵심정리하면 이 구간의 핵심은 아래와 같다.
- 웹은 기본적으로 이전 요청을 자동으로 기억하지 못한다.
- 지역변수는 요청이 끝나면 사라진다.
- 멤버변수는 객체가 유지되는 동안 남을 수 있지만, 사용자별 상태를 안전하게 구분하는 방법은 아니다.
HttpSession은 브라우저별로 구분된 상태를 서버에 저장할 수 있게 해 준다.- 세션은 생성, 조회, 삭제까지 관리되는 객체이다.
즉, 상태 유지는 단순히 값을 남기는 문제가 아니다.
누구의 상태를, 어디에, 얼마나 유지할 것인가를 결정하는 문제라고 이해해야 한다.
이 흐름이 잡히면 뒤에서scope를 배울 때도 훨씬 자연스럽게 이어진다.
2-8. redirect, forward, include
Servlet이 요청을 처리하다 보면, 지금 받은 요청을 다른 자원으로 넘기거나 다른 자원의 결과를 함께 사용해야 하는 경우가 있다.
여기서자원이라는 말은 서버 안에서 요청을 받아 처리하거나 브라우저에게 보여 줄 수 있는 대상을 뜻한다.
예를 들어HTML파일,JSP, 다른Servlet같은 것들이 모두 자원이 될 수 있다.
즉, 지금 처리하던 일을 넘겨받을 수 있는 대상, 또는 결과를 만들어 줄 수 있는 대상이라고 보면 된다.
이때 자주 나오는 방식이redirect,forward,include다.
셋 다 다른 자원을 사용한다는 점에서는 비슷해 보인다.
하지만 실제 동작 방식은 서로 다르다.
이 구간에서는 단순히 결과 화면만 보면 안 된다.
누가 다시 요청하는지, 주소창이 바뀌는지, 기존 요청 객체를 계속 쓰는지를 함께 봐야 한다.
redirect는 브라우저가 다시 요청하고,forward는 서버 내부에서 요청을 넘긴다.
이 한 줄이 이 구간의 기준이다.
redirect는 어떻게 동작하는가
redirect는 서버가 브라우저에게 다른 주소로 다시 요청하라고 알려 주는 방식이다.
즉, 서버가 현재 요청을 내부에서 이어서 넘기는 것이 아니라, 브라우저가 새 주소로 한 번 더 움직이게 만든다.
흐름을 순서대로 보면 이렇다.
- 브라우저가 처음 주소로 요청한다.
- 서버가 요청을 받는다.
- 서버가 브라우저에게 다른 주소로 다시 가라고 응답한다.
- 브라우저가 새 주소로 다시 요청한다.
그래서
redirect에서는 주소창이 바뀐다.
또 새로운 요청이 다시 시작되기 때문에, 이전request객체를 그대로 이어서 쓰는 구조가 아니다.
즉, 이전 요청은 끝나고 새 요청이 다시 시작된다고 이해해야 한다.
쉽게 말하면redirect는 브라우저를 다시 움직이게 하는 방식이다.
화면 이동처럼 보이지만, 실제로는 브라우저가 새 주소로 요청을 한 번 더 보내는 구조다.
response.sendRedirect()는 무엇인가
response.sendRedirect()는redirect를 실행하는 메서드다.
이 메서드는response앞에 붙는다.
왜냐하면redirect는 서버가 브라우저에게 “다른 주소로 다시 요청하라”는 응답을 보내는 동작이기 때문이다.
예를 들어 아래 코드가 실행되면 브라우저는/edu/first.html로 다시 요청한다.
Servlet이first.html을 직접 실행해서 내부에서 넘기는 것이 아니다.
브라우저에게 새 주소를 알려 주고, 브라우저가 그 주소로 다시 움직이게 만든다.// RedirectServlet.java response.sendRedirect("/edu/first.html"); // 브라우저가 이 주소로 다시 요청한다이 코드에서
/edu/first.html은 브라우저가 다시 요청할 최종 주소다.
그래서 실행 후 주소창도/edu/first.html쪽으로 바뀐다.
sendRedirect()는 현재 요청을 이어서 넘기는 메서드가 아니라, 브라우저에게 새 요청을 다시 보내게 만드는 메서드이다.
이 차이를 잡아야 뒤에서forward()와 헷갈리지 않는다.
예제 1. RedirectServlet으로 redirect 확인아래 코드는
sendRedirect()를 사용해서 브라우저를/edu/first.html로 다시 보내는 예제다.// RedirectServlet.java package core; import java.io.IOException; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @WebServlet("/redirect") public class RedirectServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // redirect 동작 확인용 로그 System.out.println("RedirectServlet 수행"); // 브라우저가 이 주소로 다시 요청하게 만든다 response.sendRedirect("/edu/first.html"); //response.sendRedirect("http://www.naver.com/"); } }이 코드에서 핵심은
response.sendRedirect("/edu/first.html")한 줄이다.
이 메서드를 호출하면 서버는 브라우저에게/edu/first.html로 다시 요청하라는 응답을 보낸다.
즉, 현재 요청을 이어서 넘기는 것이 아니라 브라우저가 새 요청을 다시 시작하게 만드는 구조다.
여기서/edu/first.html도 하나의 자원이다.
브라우저는 처음에는RedirectServlet을 요청했지만,redirect이후에는 다시/edu/first.html이라는 다른 자원을 요청한다.
결국 보여지는 화면은first.html의 결과이지만, 그 결과를 받기까지 브라우저가 한 번 더 요청했다는 점이 핵심이다.
그래서redirect를 확인할 때는 화면 내용만 보면 안 된다.
주소창이 바뀌는지까지 같이 봐야 한다.
주소창까지 바뀌었다면, 브라우저가 실제로 새 주소로 다시 요청했다는 뜻이다.
redirect는 브라우저가 다시 요청하고,forward는 서버 내부에서 요청을 넘긴다.
redirect는 브라우저가 새로운 주소로 다시 요청하므로 주소창도 함께 바뀐다.
이 예제에서 꼭 기억해야 할 점은 두 가지다.
- 브라우저가 새 주소로 다시 움직인다.
- 그 과정에서 새 요청이 다시 만들어진다.
즉,
redirect는 브라우저 중심으로 다시 시작되는 이동 방식이다.
forward는 어떻게 동작하는가
forward는 서버 내부에서 요청을 다른 자원으로 넘기는 방식이다.
브라우저는 한 번만 요청했고, 서버 안에서 처리 흐름만 바뀐다.
그래서 주소창은 그대로 유지된다.
흐름을 순서대로 보면 이렇다.
- 브라우저가 처음 주소로 요청한다.
- 서버가 요청을 받는다.
- 서버 내부에서 다른 자원에게 요청 처리를 넘긴다.
- 넘겨받은 자원이 같은
request,response를 사용해서 결과를 만든다.즉, 브라우저는 다시 움직이지 않는다.
서버 안에서 담당자만 바뀌는 구조다.
이 방식에서는 기존 요청 객체를 이어서 사용할 수 있다.
현재Servlet이 받고 있던request와response를 다른 자원으로 그대로 넘기는 구조이기 때문이다.
그래서request.setAttribute()로 저장한 값도forward된 자원에서 꺼내 쓸 수 있다.
쉽게 말하면forward는 서버가 내부에서 조용히 다음 자원에게 일을 넘기는 방식이다.
사용자 입장에서는 주소창이 그대로인데 화면 내용만 바뀐 것처럼 보일 수 있다.
request.getRequestDispatcher()는 무엇인가
request.getRequestDispatcher()는 현재 요청을 넘길 대상 자원을 준비하는 메서드다.
이 메서드는request앞에 붙는다.
왜냐하면forward는 현재 요청을 기준으로 서버 내부에서 다른 자원에게 넘기는 작업이기 때문이다.
예를 들어 아래 코드는 현재 요청을/first.html로 넘길 준비를 한다.// ForwardServlet.java RequestDispatcher rd = request.getRequestDispatcher("/first.html"); // 넘길 대상 자원을 준비한다이 코드가 실행됐다고 해서 아직 실제로 이동한 것은 아니다.
여기서는/first.html이라는 대상을 담당할RequestDispatcher객체를 얻어 온 것뿐이다.
RequestDispatcher는 요청을 다른 자원으로 전달하거나, 다른 자원의 결과를 현재 응답에 포함할 때 사용하는 도구다.
즉,getRequestDispatcher()는 대상을 정하고 전달 도구를 준비하는 단계라고 보면 된다.
forward()는 무엇인가
forward()는RequestDispatcher가 준비한 대상 자원에게 실제로 요청을 넘기는 메서드다.
앞에서getRequestDispatcher()로 대상을 준비했다면,forward()는 그 대상에게 현재request와response를 넘겨서 처리하게 한다.
예를 들어 아래 코드는 현재 요청과 응답 객체를 그대로/first.html쪽으로 넘긴다.// ForwardServlet.java rd.forward(request, response); // 현재 request와 response를 그대로 넘긴다여기서 중요한 점은 괄호 안에
request와response를 그대로 넣는다는 것이다.
즉, 새 요청을 만드는 것이 아니라 현재 요청 처리에 사용 중인 객체를 다음 자원에게 넘긴다.
그래서forward()는sendRedirect()와 다르다.
sendRedirect()는 브라우저에게 새 요청을 다시 보내게 만든다.
반면forward()는 서버 내부에서 현재 요청을 그대로 넘긴다.
getRequestDispatcher()는 넘길 대상을 준비하고,forward()는 그 대상에게 현재 요청과 응답을 실제로 넘긴다.
예제 2. ForwardServlet으로 forward 확인아래 코드는
RequestDispatcher를 사용해서 현재 요청을/first.html로 넘기는 예제다.// ForwardServlet.java package core; import java.io.IOException; import jakarta.servlet.RequestDispatcher; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @WebServlet("/forward") public class ForwardServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // forward 동작 확인용 로그 System.out.println("ForwardServlet 수행"); // 현재 요청을 넘길 대상 자원을 준비한다 RequestDispatcher rd = request.getRequestDispatcher("/first.html"); /*RequestDispatcher rd = request.getRequestDispatcher("http://www.naver.com/");*/ // 현재 request, response를 그대로 넘긴다 rd.forward(request, response); } }이 코드에서 핵심은 두 단계다.
먼저request.getRequestDispatcher("/first.html")로 현재 요청을 넘길 대상 자원을 준비한다.
그다음rd.forward(request, response)로 현재request와response를 그대로 넘긴다.
여기서도/first.html은 결과를 만들어 줄 자원이다.
하지만redirect와 달리 브라우저가/first.html을 다시 요청하는 것은 아니다.
처음 요청은/forward로 들어왔고, 그 요청을 서버가 내부에서/first.html로 넘겨서 처리하게 한다.
즉, 최종 결과를 만드는 자원은 바뀌었지만, 요청을 다시 시작한 것은 아니다.
그래서forward에서는 주소창이 바뀌지 않는다.
브라우저는 처음 요청한 주소만 알고 있고, 그 뒤의 이동은 서버 내부에서만 일어난다.
즉,forward를 확인할 때는 화면 내용뿐 아니라 주소창이 그대로인지를 함께 봐야 한다.
forward는 서버 내부에서 요청을 넘기므로 화면 내용이 바뀌어도 주소창은 그대로 유지된다.
이 예제에서 중요한 점은forward가 같은 요청 안에서 이루어진다는 것이다.
즉, 새 요청을 다시 만드는 것이 아니라 기존 요청 흐름을 다른 자원으로 넘긴다.
그래서request에 담긴 값도 같은 흐름 안에서 계속 이어질 수 있다.
sendRedirect()와 forward()를 비교하면
sendRedirect()와forward()는 둘 다 다른 자원으로 흐름을 넘기는 것처럼 보인다.
하지만 실제 기준은 완전히 다르다.
sendRedirect()는response로 브라우저에게 새 주소를 알려 준다.forward()는 서버 내부에서 현재request,response를 다른 자원에게 넘긴다.sendRedirect()는 주소창이 바뀐다.forward()는 주소창이 그대로다.sendRedirect()는 새 요청이 시작된다.forward()는 기존 요청 객체를 그대로 사용한다.즉,
sendRedirect()는 브라우저가 다시 요청하게 만드는 메서드이고,forward()는 서버 안에서 현재 요청을 넘기는 메서드이다.
이 차이를 기준으로 보면 두 메서드가 훨씬 명확하게 구분된다.
include는 어떻게 이해하면 되는가
include는 다른 자원의 결과를 현재 응답 안에 일부 포함시키는 방식이다.
즉,forward처럼 통째로 넘기는 것이 아니라, 기존 결과 안에 다른 결과를 덧붙인다고 이해하면 쉽다.
다른Servlet,JSP,HTML처럼 결과를 만들 수 있는 대상을 불러와서 그 결과의 일부를 현재 응답 안에 넣는다.
즉, 현재 화면 전체를 다른 자원에게 맡기는 것이 아니라 필요한 부분만 가져다 붙이는 방식이다.
그래서forward는 전체 흐름을 넘기는 느낌이고,include는 필요한 결과 조각을 끼워 넣는 느낌이다.
예를 들어 공통 메뉴, 공통 하단 영역, 일부 안내 문구처럼 다른 자원의 결과를 현재 화면 안에 함께 넣고 싶을 때 떠올리면 이해하기 쉽다.
이 단계에서는include를 문법으로 외우기보다 현재 응답 안에 다른 자원의 결과를 포함하는 방식이라고 먼저 이해하면 충분하다.
세 가지를 어떻게 구분하면 되는가정리하면
redirect,forward,include는 아래 기준으로 구분하면 된다.
redirect는 브라우저가 다시 요청한다.forward는 서버 내부에서 요청을 넘긴다.include는 다른 자원의 결과 일부를 현재 응답 안에 포함시킨다.여기서 더 구체적으로 보면 이렇게 정리할 수 있다.
redirect는 주소창이 바뀌고 새 요청이 시작된다.forward는 주소창이 그대로이고 같은 요청 객체를 이어서 사용한다.include는 현재 응답 흐름은 유지한 채 필요한 결과만 추가한다.
이 세 가지를 헷갈리지 않으려면 “다른 자원을 사용한다”는 공통점보다 어떤 방식으로 사용하느냐를 봐야 한다.
다시 요청하게 만드는지, 내부에서 넘기는지, 결과 일부만 가져오는지 이 기준으로 보면 정리가 쉽다.
이 구간에서 꼭 잡아야 하는 핵심이 구간에서는 화면만 보고 판단하면 헷갈리기 쉽다.
그래서 아래 기준으로 보면 된다.
- 누가 다시 요청하는가
- 주소창이 바뀌는가
- 기존
request를 그대로 쓰는가즉, 결과 화면보다 처리 흐름을 봐야 한다.
redirect는 브라우저가 다시 요청하고,forward는 서버 안에서 요청을 넘기며,include는 현재 응답 안에 다른 결과를 끼워 넣는다.
이 기준이 잡히면 뒤에서scope,request값 공유,JSP연결 흐름도 훨씬 쉽게 이어진다.
2-9. 객체 공유와 scope
scope는 객체가 어디까지 보이고, 언제까지 살아 있는지를 나타내는 기준이다.
웹에서는 값을 저장했다고 해서 그 값이 계속 남아 있는 것이 아니다.
어느 위치에 저장했는지에 따라 공유 범위와 유지 시간이 달라진다.
그래서scope를 이해할 때 가장 먼저 봐야 하는 것은 값 자체가 아니라 저장 위치다.
같은setAttribute()를 사용해도request에 저장했는지,session에 저장했는지,application에 저장했는지에 따라 전혀 다른 범위에서 동작한다.
초보자가 여기서 헷갈리는 이유는 저장하는 코드 모양이 비슷하기 때문이다.
하지만 실제로는 어디에 저장했는가가 곧 객체의 사용 범위를 결정한다.
이 기준만 분명하게 잡으면scope는 훨씬 쉽게 이해할 수 있다.
setAttribute(), getAttribute(), removeAttribute()는 무엇인가
scope를 볼 때 자주 만나는 메서드가setAttribute(),getAttribute(),removeAttribute()다.
이 세 메서드는 서로 따로 외우기보다, 저장하고 꺼내고 지우는 한 세트로 보면 이해가 쉽다.
setAttribute()는 객체를 저장하는 메서드다.
예를 들어request.setAttribute()라고 쓰면 현재 요청 범위에 값을 넣는다는 뜻이고,session.setAttribute()라고 쓰면 세션 범위에 값을 넣는다는 뜻이다.
즉, 메서드 이름은 같아도 어떤 객체 앞에 붙였는지에 따라 저장 위치가 달라진다.
getAttribute()는 저장해 둔 객체를 다시 꺼내는 메서드다.
즉, 앞에서 넣어 둔 값을 나중에 다시 읽어 와서 화면에 출력하거나 다음 처리에 사용할 때 쓴다.
removeAttribute()는 저장해 둔 특정 객체를 삭제하는 메서드다.
즉, 더 이상 필요 없는 값을 해당 범위에서 지울 때 사용한다.
정리하면setAttribute()는 저장,getAttribute()는 꺼내기,removeAttribute()는 삭제다.
그리고 이 세 메서드는request,session,application같은 저장 공간 객체와 함께 쓰인다는 점을 같이 기억하면 된다.
scope는 무엇을 뜻하는가웹 프로그램은 요청이 들어올 때마다 새로운 흐름으로 움직인다.
이때 어떤 값은 지금 처리 중인 요청 안에서만 잠깐 필요하다.
어떤 값은 같은 세션 안에서 다음 요청까지 이어서 필요하다.
또 어떤 값은 웹 애플리케이션 전체가 함께 봐야 할 수도 있다.
scope는 바로 이런 차이를 구분하는 개념이다.
쉽게 말하면 이 객체를 누가 함께 쓸 수 있는가, 그리고 언제까지 유지되는가를 설명하는 기준이다.
즉,scope는 값을 저장하는 범위를 정하는 개념이다.
예를 들어 조회 결과처럼 한 번 보여 주고 끝날 값은 짧게 살아 있으면 된다.
반대로 로그인 상태처럼 여러 요청에 걸쳐 계속 필요하면 더 오래 유지되어야 한다.
또 공통 설정값처럼 모두가 함께 봐야 하는 데이터는 공용으로 관리해야 한다.
이처럼 데이터 성격이 다르기 때문에 저장 범위도 구분해서 사용해야 한다.
저장 위치가 왜 중요한가웹에서는 데이터 자체만 보는 것이 아니라, 그 데이터를 어디에 담아 두는지도 같이 봐야 한다.
값을 저장하는 위치에 따라 공유 범위와 유지 시간이 달라지기 때문이다.
객체를 담아 두는 공간으로는HttpServletRequest,HttpSession,ServletContext같은 것이 있다.
어디에 저장하느냐에 따라 한 요청 안에서만 쓸 수도 있고, 같은 세션 동안 계속 유지할 수도 있고, 현재 웹 애플리케이션 전체가 함께 쓸 수도 있다.
즉, 같은 값을 저장하더라도 저장 위치가 다르면 동작 방식도 달라진다.
그래서scope를 이해할 때는 값 자체보다 어디에 저장했는지를 먼저 보는 것이 중요하다.
저장 위치가 곧 사용 범위와 유지 시간을 결정한다.
scope를 큰 그림으로 보면
scope는 보통page,request,session,application으로 나눈다.
다만 지금 흐름에서는request,session,application세 가지를 먼저 정확히 잡는 것이 더 중요하다.
page scope는JSP페이지가 처리되는 동안만 쓰이는 범위라서,JSP를 배울 때 같이 이해하면 된다.
지금 단계에서 먼저 기억해야 할 것은 아래 세 가지다.
request는 한 번의 요청 동안만 살아 있는 범위다.session은 같은 브라우저 세션이 유지되는 동안 살아 있는 범위다.application은 현재 웹 애플리케이션이 실행되는 동안 전체에서 공유되는 범위다.즉, 범위는 점점 넓어진다.
request보다session이 더 넓고,session보다application이 더 넓다.
이 흐름으로 보면 각각의 차이를 훨씬 쉽게 구분할 수 있다.
정리하면 범위는request<session<application순서로 점점 넓어진다.
scope는page→request→session→application순서로 범위가 넓어진다.
request scope
request scope는 한 번의 요청이 처리되는 동안만 유효한 범위다.
브라우저가 서버에 요청을 보내고, 서버가 응답을 마칠 때까지 잠깐 살아 있는 범위라고 보면 된다.
응답이 끝나면 그request객체도 역할을 마치므로, 여기에 저장한 값도 함께 끝난다.
그래서request scope는 현재 요청 안에서만 잠깐 필요한 데이터를 담을 때 적합하다.
예를 들어 조회 결과, 계산 결과, 화면에 한 번만 보여 줄 메시지처럼 지금 처리 중인 흐름 안에서만 필요한 값이 여기에 잘 맞는다.
다음 요청까지 계속 들고 갈 필요가 없는 데이터라고 보면 된다.
여기서 중요한 점은request scope가 사용자 기준이 아니라 요청 기준이라는 것이다.
같은 브라우저에서 다시 버튼을 눌러도 그것은 새로운 요청이다.
즉, 같은 브라우저에서 보낸 요청이라도 이전request와 다음request는 서로 다른 객체다.
쉽게 말하면request scope는 이번 한 번만 쓰는 임시 메모에 가깝다.
지금 요청이 끝나면 그 메모도 함께 사라진다.
request scope와forward
request scope를 이해할 때 가장 중요한 연결점은forward다.
forward는 같은 요청을 다른 자원으로 넘기는 방식이다.
즉, 요청이 새로 만들어지는 것이 아니라 지금 쓰고 있던 요청 객체를 그대로 이어서 사용한다.
그래서 앞에서request.setAttribute()로 저장한 값을 뒤쪽 자원에서도 꺼낼 수 있다.
여기서 같이 기억해야 할 점이 있다.
forward는 아무 곳으로나 보내는 방식이 아니다.
같은 웹 애플리케이션 안에서 요청을 넘길 때 사용하는 방식이다.
반대로redirect는 새 요청이 시작되는 방식이다.
기존 요청은 끝나고 브라우저가 다시 요청을 보내기 때문에 새로운request객체가 만들어진다.
그래서 이전request에 저장한 값은 이어지지 않는다.
forward는 같은 요청이라서request scope가 유지되고,redirect는 새 요청이라서request scope가 끊어진다.
request scope는 하나의 요청이 끝날 때까지만 유지되고, 같은 요청 안에서forward되는 자원끼리 공유된다.
session scope
session scope는 같은 브라우저 세션이 유지되는 동안 유효한 범위다.
즉, 한 번의 요청이 끝나도 사라지지 않고, 같은 브라우저 세션에서 이어지는 다음 요청에서도 계속 사용할 수 있다.
그래서request scope보다 더 오래 유지된다.
이 범위는 세션별 상태를 관리할 때 특히 중요하다.
예를 들어 로그인 정보, 장바구니, 세션별 임시 설정처럼 같은 세션에서만 계속 이어져야 하는 데이터를 저장할 때 적합하다.
이 값들은 한 번의 요청으로 끝나는 데이터가 아니라, 여러 요청에 걸쳐 계속 필요하기 때문이다.
여기서 중요한 점은session이 서버 전체 공용 공간이 아니라는 것이다.
각 브라우저 세션마다 따로 세션이 만들어진다.
즉, 브라우저A의session에 저장한 값과 브라우저B의session에 저장한 값은 서로 다르다.
그래서 세션별 데이터를 관리하기에 알맞다.
즉,session은 사용자 또는 브라우저별로 구분해서 값을 저장하는 서버 쪽 저장 공간이라고 이해하면 된다.
다만session이 무조건 계속 유지되는 것은 아니다.
로그아웃처럼 세션을 지우는 처리를 할 수도 있고, 일정 시간 동안 요청이 없으면 자동으로 끝날 수도 있다.
즉,session scope는request보다 길지만, 영원히 유지되는 범위는 아니다.
session scope는 같은 브라우저 세션이 유지되는 동안 계속 사용할 수 있고, 세션마다 따로 관리된다.
application scope
application scope는 현재 웹 애플리케이션이 실행되는 동안 유지되는 가장 넓은 범위다.
이 범위에 저장한 값은 특정 요청 하나의 값도 아니고, 특정 사용자 한 명만의 값도 아니다.
현재 웹 애플리케이션 안에서 동작하는 모든 요청과 모든 사용자가 함께 보는 공용 데이터다.
그래서 공통 설정값, 전체 방문자 수, 전체 사용자에게 공통으로 보여 줄 기준 데이터처럼 웹 애플리케이션 전체가 함께 참고해야 하는 값이 여기에 어울린다.
반대로 사용자마다 달라져야 하는 값을 여기에 저장하면 안 된다.
왜냐하면 모든 사용자가 같은 값을 함께 보게 되기 때문이다.
즉,application scope는 범위가 가장 넓은 대신, 그만큼 더 조심해서 사용해야 하는 저장 위치다.
쉽게 말하면 현재 웹 애플리케이션 전체가 함께 쓰는 공용 게시판이라고 보면 된다.
application scope는 현재 웹 애플리케이션 전체에서 모든 요청과 모든 사용자가 함께 공유하는 범위다.
이 예제를 흐름으로 보면이 예제의 핵심은
Servlet이 데이터를 준비해서 각 범위에 저장하고,JSP는 그 값을 꺼내서 화면에 보여 준다는 점이다.
즉, 둘이 같은 일을 하는 것이 아니라 역할이 나뉘어 있다.
Servlet은 처리와 전달을 맡고,JSP는 표현을 맡는다.
흐름은 단순하다.
Servlet이 값을 만든다.- 값을 알맞은
scope에 저장한다.forward를 사용해서JSP로 넘긴다.JSP는 전달받은 값을 꺼내서 화면에 출력한다.즉, 이 예제는
scope이름만 외우는 예제가 아니라 저장 위치와 화면 출력이 어떻게 연결되는지를 보여 주는 예제다.
제공된 Servlet 코드에서 실제로 하는 일
ShareObjectServlet에서는 세 가지 범위에 각각 값을 저장한 뒤shareexam.jsp로 넘긴다.
코드를 보면 역할이 더 분명하게 보인다.
여기서getServletContext()도 같이 봐야 한다.
이 메서드는 현재 웹 애플리케이션 전체를 나타내는ServletContext객체를 가져온다.
즉,application scope에 값을 저장할 때는 막연히 "전체 범위"라고만 생각하는 것이 아니라, 그 전체 범위를 대표하는 객체를 먼저 가져와서 그 안에 저장한다고 이해하면 된다.// ShareObjectServlet.java package core; import java.io.IOException; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @WebServlet("/share") public class ShareObjectServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setAttribute("myRequest", "요청동안 공유하는 객체"); // 요청 범위에 저장 request.getSession().setAttribute("mySession", "세션객체가 유지되는 동안 공유하는 객체"); // 세션 범위에 저장 getServletContext().setAttribute("myApp", "서버가 기동되는 동안 공유하는 객체"); // 애플리케이션 범위에 저장 request.getRequestDispatcher("/jspexam/shareexam.jsp").forward(request, response); // 같은 요청으로 JSP에 전달 } }이 코드에서 중요한 부분은
setAttribute()가 세 번 나온다는 점이 아니다.
어느 객체에 저장했는가가 핵심이다.
request에 저장한 값은 이번 요청 동안만 살아 있고,session에 저장한 값은 같은 브라우저 세션 동안 살아 있고,application에 저장한 값은 현재 웹 애플리케이션 전체에서 살아 있다.
JSP는 여기서 어떤 역할을 하는가이 예제에서
JSP는 새로운 값을 만드는 쪽보다,Servlet이 전달한 값을 꺼내서 화면에 보여 주는 역할에 가깝다.
즉,Servlet이 준비한 데이터를 브라우저가 볼 수 있는HTML형태로 표현하는 화면 담당이라고 이해하면 된다.
그래서 이 예제에서 역할을 나누면 이렇게 보면 된다.
Servlet은 값을 저장하고 넘긴다.JSP는 그 값을 꺼내서 화면에 보여 준다.표현식 태그는
<%= %>처럼 작성하고, 안에 적은 식의 결과를 바로 화면에 출력한다.
즉, 이 예제에서는JSP가 복잡한 계산을 하는 것이 아니라, 이미 저장된 값을 표현식 태그로 꺼내서 보여 주는 역할을 한다.// shareexam.jsp <ul> <li>요청 스코프 : <%= request.getAttribute("myRequest") %></li> <!-- request에 저장된 값 출력 --> <li>세션 스코프 : <%= session.getAttribute("mySession") %></li> <!-- session에 저장된 값 출력 --> <li>애플리케이션 스코프 : <%= application.getAttribute("myApp") %></li> <!-- application에 저장된 값 출력 --> </ul>
Servlet이request,session,application에 저장한 값을JSP가 표현식 태그로 꺼내서 화면에 출력한 결과다.
이 코드는 앞에서Servlet이 저장한 값을,JSP가 각 내장 객체를 통해 꺼내서 바로 화면에 출력하는 코드다.
즉, 이 예제는 저장된 데이터를 표현식 태그로 보여 주는 예제라고 이해하면 된다.
실행 결과에서 request scope가 null로 나온 이유실행 화면을 보면
세션 스코프와애플리케이션 스코프는 값이 보이는데,요청 스코프는null로 나올 수 있다.
이 부분이 가장 중요하다.
이 결과는 오히려scope차이를 더 분명하게 보여 준다.
핵심은 어떤 주소로 들어왔는가다.
/share로 요청하면ShareObjectServlet이 먼저 실행된다.
이때request.setAttribute("myRequest", ...)가 실행되고, 그 상태로forward되어shareexam.jsp가 열리면request.getAttribute("myRequest")로 값을 꺼낼 수 있다.
하지만shareexam.jsp를 직접 요청하면 상황이 달라진다.
이 경우에는ShareObjectServlet이 먼저 실행되지 않는다.
즉, 현재 요청에는myRequest를 넣어 주는 과정이 없기 때문에request.getAttribute("myRequest")는null이 된다.
반면session값은 같은 브라우저 세션 동안 유지된다.
application값은 현재 웹 애플리케이션이 실행되는 동안 유지된다.
그래서 이전에/share를 한 번 거쳐서 저장된 적이 있다면,JSP를 직접 열어도 이 두 값은 남아 있을 수 있다.
즉, 이 실행 결과는 오류라기보다 세 범위의 유지 시간 차이를 그대로 보여 주는 장면이다.
request는 현재 요청에 값이 들어 있어야만 보이고,session과application은 더 긴 범위에 저장되기 때문에 이전에 저장된 값이 남아 있을 수 있다.
shareexam.jsp를 직접 열면 현재 요청에는request값이 없어서요청 스코프가null로 나올 수 있다.
이 구간에서 꼭 잡아야 하는 핵심정리하면
scope는 객체가 어디까지 공유되고 언제까지 유지되는지를 설명하는 개념이다.
핵심은 메서드 이름이 아니라 저장 위치다.
request scope는 한 번의 요청 안에서만 공유된다.session scope는 같은 브라우저 세션 안에서 여러 요청에 걸쳐 공유된다.application scope는 현재 웹 애플리케이션 전체에서 모든 사용자가 함께 공유한다.그리고 이 예제 흐름에서는
Servlet이 값을 저장하고 넘기고,JSP는 그 값을 꺼내서 화면에 보여 준다.
즉,JSP는 여기서 표현 담당이라고 이해하면 된다.
결국scope는 데이터를 어디에 저장해야 하는지 판단하는 기준이고,JSP는 그렇게 저장된 데이터를 화면에 맞게 꺼내서 보여 주는 역할이다.