
🐰 24.09.14 협업 역량 강화를 위한 릴레이 온라인 특강 : 실무에서 많이 사용하는 JAVA 1
자바 기반의 웹 프로그램 개발을 위해 만들어진 기술로 자바로 작성된 프로그램을 실행할 수 있는 서버 소프트웨어(톰캣)을 통해 관리된다. 서블릿을 실행하기 위해서는 톰캣과 같은 서블릿 컨테이너가 필요하며, 이러한 서버 소프트웨어는 일반적으로 WAS(Web Application Server)로 불리기도 한다.
기본적인 웹의 요청과 응답 과정은 http 서버에서 클라이언트 웹 브라우저로 응답

[서블릿의 동작구조]
서블릿은 HTML과 데이터를 조합하는 방식에 어려움이 있었고, 이를 해결하기 위해 JSP가 등장했다.서블릿에서 HTML과 데이터 결합을 손쉽게 처리하기 위해 만들어졌으며, HTML에서 자바 코드를 사용할 수 있는 구조인 HTML에 자바 코드를 더한 형태로 구성되며, 컨테이너에 의해 서블릿 형태의 자바 코드로 변환 후 컴파일되어 컨테이너에 적재되는 구조를 가지고 있다. 문법 자체는 page 지시어 선언 부분을 제외하면 HTML 파일 구조와 동일하고, CSS, JavaScript 사용 형식 또한 동일하다.
<html>
...
<h2><% = username %></h2>
...
</html>
데이터를 반복해서 출력하거나 조건을 체크해야 되는 경우 단순한 HTML 문법만으로는 처리할 수 없기 때문에 자바 코드를 중간중간 사용해야 한다.
<table>
<% for(Member m : mlist) { %>
<tr>
<td> <%=m.name %> </td>
<td> <%=m.email%> </td>
</tr>
<% } %>
</table>
HTML 부분은 서블릿 컨테이너(WAS)에 의해 out.println()을 사용하는 형태로 변환되었다.
→ JSP는 HTML이 아니라 서블릿 형태의 프로그램으로 변환되는 것
JSP의 구조적 문제를 해결하기 위해 커스텀 태그를 기반으로 도입되었다.
<table>
<c:forEach var="m" items="${mlist}">
<tr>
<td> ${m.name} </td>
<td> ${m.email} </td>
</tr>
</c:forEach>
</table>
서버 응답을 다양한 형태로 전달하는 개념으로, HTTP의 기본 응답이 HTML인데 이를 JSON 등 다른 규격을 사용하는 것으로 접근하고, 이에 따라 웹을 단순히 브라우저에서 콘텐츠를 이용하는 브라우저 서비스가 아닌 데이터를 주고받기 위한 API 서비스의 형태로 발전시킨 계기가 되었다.
REAT API는 안정되고 검증된 웹 기술을 그대로 사용할 수 있으며, 성능, 안정성, 보안, 백업, 분산 등 여러 인프라의 재활용이 가능하다.
{
"squadName" : "Super hero squad",
"homeTown" : "Metro City",
"formed" : 2026,
"secreBase" : "Super tower",
"active" : "Super hero squad",
"members" : [
{
"name" : "Molecule Man",
"age" : 25,
"secretlentity" : "Dan Jukes",
"powers" : ["Radiation resistance", "Turning tiny", "Radiation blast"]
}
]
}

HTML 구조와의 차이점은 문서구조, 태그들을 가지고 있는 것이 아니라 JSON 형태로 데이터만 알아볼 수 있는 형태로 만들어져있다.
자바에서 REAT API 개발을 위한 서버 프로그램의 표준 규격, 해당 규격을 가지고 생성하면 어떤 환경에서도 똑같이 사용할 수 있다. 이 방법보다 더 개선된 부분이 스프링 프레임워크의 ReatControlloer 이다.
// JAX-RS 사용하는 경우
@Path("/addrbook")
public class RestApiService{
Logger logger = Logger.getLogger("RestApiService");
AddrBookDAO dao = new AddrBookDAO();
@GET
@Path("list")
@Produces(MediaType.APPLICATION_JSON)
public List<AddrBook> getList(){
List<AddrBook> apiList = dao.getAll();
logger.info("API call : context_root/api/addrbook/list");
return apiList;
}
}
// REST API만 스프링 프레임워크를 사용하는 경우
@RestController
@RequestMapping("/addrbook")
public class RestApiService{
Logger logger = Logger.getLogger("RestApiService");
AddrBookDAO dao = new AddrBookDAO();
@GETMapping("list")
public List<AddrBook> getList(){
List<AddrBook> apiList = dao.getAll();
logger.info("API call : context_root/api/addrbook/list");
return apiList;
}
}

