1. Header
- 클라이언트 측에서 요청을 보낼 때 함께 전달되는 수식어
- 클라이언트의 정보, 요청하는 메서드 종류 등이 담겨있음
- 이 정보를 이용하여 서버에서는 사용자를 판단하고 그에 맞는 응답 가능
package org.example.demo6;
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.util.Enumeration;
@WebServlet("*.do")
public class DemoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// String method = req.getHeader("User-Agent");
// System.out.println("User-Agent : " + method);
//
// String host = req.getHeader("Host");
// System.out.println("Host : " + host);
Enumeration<String> headerNames = req.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
String headerValue = req.getHeader(headerName);
System.out.println(headerName + ": " + headerValue);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
}

- POST 요청 시 Body 내용을 Parsing → getParameter를 통해 확인 가능
- key에 따른 value 값 확인 가능
- 일반 평문 (text/plain)으론 불가능
- Context-Type : x-www-form-urlencoded으로 지정해야 파싱 가능
package org.example.demo6;
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.BufferedReader;
import java.io.IOException;
import java.util.Enumeration;
@WebServlet("*.do")
public class DemoServlet extends HttpServlet {
//localhost:8080/demo.do?username=ssar&password=1234
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// String method = req.getHeader("User-Agent");
// System.out.println("User-Agent : " + method);
//
// String host = req.getHeader("Host");
// System.out.println("Host : " + host);
// Enumeration<String> headerNames = req.getHeaderNames();
//
// while (headerNames.hasMoreElements()) {
// String headerName = headerNames.nextElement();
// String headerValue = req.getHeader(headerName);
// System.out.println(headerName + ": " + headerValue);
// }
// 주소에 걸리는 queryString = DB 내의 where
String path = req.getRequestURI();
System.out.println("path: " + path);
String method = req.getMethod();
System.out.println("method: " + method);
String queryString = req.getQueryString();
System.out.println("queryString: " + queryString);
String username = req.getParameter("username");
System.out.println("username: " + username);
String password = req.getParameter("password");
System.out.println("password: " + password);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// String body = "";
//
// BufferedReader br = req.getReader();
// while(true) {
// String line = br.readLine();
// if(line == null) break;
// body += line;
// }
// System.out.println("body: " + body);
// Context_Type : x-www-form-urlencoded
String method = req.getMethod();
System.out.println("method: " + method);
String username = req.getParameter("username");
System.out.println("username: " + username);
String password = req.getParameter("password");
System.out.println("password: " + password);
}
}
- username, password 입력 후 전송 버튼을 누르면 localhost:8080/hello.do로 요청 전송
- body : username=ssar&password=12345
- method : POST → body parsing 후 username, password 출력


2. Dispatcher
- 한 서버 내에는 여러 개의 View를 소유하고 있을 수 있음
- 클라이언트의 요청에 따라 올바른 View를 응답해야 함
- View와 클라이언트를 1:1로 연결할 경우, 코드의 재사용이 불가
- a, b, c 세 곳에서 전부 DB를 사용한다면, 세 곳에 전부 다 DB를 연결해야 함
- 이를 해결하기 위해 사용하는 것이 DS (Dispatcher Servlet)
- 경로 찾기
- 클라이언트가 요청하는 View를 path 파싱을 통해 찾아서 응답
- 공통 로직
- 한 코드 내에서 path에 따라 다른 응답을 제공할 수 있으므로 공통 로직 수행 가능
- 각 View에 바로 접근하는 것이 아닌, Servlet에 접근한 후 View에 접근
- DB 연결 (Model), Dispatcher (Controller), View (View)를 사용한 서버 구동 방식
- MVC Pattern

package org.example.demo8;
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;
// localhost:8080/hello.do?path=a
@WebServlet("*.do")
public class FrontController extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("common logic");
String path = req.getParameter("path");
if (path.equals("a")) {
req.getRequestDispatcher("a.jsp").forward(req, resp);
} else if (path.equals("b")) {
req.getRequestDispatcher("b.jsp").forward(req, resp);
} else if (path.equals("c")) {
req.getRequestDispatcher("c.jsp").forward(req, resp);
} else {
}
}
}
- a.jsp
- b, c는 <h1> 내용만 다름
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>A view</h1>
</body>
</html>
결과
- path=a

- 공통 로직 수행 확인

- path=b


- path=c


