스프링을 이용한 방법이 아닌 서블릿 애플리케이션을 구현하는 방법을 먼저 알아보고자 한다.
자바버전 불일치에 따른 빌드오류
[참고]
우선 실습 전에 저번에 생성해둔 프로젝트가 빌드가 되지 않은 문제가 발생했다. (프로젝트만 만들어두고 빌드해볼 생각을 안함 ㅎㅎ...)
원인은 jdk, 자바버전 불일치 오류였다!
- 자바버전 불일치 오류
- jdk 불일치 오류
- gradle FAILURE: Build failed with an exception
- 컴파일 버전 차이 오류 java: error: release version 17 not supported
인텔리제이 프로젝트 속성에서 빌드버전을 맞춰주었다.
1.
Preferences에 Build, Execution, Deployment > Build Tools > Gradle

다음과 같이 설정되어 있는 Gradle을 IntelliJ IDEA로 변경 및 Gradle JVM 버전 변경
2.
File 에서 Setting > Java Compiler
bytecode version 11로 변경, Module에 아무것도 없다면 프로젝트 추가해서 버전 11로 맞춰주기
3.
File에서 project Structure > project 탭

File에서 project Structure > Modules탭

Containerless 개발 준비
컨테이너 설치와 배포 등의 작업을 하지 않고 서블릿 컨테이너를 동작시키는 방법을 코드로 구현해본다. 스프링 부트가 사용하는 것으로 보이는 다음 두 라인을 제거하고 빈 main() 메소드만 남긴다.
public class HelloApplication {
public static void main(String[] args) {
System.out.println("Hello jiayni");
}
}
서블릿 컨테이너 띄우기
스프링 부트 프로젝트를 만들때 spring web 모듈을 선택하면 내장형 톰캣 라이브러리가 추가되어 있다.
내장형 톰캣의 초기화 작업과 간편한 설정을 지원하도록 스프링 부트가 제공하는 TomcatServletWebServerFactory를 사용하면 톰캣 웹 서버(서블릿 컨테이너)를 실행하는 코드를 만들 수 있다.
- 톰켓 불러서 사용하기
public static void main(String[] args) {
//내장형 톰켓을 쉽게 실행할 수 있도록 하는 도우미 클래스
ServletWebServerFactory serverFactory = new TomcatServletWebServerFactory();
WebServer webServer = serverFactory.getWebServer(); //서블릿컨테이너를 만드는 생성함수
webServer.start();
}
WebServer 를 추상화하여 톰켓말고도 다양한 서버를 사용하여 실행시킬 수 있도록 되어 있다.
서블릿 등록
매핑 작업이란, 클라이언트에서부터 들어오는 요청을 어떤 서블릿이 해결할 수 있을지 각 요청에 맞는 서블릿을 연결시키는 작업을 말한다.
코드에서 서블릿을 등록하려면 ServletContext가 필요하다. ServletContext를 전달해서 서블릿 등록과 같은 초기화 작업을 할 때는 ServletContectInitializer를 구현한 오브젝트를 ServletWebServerFactory의 getWebServer() 메소드에 전달한다.
*서블릿으로 개발하는 방법을 알기보다는 서블릿을 통해 어떻게 HTTP프로토콜을 이용하여 응답과 요청을 처리하는지에 대해 이해라고 학습하자!!
public static void main(String[] args) {
ServletWebServerFactory serverFactory = new TomcatServletWebServerFactory();
WebServer webServer = serverFactory.getWebServer(new ServletContextInitializer() {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.addServlet("hello", new HttpServlet() {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 2)
//요청 필수3요소 : 1.상태 코드 2.바디컨텐츠타입 3.바디
resp.setStatus(200);
resp.setHeader("Content-Type" ,"test/plain");
resp.getWriter().print("Hello Servlet");
}
}).addMapping("/hello"); // 1) hello로 요청으로도 들어오는 작업은 해당 서블릿으로 매핑!
}
});
webServer.start();
}
1) 서블릿을 등록할 때는 서블릿 이름과 서블릿 오브젝트를 이용한다. 서블릿 등록 정보에는 매핑할 URL 정보를 지정해야 한다.
2) 서블릿에서는 HttpServletRequest를 이용해서 요청 정보를 가져오고, HttpServletResponse를 이용해서 응답을 만드는 작업을 수행한다. 3가지 요소(상태코드, 헤더, 바디)를 이용해서 웹 요청을 생성한다.
서블릿 요청 처리
웹 클라이언트로부터 전달 받은 요청 정보를 서블릿 기능을 작성할 때 활용할 수 있다.
대표적으로 URL 등으로 전달되는 파라미터 값을 추출해서 사용하는 방법이 있다. (getParameter() 활용)
public static void main(String[] args) {
ServletWebServerFactory serverFactory = new TomcatServletWebServerFactory();
WebServer webServer = serverFactory.getWebServer(new ServletContextInitializer() {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.addServlet("hello", new HttpServlet() {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//동적으로 응답처리하기
String name = req.getParameter("name");
resp.setStatus(HttpStatus.OK.value()); //200
resp.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_EVENT_STREAM_VALUE);
resp.getWriter().print("Hello Servlet "+ name);
}
}).addMapping("/hello"); //hello로 요청으로도 들어오는 작업은 해당 서블릿으로 매핑!
}
});
webServer.start();
}
다음과 같이 정상적인 응답메시지와 헤더값도 확인할 수 있다.
프론트 컨트롤러
여러 요청을 처리하는데 반복적으로 등장하게 되는 공통 작업을 하나의 오브젝트에서 일괄적으로 처리하게 만드는 방식을 프론트 컨트롤러 패턴이라고 한다.
즉, 공통적인 작업을 처리하고 해당 역할을 담당하는 쪽에 매핑하여 처리하도록 하는 것이다. (인증,보안 등)
서블릿을 프론트 컨트롤러로 만들려면 모든 요청, 혹은 일정 패턴을 가진 요청을 하나의 서블릿이 담당하도록 매핑해 준다.
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// hello url요청과 get방식으로 요청이 들어온 경우에만
if(req.getRequestURI().equals("/hello") && req.getMethod().equals(HttpMethod.GET.name())){
String name = req.getParameter("name");
resp.setStatus(HttpStatus.OK.value()); //200
resp.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_EVENT_STREAM_VALUE);
resp.getWriter().print("Hello Servlet "+ name);
} else if (req.getRequestURI().equals("/user")) {}
else{
resp.setStatus(HttpStatus.NOT_FOUND.value()); // 404 에러 던지기
}
}
}).addMapping("/*");
Hello 컨트롤러 매핑과 바인딩
//컨트롤러 처리 하는 부분
HelloController helloController = new HelloController();
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if(req.getRequestURI().equals("/hello") && req.getMethod().equals(HttpMethod.GET.name())){
String name = req.getParameter("name");
String ret = helloController.hello(name); //컨트롤러 사용
resp.setStatus(HttpStatus.OK.value()); //200
//HttpHeaders.CONTENT_TYPE = "Content-Type"
//MediaType.TEXT_EVENT_STREAM_VALUE = "test/plain"
resp.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_EVENT_STREAM_VALUE);
resp.getWriter().print(ret); //컨트롤러를 이용하여 처리
} else if (req.getRequestURI().equals("/user")) {}
else{
resp.setStatus(HttpStatus.NOT_FOUND.value()); // 404 에러 던지기
}
}
}).addMapping("/*");
프론트 컨트롤러가 요청을 분석해서 처리할 요청을 구분한 뒤에 이를 처리할 핸들러(컨트롤러 메소드)로 요청을 전달한다. 핸들러가 처리하고 돌려준 리턴 값을 해석해서 웹 요청을 생성한다. 즉, 프론트 컨트롤러의 두 가지 중요한 기능은 바로 매핑과 바인딩이다.
핸들러에게 웹 요청 정보를 추출하고 의미있는 오브젝트에 담아서 전달하는 작업을 바인딩이라고 한다. 즉, 인자값으로 넘겨주는 작업, 파라미터로 넘겨주는 것을 바인딩이라고 한다.
매핑과 바인딩은 세밀한 규칙을 부여하면 매번 코드를 작성하지 않고도 공통 코드를 이용해서 이를 처리할 수 있도록 만들 수 있다.
'[개발] 프로그래밍 > Spring, Web' 카테고리의 다른 글
토비의 스프링 부트 - 이해와 원리(2) : 스프링 부트 시작하기 (0) | 2023.05.08 |
---|---|
토비의 스프링 부트 - 이해와 원리(1) : 스프링 부트 살펴보기 (1) | 2023.05.08 |
댓글