이제 본격적으로 웹 개발을 하기 위해서 개발환경의 구축이 필요하다. 본 강의에서는 이클립스를 사용하고 서버와 WAS를 위해서 Tomcat을 사용한다. 이를 위한 자세한 내용은 boostcourse에 자세하게 나와있다.
모든 개발환경을 갖추었다면 이클립스에서 Dynamic Web Project로 Sevlet 하나의 간단한 웹 서버를 만들 수 있다.
package examples;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class HelloServlet
*/
@WebServlet("/HelloServlet")
public class HelloServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public HelloServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<h1>Hello World</h1>");
}
}
@webServlet("/HelloServlet") 으로 시작하는 하나의 어노테이션을 찾을 수 있다. 위의 예시에서는 GET 메서드를 사용했기 때문에 클라이언트가 웹 서버에게 요청을 보내면 doGet() 부분을 통해 응답한다.
이때, 클라이언트가 요청할 때 서버는 요청을 받아내는 객체와 응답하기 위한 객체 총 두 개를 만들어낸다.
doGet( ... request, ... response ) 에서의 두 파라미터가 그 역할을 한다.
응답하기 위한 response에서 응답을 받았을 때, 리소스가 이미지인가 동영상인가 등을 알아보기 위해서
먼저 setContentType() 이 필요하다. 이를 통해 응답을 보낼 내용을 약속하고 실제 응답을 보낼 통로가 필요하다. 이 역할을 하는 것이 getWriter() 라는 메서드이고 PrintWriter 라는 객체가 return 되는 것을 확인할 수 있다.
이제 Servlet이 무엇인가? 라는 의문이 들었을 것이다. 이를 해결하기 위해서 먼저 자바 어플리케이션에 대해서 먼저 살펴보자!
자바 웹 어플리케이션이란 WAS에 설치되어 동작하는 어플리케이션으로 HTML, CSS, 이미지, 자바로 작성된 클래스, 각종 설정 파일 등이 포함된다.
실제 개발을 할때 WAS나 middle ware 혹은 framework를 사용한다면 그에 걸맞는 형식과 약속을 지켜야한다. 웹 어플리케이션 역시 WAS에 의해서 동작하기 때문에 지정된 규칙에 따라야 하는데 위의 폴더 구조가 그 중 하나이다.
가장 중요시 봐야할 것은 WEB-INF 폴더이다. 하위 폴더로는 각종 자료 파일등을 담는 lib 폴더나, 실제 class, 패키지를 담는 classes 폴더가 존재한다. 핵심이라고 할 수 있는 것은 web.xml 파일이다. web.xml 파일은 배포기술자로써 웹 어플리케이션에 대한 정보를 모두 가지고있는 파일이다. 이때, web.xml 파일은 servlet 버젼에 따라서 다르게 동작하는데 그것에 대해서는 아래에서 알아보자.
이제 Servlet이 무엇인지 알아보자
Servlet이란 자바 웹 어플리케이션의 구성요소 중 동적인 처리를 하는 프로그램의 역할이다. 말 그대로 동적이기 때문에 이미 응답할 페이지를 만들어서 가지고 있는 것이 아니라 요청이 들어왔을 때 프로그램이 실행되면서 응답할 코드를 만들어내고 그때 그 코드로 응답하는 것이다.
Servlet을 정의해보면 WAS에서 동작하는 Java 클래스이고, HttpServlet 클래스를 상속받아야한다. Servlet과 JSP로부터 최상의 결과를 얻으려면, 웹 페이지를 개발할 때, 이 두가지를 조화롭게 사용해야한다.
그럼 이 servlet을 어떻게 작성해야할까? 위에서 말했다시피 servlet 버젼에 따라서 web.xml 파일을 작성하는 방법이 다르다.
Servlet 3.0 spec 이상에서는 web.xml 파일을 사용하지 않고 자바 어노테이션을 사용한다. 이는 위에서 간단하게 만든 웹 서버를 참고하면 알 수 있다. 어노테이션은 @webServlet("/HelloServlet") 로 URL을 나타내기도 한다. 따라서 어노테이션 부분을 바꾼다면 해당 URL도 같이 바뀌는 것을 볼 수 있다.
Servlet 3.0 spec 이하에서는 servlet을 등록할 때 web.xml 파일에 등록해야한다.
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<display-name>exam25</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<servlet>
<description></description>
<display-name>TenServlet</display-name>
<servlet-name>TenServlet</servlet-name>
<servlet-class>exam.TenServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>TenServlet</servlet-name>
<url-pattern>/ttt</url-pattern>
</servlet-mapping>
</web-app>
java 파일이 아닌 web.xml 파일에 <servlet> 이라는 태그를 통해 등록을 해야한다.
"Servlet이란 자바 웹 어플리케이션의 구성요소 중 동적인 처리를 하는 프로그램의 역할이다."
실습을 통해서 동적인 처리과정을 직접 알아보자
package examples;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/LifecycleServlet")
public class LifecycleServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public LifecycleServlet() {
System.out.println("LifecycleServlet 생성!!");
}
public void init(ServletConfig config) throws ServletException {
System.out.println("init test 호출!!");
}
public void destroy() {
System.out.println("destroy 호출!!");
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
System.out.println("service호출!!");
}
}
위와 같은 코드가 있다고 하자. LifecycleServlet() 속에 init, destroy, doGet 메서드를 생성하고 각 메서드가 호출될때마다 콘솔에 표시해 직접 확인할 수 있다.
해당 URL로 클라이언트가 서버에게 요청한다. 서버는 URL을 받아서 LifecycleServlet 이라는 URL mapping을 확인하고 해당 클래스가 메모리에 있는지를 체크한다. 해당 이름이 메모리에 없다면 새로 만들어서 메모리에 올려주는 일을 해야한다. 메모리에 올린다는 말은 객체를 생성하는 작업을 뜻한다. 처음 실행했을 때는 당연히 메모리에 없기 때문에 LifecycleServlet을 생성, 따라서 생성자가 출력된다. 그 이후 init, service 가 연속적으로 실행된다.
Servlet은 서버에 Servlet 객체를 여러 개 만들지 않는다. 요청이 여러 개 들어오면 매번 생성하고 반복하는 것이 아니라 요청된 객체가 메모리에 있는지 체크하고 있으면 서비스라는 객체만 호출한다. 해당 URL에서 새로고침을 눌렀을 때, 이미 생성자로 메모리에 LifecycleServlet이 있기 떄문에 service만 호출되는 것을 확인할 수 있다.
만약 LifecycleServlet을 수정하고 저장했을 때, 메모리에 있는 Servlet의 객체는 이전과 달라졌기 때문에 사용할 수 없다. 따라서 이때 destroy 객체가 호출되고 다시 생성자와 init, service가 연달아 호출된다.
다시 한번 정리해보자면
- WAS는 서블릿 요청을 받으면 해당 서블릿이 메모리에 있는지 확인한다.
- if ( 메모리에 없음) {
- 해당 서블릿 클래스를 메모리에 올림
- init() 메서드를 실행
}
- service() 메서드를 실행 - WAS가 종료되거나, 웹 어플리케이션이 새롭게 갱신될 경우 destroy() 메서드가 실행된다.
service(request, response) 메소드
HttpServlet의 service메소드는 템플릿 메소드 패턴으로 구현합니다.
- 클라이언트의 요청이 GET일 경우에는 자신이 가지고 있는 doGet(request, response)메소드를 호출
- 클라이언트의 요청이 Post일 경우에는 자신이 가지고 있는 doPost(request, response)를 호출
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head><title>form</title></head>");
out.println("<body>");
out.println("<form method='post' action='/firstweb/LifecycleServlet'>");
out.println("name : <input type='text' name='name'><br>");
out.println("<input type='submit' value='ok'><br>");
out.println("</form>");
out.println("</body>");
out.println("</html>");
out.close();
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
String name = request.getParameter("name");
out.println("<h1> hello " + name + "</h1>");
out.close();
}
템플릿 메서드 패턴으로 구현되기 때문에 위와 같은 코드를 사용할 수 있다. doGet은 submit 버튼으로 name을 입력하여 전송하는 코드이다. 이때, method를 post라고 했기 때문에 위에서 입력한 name은 doPost에서 request로 전달되는 것을 알 수 있다.
Request, Response 객체
WAS는 웹 브라우저로부터 Servlet 요청을 받으면 두 가지 객체를 생성한다.
요청할 때 가지고 있던 정보를 HttpServletRequest 객체를 생성하고 저장하며, 웹 브라우저에게 응답을 보낼 때 사용하기 위해서 HttpServletResponse 를 생성한다. 생성된 두 객체를 WAS는 서블릿에 전달한다.
HttpServletRequest
- http프로토콜의 request정보를 서블릿에게 전달하기 위한 목적으로 사용한다
- 헤더정보, 파라미터, 쿠키, URI, URL 등의 정보를 읽어 들이는 메소드를 가지고 있다.
- Body의 Stream을 읽어 들이는 메소드를 가지고 있다.
HttpServletResponse
- WAS는 어떤 클라이언트가 요청을 보냈는지 알고 있고, 해당 클라이언트에게 응답을 보내기 위한 HttpServleResponse객체를 생성하여 서블릿에게 전달한다.
- 서블릿은 해당 객체를 이용하여 content type, 응답코드, 응답 메시지등을 전송한다.