3. Reflection
- Servlet의 코드들은 Reflection을 사용하면 더욱 편하게 사용 가능
- Spring Boot
package org.example.second.controller;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller // PrintWriter 응답 @Controller -> requestDispatcher
public class DemoController {
@GetMapping("/haha")
public @ResponseBody String haha(){
return "haha";
}
@GetMapping("/home")
public void home(HttpServletResponse response){
response.setStatus(302);
response.setHeader("Location", "/haha");
}
}
결과
- localhost:8080/home 요청 → 302 코드 (Redirection) + Redirect Location (/haha) 응답
- localhost:8080/haha 재요청(Redirection) → haha 응답
4. Dispatcher 보완
- Dispatcher를 무조건 거치게 하기 위해서는 기존 jsp 파일을 숨겨야 함
- WEB-INF 파일 사용 (보안 폴더 : 외부 접근 차단)
- 내부 접근만 가능
- 내부 접근을 통해, Dispatcher의 공통 로직을 무조건 거칠 수 있도록 설계
- Dispatcher
- New Class ViewResolver : path 단축
- Business에 따라, 또는 개발자 성향에 따라 더욱 단축 가능
package org.example.demo9;
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;
// http://localhost:8080/
@WebServlet("/") // 모든 요청이 여기로 몰린다. ex) /, /abc, /a/b/c
public class DemoServlet extends HttpServlet {
// http://localhost:8080?path=a
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// body -> path=값&name=값
// queryString -> ?path=값&name=값
String path = req.getParameter("path");
if (path.equals("a")) {
req.setAttribute("name", "ssar");
req.getRequestDispatcher(ViewResolver.viewName("a")).forward(req, resp);
} else if (path.equals("b")) {
req.setAttribute("age", 20);
req.getRequestDispatcher(ViewResolver.viewName("b")).forward(req, resp);
}
}
}
- ViewResolver
- path를 받아 전체 path를 return
- 추가 메서드 제작으로 RequestDispatcher 항목을 더욱 단축 가능
package org.example.demo9;
public class ViewResolver {
private static final String prefix = "/WEB-INF/views/";
private static final String suffix = ".jsp";
public static String viewName(String filename) {
return prefix + filename + suffix;
}
}
- a.jsp와 b.jsp는 WEB-INF > views로 이동
- 보안 폴더 내부로 이동 시켜 외부 접근 차단
- 많은 페이지들이 담겨있을 수 있고, 설정 파일이 존재할 수 있으므로 폴더로 구분
결과
- localhost:8080/?path=a

- localhost:8080/?path=b

- 직접 파일 Access 시도 시 차단 (WEB-INF 폴더 내부에 있음 : 외부 접근 차단)
- 기본 디렉토리 : webapp → /a.jsp는 ./webapp/a.jsp를 찾음
- 파일 위치를 옮겼으므로 찾을 수 없음

- WEB-INF/views/b.jsp 직접 연결 시도 시 차단
- 보안 폴더 내부

5. 완성
Component Scan
- Annotation (@Controller)
componentScan
package org.example.demo10.core;
import jakarta.servlet.ServletContext;
import java.io.File;
import java.util.HashSet;
import java.util.Set;
public class ComponentScan {
private final ServletContext servletContext;
public ComponentScan(ServletContext servletContext) {
this.servletContext = servletContext;
}
// 클래스를 스캔하는 메소드
public Set<Object> scanClass(String pkg) {
Set<Object> instances = new HashSet<>();
try {
// 톰캣의 webapps 폴더 내 WEB-INF/classes 경로 가져오기
String classPath = servletContext.getRealPath("/WEB-INF/classes/");
// C:\Program Files\Apache Software Foundation\Tomcat 11.0\webapps\ROOT\WEB-INF\classes\
File slashDir = new File(classPath);
File dotToSlashDir = new File(slashDir, pkg.replace(".", File.separator));
for (File file : dotToSlashDir.listFiles()) {
System.out.println(file.getName());
String className = pkg + "." + file.getName().replace(".class", "");
System.out.println(className);
try {
Class cls = Class.forName(className);
if (cls.isAnnotationPresent(Controller.class)) {
System.out.println("Controller 어노테이션");
Object instance = cls.getDeclaredConstructor().newInstance();
instances.add(instance);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return instances;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Reflection & Annotation
Controller
package org.example.demo10.core;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Controller {
}
RequestMapping
package org.example.demo10.core;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequestMapping {
String value();
}
DispatcherServlet
package org.example.demo10;
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;
import org.example.demo10.core.ComponentScan;
import org.example.demo10.core.RequestMapping;
import org.example.demo10.core.ViewResolver;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Set;
@WebServlet("*.do")
public class DispatcherServlet extends HttpServlet {
private Set<Object> controllers;
@Override
public void init(ServletConfig config) throws ServletException {
// 1. 컴포넌트 스캔
ComponentScan componentScan = new ComponentScan(config.getServletContext());
controllers = componentScan.scanClass("org.example.demo10.controller");
//System.out.println(controllers.size());
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// localhost:8080/user.do?path=join
String path = req.getParameter("path");
// 2. 라우팅
String templatePath = route(path);
// 3. 리다이렉션
if(templatePath.contains("redirect:")) {
String redirectPath = templatePath.replace("redirect:", "");
// resp.setStatus(302);
// resp.setHeader("Location", "?path=" + redirectPath);
resp.sendRedirect("?path=" + redirectPath);
return;
}
// 4. 이동
if (templatePath == null) {
resp.setStatus(404);
resp.getWriter().println("<h1>404 Not Found</h1>");
} else {
req.getRequestDispatcher(ViewResolver.viewName(templatePath)).forward(req, resp);
}
}
private String route(String path) {
for (Object instance : controllers) {
Method[] methods = instance.getClass().getMethods();
for (Method method : methods) {
RequestMapping rm = method.getAnnotation(RequestMapping.class);
if (rm == null) continue; // 다음 for문으로 바로 넘어감
if (rm.value().equals(path)) {
try {
return (String) method.invoke(instance);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
return null;
}
}
Controller
UserController
package org.example.demo10.controller;
import org.example.demo10.core.Controller;
import org.example.demo10.core.RequestMapping;
@Controller
public class UserController {
@RequestMapping("join")
public String join() {
System.out.println("UserController join");
return "join";
}
@RequestMapping("login")
public String login() {
System.out.println("UserController login");
return "login";
}
}
결과
- localhost:8080/*.do?path=join

- localhost:8080/*.do?path=login

- path가 다를 경우 : 404 Not Found 출력

Share article