// Servlet 구조
package sample;
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("/hello")
public class MyServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public HelloServlet() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
out.println("<h1>Welcome to HelloWorld!</h1>");
out.close();
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
실제 자바 웹 개발에서이 서블릿 조합으로는 화면 구성을 위해(렌더링을 하기 위해) JSP와 같은 템플릿 엔진을 사용하고, REST API 구현을 위해서는 JAX-RS를 사용한다. 복잡한 서비스 구현을 위해 프런트 컨트롤러 모델 등을 사용한다. (서블릿에 해당하는 많은 로직들을 분기해서 만들고, 해당 구조를 JSP 로 전송해서 쉽게 개발할 수 있는 구조로 만든다.)
서블릿 자체를 자바로 구현하나, 서블릿 컨테이너에 해당 클래스가 서블릿임을 알려야 하고, 어떤 URP 접근에 실행해야 하는지 등록하는 과정이 필요하다.
✅ jakarta.servlet.Servlet 인터페이스를 구현한 추상 클래스인 GenericServlet 클래스와 HttpServlet 클래스 중 하나를 사용해 구현 ➡️ HTTP 프로토콜에 최적화되어 있는 HttpServlet 클래스를 상속해 구현하는 것이 좋음
✅ HttpServlet을 상속받아 doGet(), doPost() 메서드를 오버라이딩한 구조
/**
* Servlet implementation class HelloWorldServlet
*/
public class HelloWorldServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
response.getWriter().append("Served at: ").append(request.getContextPath());
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
| 메서드 | 반환 예 | 설명 |
|---|---|---|
⭐getParameter(name) | hong1234 | name 속성으로 전달된 파라미터 값 |
getParameterValues(name) | {kildong, kildong@gmail.com} | 동일한 name 속성으로 전달된 모든 파라미터 값 |
getRequestURL() | http://www.xxx.com:8080/swa01/list.jsp | URL |
getRequestURI() | /swa01/list.jsp | URL에서 스키마, 서버 이름, 포트 번호를 제외한 나머지 주소와 파라미터 |
getScheme() | http | http, https, FTP와 같은 프로토콜 |
getServerName() | www.xxx.com | 서버 이름 |
getServerPort() | 8080 | 서버 포트 |
getContextPath() | /swa01 | 컨텍스트 경로 |
⭐getMethod() | GET | GET, POST 등의 HTTP 메서드 |
isSecure() | false | SSL 보안 여부. https와 같은 보안 채널의 사용 여부 (true/false) |
⭐getLocale() | ko_KR | 지역 정보 |
getProtocol() | HTTP/1.1 | 사용하는 프로토콜, 프로토콜/메이저 버전, 마이너 버전 |
getLocalAddr() | 127.0.0.1 | 서버의 로컬 IP 주소 |
⭐getRemoteAddr() | 210.102.111.212 | 클라이언트 IP 주소 |
HttpServletRequest 와 마찬가지로 클라이언트와 연결된 처리가 가능하지만, 클라이언트에서 서버로 전달하는 것과 관련된 것이 아니라 서버에서 클라이언트로 전달하려는 목적을 위한 기능으로 구성된다. 서블릿은 해당 객체를 이용하여 content type, 응답 코드, 응답 메시지 등을 전송할 수 있다.
*서블릿 컨테이너 : 요청 클라이언트에 응답을 보내기 위한 HttpServletResponse 객체를 생성하여 서블릿에 전달함
| 메서드 | 설명 |
|---|---|
⭐ sendRedirect (String location) | 클라이언트에 리다이렉트(Redirect) 응답을 보낸 후 특정 URL로 다시 요청하게 함 |
⭐ getWriter() | 클라이언트 데이터를 보내기 위한 출력 스트림을 리턴 |
setContentType (String type) | 클라이언트에 전달되는 콘텐츠 타입을 지정 |
addCookie (Cookie cookie) | 응답에 쿠키를 추가 |
addHeader (String name, String value) | 헤더에 name 과 value 를 추가 |
⭐ encodeURL(String url) | 클라이언트가 쿠키를 지원하지 않을 때 세션 id를 포함한 특정 URL 을 인코딩 |
getHeaderNames() | 현재 응답이 헤더에 포함된 name을 얻어옴 |
서블릿 클래스만으로는 톰캣에서 실행 불가 → web.xml이나 어노테이션으로 서블릿임을 선언해야 함
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://jakarta.ee/xml/ns/jakartaee"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd" id="WebApp_ID"
version="6.0">
<display-name>swa01</display-name>
<servlet>
<description></description>
<display-name>HelloWorldServlet</display-name> <servlet-name>HelloWorldServlet</servlet-name>
<servlet-class>swa01.wk03.servlet. HelloWorldServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloWorldServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
서블릿 3.0에서 자바 어노테이션을 이용한 등록 → 어노테이션 사용이 권장되지만, 필요하다면 web.xml을 사용하는 것도 가능함
@WebServlet (description = "Hello World Servlet", urlPatterns = "/hello")
public class HelloWorldServlet3 extends HeepServlet {
...
}
객체의 생성에서 종료에 이르는 과정

init() 메서드service() 메서드doGet() 이나 doPost() 로 분기됨request 와 response 객체가 제공됨| Scope Object | 클래스 | 생성 | 소멸 | 범위 |
|---|---|---|---|---|
| Request | javax.servlet. ServletRequest | 현재 페이지가 요청될 때 | 다른 페이지로 이동할 때 | 현재 페이지. 포워딩의 경우 다음 페이지까지 참조 가능 |
| Session | javax.servlet.http. HttpSession | 클라이언트가 서버에 접속할 때 | 일정 시간이 지나거나 브라우저가 종료될 때 | 동일 클라이언트에 대해 다른 페이지에서도 참조 가능 |
| Web Context or Application | javax.servlet. ServletContext | 웹 애플리케이션이 시작될 때 | 웹 애플리케이션이 종료될 때 | 모든 클라이언트에서 참조 가능 |

JSP는 HTML과 자바 코드를 섞어 사용할 수 있는데, 이때 사용되는 자바 코드를 의미함
자주 쓰이는 스크립트 요소는 <%! %> <%= %> <% %> 이 있음
/*
로그인 페이지에서 입력한 이름을 가져와 화면에 출력한 다음
사용자 목록을 for문을 이용해 출력하는 부분의 JSP 코드
*/
<%
String name = request.getParamenter("uname");
%>
<h2> <%= uname %> </h2>
<hr>
<table>
<% for(Member m : mlist) { %>
<tr>
<td> <%=m.name %> </td>
<td> <%=m.email %> </td>
</tr>
<% } %>
</table>
out.println()을 사용할 경우
<table>
<%
for(Member m: mlist) {
out.println("<tr><td>"+m.name +"</td><td>" + m.email +"</td></tr>");
}
%>
</table>
클라이언트 요청을 다른 페이지로 전환하는 액션, 리디렉션과 기능적으로 유사하지만 내부적으로는 차이가 있음
// main.jsp
<jsp:forward page="result.jsp">
<jsp:param name="title" value="My Homepage" />
</jsp:include>
// result.jsp
<h2><%= request.getParameter("title") %></h2>

오라클은 2017년 자바 EE 8 릴리즈를 마지막으로 오픈소스 SW를 지원하는 비영리 단체인 이클립스 재단에 자바 EE 프로젝트를 이관. 썬 마이크로시스템즈를 인수한 오라클이 사실상 자바 EE의 수익화에 실패하면서 기술 주도권을 포기한 것으로 판단
엔터프라이즈 자바 생태계를 구축하고 발전시켜온 자바 EE는 오랜 노력과 공헌에도 불구하고 상업 벤더 중심의 폐쇄적인 정책과 영리 목적으로 운영되어 많은 비판을 받음

➡️ 모바일 및 앱 프런트엔드 기반 웹 애플리케이션 개발이 늘어나면서 MVP(Model-View-Presenter), MVVM(Model-View-ViewModel)과 같은 패턴도 널리 사용되고 있음
MVC 패턴을 사용하게 되면 향후의 가독성이라던가 어떤 방향으로 개발이 되었는지 이해가 쉬워짐
MVC 패턴의 목적은 화면과 데이터 처리를 분리하여 코드 간 종속성을 줄이는 것임
➡️ 즉 구성요소 간 역할을 명확하게 하여 코드를 쉽게 분리하고 협업이 용이해짐

데이터를 처리하는 영역, 뷰나 컨트롤러에 독립적인 구조로 데이터베이스 처리를 필요로 하는 여러 애플리케이션에서 공유할 수 있으며, 웹 애플리케이션이 아닌 경우에도 사용할 수 있음
🤚 DAO, DO는 필수적인가?
꼭 그렇지는 않지만, 사용하게 되면 추후 유지보수 차원에서 좋음
DO 혹은 List<DO> 형태의 객체를 request에 저장한 후 포워딩함➡️ 따라서 프로젝트의 규모가 클수록 컨트롤러는 복잡해지고 관리가 어려워지는 문제가 있음
JSP, 서블릿 모두 가능함
🤚 서블릿만으로 컨트롤러 구현이 가능할까?

➡️ SQL문으로 데이터를 조작하는 형태로 동작함
Statement 중 성능을 위해 주로 java.sql.PreparedStatement를 사용함
로딩하고 연결하는 단계는 한번만 실행하고 중간 과정만 반복됨
모델 구현(DAO) : StudentDAO
package wk07;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class StudentDAO {
Connection conn = null;
PreparedStatement pstmt = null;
final String JDBC_DRIVER = "org.h2.Driver";
final String JDBC_URL = "jdbc:h2:tcp://localhost/./swadb";
public void connect() {
try {
Class.forName(JDBC_DRIVER);
conn = DriverManager.getConnection(JDBC_URL, "swauser", "12345678");
} catch (Exception e) {
e.printStackTrace();
}
}
public void close() {
try {
if (pstmt != null) pstmt.close();
if (conn != null) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
public void insert(Student s) {
connect();
String sql = "INSERT INTO student(username, univ, birth, email) values(?,?,?,?)";
try {
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, s.getUsername());
pstmt.setString(2, s.getUniv());
pstmt.setDate(3, s.getBirth()); // Assumes s.getBirth() returns java.sql.Date
pstmt.setString(4, s.getEmail());
pstmt.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
close();
}
}
}