728x90

MVC 프레임 워크 만들기


FrontController 패턴 특징

-프론트 컨트롤러 서블릿 하나로 클라이언트의 요청을 받음

-프론트 컨트롤러가 요청에 맞는 컨트롤러를 찾아서 호출 입구를 하나로!

-공통 처리 가능

-프론트 컨트롤러를 제외한 나머지 컨트롤러는 서블릿을 사용하지 않아도 됨

-스프링 웹 MVC의 핵심도 바로 FrontController

-스프링 웹 MVC의 DispatcherServlet이 FrontController 패턴으로 구현되어 있음


프론트 컨트롤러 도입

1. ControllerV 라는 인터페이스를 만들고 이 인터페이스에서 process라는 메소드를 하나 만든다. 이 메소드는 HttpServlet에 service 메소드와 똑같다.

2. ControllerV를 상속받는 Controller class 를 만든다 (회원가입, 저장 , 조회)

>> 각 로직은 servlet과 mvc를 활용한 코드를 그대로 사용했다.

3. 프론트 컨트롤러 구현 (FrontControllerServlet)

3-1) HttpServlet를 상속받고 Map을 활용했다.>> Map<String, ControllerV> controllerMap << 으로

key: 매핑 URL이고 value: 호출될 컨트롤러이다. 즉,  String은 경로 , ControllerV는 호출될 컨트롤러 이다.

3-2) 프론트 컨트롤러 생성자에 일단 위에서 만든 Controller class 와 각 경로들을 정해서 controllerMap에 .put 시켜준다.

3-3) 프론트 컨틀로러 service 메소드에 String requestURI = request.getRequestURI() 코드를 넣어서 이 service를 호출한 경로를 확인 할 수 있다. 먼저 requestURI 를 조회해서 실제 호출할 컨트롤러를 controllerMap 에서 찾는다.

3-4) 이 경로를 이용해서 생성자에서 만든 Map과 일치한 경로가 있는지 확인을 한다 - > ControllerV1 controller = controllerMap.get(requestURI)

3-5) 없으면 .setStatus(404) 를 반환하고, 있으면 controller.process(request, response); 을 호출해서 해당 컨트롤러를 실행한다.

 

*다형성을 활용하여 프론트 컨트롤러를 도입했지만 Controller class 안에 공통된 부분들이 아직도 존재한다. 이런 부분을 더 해결할 필요가 있다*


View 분리

String viewPath = "/WEB-INF/views/new-form.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);

각 Controller class 에 viewPath 라는 변수에 경로를 받고 request.getRequestDispatcher(viewPath) 해서 .forward() 시키는 작업이 중복이 됐다. 이를 한 곳에서 처리하기 위해 View를 분리했다.

1. MyView라는 클래스를 만들고 여기 안에서 viewPath 를 필드에 선언하고 생성자에 이 viewPath를 초기화 시켜준다.

그리고 render라는 함수를 만들어서 위에 request.getRequestDispatcher(viewPath) 해서 .forward() 시키는 작업을 render함수 안에 넣어준다.

public void render(HttpServletRequest request, HttpServletResponse response) {
 	RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
 	dispatcher.forward(request, response);
    }

2. 프론트 컨트롤러 도입과 똑같은 방식으로 하는데, 각 Controller class process 메소드에 return new Myview("경로") 를 반환한다.

 

*위와 같은 방식으로 하면 각 Controller class 에 중복되는 코드들이 없어지는데 이 구조는 프론트 컨트롤러에서 컨트롤러를 호출하면 그 컨트롤러에서 MyView를 반환하고 MyView 클래스에서는 render함수가 있는데 이 함수는 프론트 컨트롤러에 service 함수에서 실행이 된다. 그러면 그 경로에 맞게 JSP 파일이 실행되게 된다*


Model 추가(가장 중요)

컨트롤러에서 HttpServletRequest , HttpServletResponse가 꼭 필요하지는 않다. 요청 파라미터 정보는 Map 담아 놓고 필요할때 꺼내서 쓰는 형식으로 바꾸면 된다.

 

서블릿의 종속성을 제거하기 위해 Model을 직접 만들어 코드를 리팩토링 한다. 추가로 뷰 이름이 중복 되는 것을 제거 할 것이다.

이것을 하는 이유는 각 컨트롤러에 경로를 입력해주는데 "/WEB-INF..." 여기서 경로가 바뀌게 된다면 모든 컨트롤러를 다 바꿔줘야 하지만 이렇게 한 곳에서 경로를 반환해주면 한 곳에서만 코드를 바꾸면 되기 때문에 더 좋은 설계가 되기 때문이다.

 

1. 서블릿의 종속성을 제거하기 위해 Mdoel을 직접 만들어야 한다. 그래서 ModelView class 를 만들어주고 viewName (new-form,save-result..) , Map model 을 필드로 가진다.

2. 각 컨트롤러 클래스는 ModelView 로 반환이 되고 파라미터로 Map<String, String> paramMap 이 넘어온다.

(paramMap에는 쿼리 파라미터 정보가 들어있다. 서블릿의 종속성을 제거 하려고 하기 때문에 request.getAttribute() 로 가져오는것이 아닌 우리가 paramMap에 정보를 담는다.)

3. 회원 등록 폼에서는 paramMap에서 정보를 사용할게 없기 때문에, return new ModelView("new-form") 으로 논리적인 이름만 반환한다. 하지만 저장할때는 쿼리 파라미터 정보가 필요하기 때문에 paramMap.get("useranme") 처럼 정보를 가져와서 member 객체를 만들고 repository에 저장을 하고 ModelView mv = new ModelView("save-result"); 하면서 논리적인 이름만 반환을 하는데, 여기서 member 객체도 같이 전달해야한다. mv.getModel().put("member",member);

- ​paramMap.get("username");

>> 파라미터 정보는 map에 담겨있다. map에서 필요한 요청 파라미터를 조회하면 된다.

- mv.getModel().put("member", member);

>> 모델은 단순한 map이므로 모델에 뷰에서 필요한 member 객체를 담고 반환한다.

 

4. 프론트 컨트롤러 수정

4-1) 위에서 서블릿 종속성을 제거하기 위해 쿼리 파라미터 정보를 paramMap에 담는다고 했기 때문에 이 코드를 작성한다. 모든 쿼리 파라미터를 조회하는 것은 전에 배웠기 떄문에 자세한 코드는 생략.

4-2) controller.process(paramMap) 을 실행하고 반환되는 ModelView mv 를 활용해서 경로를 설정해준다. mv.getViewName() 으로 논리적인 이름만 뽑아 놓고 문자열 조합을 이용해서 (viewResolver) MyView view 객체를 새로 만들고 view.render()로 랜더링을 해준다.

4-3) .render() 를 할 때 원래 파라미터로 request, response만 넘겨줬는데 이것은 우리가 request.setAttribute()를 이용해서 정보를 담았기 때문에 가능했다. 하지만 우리는 model에 담았기 때문에 .render(mv.getModel() , request, response) 이렇게 3개의 파라미터를 날려줘야한다.

4-4) MyView 클래스에 3개의 파라미터가 넘어왔을때 render 메소드를 추가해준다. 이때 model에 있는 정보를 다시 request 객체에 저장을 해야하기 때문에 model.forEach((key, value)->request.setAttribute(key, value)); 코드를 추가한다.

4-5) 그런후 똑같이 request.getRequestDispatcher(viewPath) 를 해줘서 .forward(request, reponse)를 실행하면 된다.


단순하고 실용적인 컨트롤러

서블릿 종속성을 제거하고 뷰 경로의 중복을 제거하는 등, 잘 설계된 컨트롤러이다. 그런데 실제 컨트롤러 인터페이스를 구현하는 개발자 입장에서는 조금 번거롭다.

 

구조는 위와 같지만, 컨트롤러에서 ModelView를 반환하지 않고 ViewName만 반환한다.

 

1. ControllerV의 인터페이스에 process 메소드는 ModelView로 반환되는게 아니라 String 으로 반환이 되는데,

이는 뷰의 이름을 반환한다는 의미이다. 그러면 ModelView에서 사용하는 model 객체는 파라미터로 받는 것이다.

ex) String process(Map<String,String> paramMap , Map<String,Obejct> model);

>>ModelView에서 model을 관리하면 번거로움이 있었는데 model을 프론트 컨트롤러에서 관리하면 아주 간결하고 실용적이게 코드를 작성할 수 있다.

2. 회원 등록 폼에서는 그냥 return "new-form" 이런식으로 viewName만 리턴을 시키고 회원 저장 폼에서는 정보를 가져와야 하는데 파라미터로 model이 넘어오기 때문에 model.put("member",member); return "save-result";  의 코드로 아주 간결해진다. 회원 목록 조회도 model.put("members",members); return "members";

3. model을 프론트 컨트롤러에서 생성한다고 했는데 Map<String,Object> model = new HashMap<>() 으로 model을 생성하고, 각 컨트롤러에서 메소드 process를 실행시킬때 원래는 paramMap만 넘겨줬지만 model까지 같이 넘겨준다.

4. 그럼 return 되는 값은 viewName("new-form"...) 이걸로 viewResolver 메소드를 이용해 완벽한 경로 이름을 만들고 이를 view.render()를 해준다.

5. 위에서는 .render(mv.getModel()..) 로 모델 객체를 꺼내서 넘겨줬지만 프론트 컨트롤러에서 model을 만들고 관리하기 때문에 view.render(model...) 으로 넘기면 된다.

728x90
728x90

간단한 기능을 하는 회원 관리 웹 애플리케이션을 만들어봤다.

회원 정보로는 이름과 나이를 가지고 저장하고 조회할 수 있는 기능을 한다.

그래서 Member라는 클래스 안에 id, name, age 를 필드로 가지게 했고 MemberRepository를 만들어서 저장,조회 기능을 가지게 했다.

(싱글톤으로 만드는데 spring의 기능을 사용하고 순수 서블릿 만으로 구현하기 때문에 private 생성자로 막아놓고 get메서드로 memberRepository를 가질 수 있게 했다.)


servlet 의 불편한 점 (응답 메시지 작성)

-reponse.setContentType("text/html")

-reponse.setCharacterEncoding("utf-8")

-getParameter 로 가져와서 하는것은 정말 편리하다.

-하지만 자바 코드로 html 문서를 작성 하는것이 너무 어렵다.

-서블릿과 자바 코드만으로 HTML을 만들었는데, 진짜 한눈에 봐도 비효율적이라는 것을 알 수 있다.

 

>>템플릿 엔진 사용. 위에서 방식은 자바코드를 html로 만든것이지만 템플릿 엔진은 html에 자바 코드를 넣는것

 

>>템플릿 엔진 종류(JSP , Thymeleaf , Freemarker , Velocity) JSP는 거의 사용 안 하는 추세이다.

JSP 사용해보기

-build.gradle에 jsp 라이브러리 추가를 해야한다.

 

-<%@ page contentType="text/html;charset=UTF-8" language="java" %>

>> jsp문서라는 뜻을 알려주는 코드

>>jsp 코드는 html과 비슷한데 jsp는 서버 내부에서 서블릿으로 변환되서 보여준다.

 

-<%~~%>

>> 자바 코드를 입력할 수 있다.

 

-<%=~~%>

>>자바 코드를 출력할 수 있다.

 

*jsp 코드를 보면 서블릿 코드와 비슷한데 html을 중심으로 하고, 자바 코드를 부분부분 입력했다 (<% ~ %>) 를 사용해서 자바 코드를 넣어줄 수 있다*

jsp를 사용해서 html안에 자바코드를 넣어서 동적으로 하기는 했지만 jsp에 회원을 저장하는 비즈니스 로직도 있고 화면을 보여주는 html코드도 있다.

 

>> 하지만 이는 너무 많은 역할을 담당하고 있다. 비즈니스 로직은 비즈니스 로직만 처리하고 html로 화면을 그리는것은 화면만 그리는데에 집중 할 수 있게 해야한다.

 

>>그래서 MVC패턴을 사용한다.


MVC (Model View Controller)

Controller

>> HTTP 요청을 받아서 파라미터를 검증하고, 비즈니스 로직을 실행, 그리고 뷰에 전달할 결과를 데이터를 조회해서 모델에 담는다. (Servlet)

Model

>> 뷰에 출력할 데이터를 담아두고, 뷰가 필요한 데이터를 모두 모델에 담아서 전달해주는 덕분에 뷰는 화면을 랜더링 하는 일에만 집중 한다.

View

>> 모델에 담겨있는 데이터를 사용해서 화면을 그린다. (Jsp)

 

 

* Controller에 비즈니스 로직까지 둘 수 있지만 또 이렇게 되면 컨트롤러가 너무 많은 역할을 담당하기 때문에 비즈니스 로직, 데이터 접근은 Service라는 계층을 별도로 만들어서 처리한다. 말 그대로 컨트롤을 해주는 부분이라고 할 수 있다. 그리고 컨트롤러는 비즈니스 로직이 있는 서비스를 호출하는 역할을 담당한다.*

* Model은 httpServletRequest 객체를 사용하는데 request 내부에 데이터 저장소를 가지는데 request.setAttribute() , request.getAttribute()을 사용해서 보관, 조회를 한다.

MVC 패턴

필기하면서 들은거라 그림이 좀 지저분하다.

위 그림에서 말하는  

비즈니스 로직 = 회원을 저장하고, 주문 로직 => 즉, 서비스나 리포지토리에 저장한다고 볼 수 있다.

컨트롤러 로직 => 파라미터를 꺼내고 고객이 제대로 요청한건지, 맞는지 확인, 잘못된건 오류내어 튕겨줌 => 만약 맞다면 서비스나 리포지토리을 호출해서 그 안에 있는 주문하거나 하는 로직을 실행한다.

 

 


MVC 활용(회원 등록 폼 컨트롤러 / 뷰)

​서블릿을 컨트롤러로 사용하고, JSP를 뷰로 사용해서 MVC 패턴을 적용해보자.

 

-Model은 HttpServletRequest 객체를 사용한다.

-request는 내부에 데이터 저장소를 가지고 있는데, request.setAttribute() , request.getAttribute() 를 사용하면 데이터를 보관하고, 조회할 수 있다.

 

-회원 등록 폼 컨트롤러에서는 viewPath = "/WEB-INF/..." 로 jsp 경로를 넣어준다.

ex) String viewPath = "/WEB-INF/views/new-form.jsp";

-request.getRequestDispathcher(viewPath)

-dispatcher.forward()

>>다른 서블릿이나 JSP로 이동할 수 있게 해준다 (서버 내부에서 다시 호출이 발생)

 

*/WEB-INF 로 경로를 잡으면 이 경로안에 JSP가 있으면 외부에서 직접 JSP를 호출 할 수 없다 (url에 경로대로)

>> 항상 컨트롤러를 통해서 JSP를 호출 해야한다*

 

>> redirect vs forward

> 리다이렉트는 실제 클라이언트(웹 브라우저)에 응답이 나갔다가, 클라이언트가 redirect 경로로 다시 요청한다. 따라서 클라이언트가 인지할 수 있고, URL 경로도 실제로 변경된다.

> 반면에 포워드는 서버 내부에서 일어나는 호출이기 때문에 클라이언트가 전혀 인지하지 못한다.

 

-회원 등록 폼 뷰에서는  <form action="save" method="podt"> 와 같이 form의 action을 보면 상대경로( / 로 시작X)인 것을 확인할 수 있다.

 

-이렇게 상대경로를 사용하면 폼 전송시 현재 URL이 속한 계층 경로 + save가 호출된다.

-현재 계층 경로: /servlet-mvc/members/

-결과: /servlet-mvc/members/save

 

MVC 활용 (회원 저장 컨트롤러 / 뷰, 회원 목록 조회 컨트롤러 / 뷰)

@WebServlet(name = "mvcMemberSaveServlet", urlPatterns = "/servlet-mvc/members/save")
public class MvcMemberSaveServlet extends HttpServlet {
	private MemberRepository memberRepository = MemberRepository.getInstance();
 	@Override
 	protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
 		String username = request.getParameter("username");
 		int age = Integer.parseInt(request.getParameter("age"));
 		Member member = new Member(username, age);
 		System.out.println("member = " + member);
 		memberRepository.save(member);
 		//Model에 데이터를 보관한다.
 		request.setAttribute("member", member);
 		String viewPath = "/WEB-INF/views/save-result.jsp";
 		RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
		dispatcher.forward(request, response);
 	}
}

-회원 저장 컨트롤러에서는 MvcMemberSaveSerlet 클래스를 만들어서 정보들을 담아서 뷰에 다시 넘겨줘야한다.

-.getParameter("age") ...를 통해 username , age 를 알아내고 new member(username,age) 로 객체를 생성한다.

-request.setAttribute("member",member); key = "member" 이고 위에서 만든 member 객체를 넘겨주는 것이다. (뷰로)

-그때 위에서 한 것 처럼 "/WEB-INF/.../save-result.jsp" 경로를 저장해두고

-.getRequestDispatcher(path) 로 넘겨주고

-.forward(requet,response)로 뷰에 넘겨주는 것이다.

 

-회원 저장 뷰에서는 .getAttribute()로 데이터를 꺼낼 수 있지만, JSP에서 ${} 문법을 제공해서 더 쉽게 데이터를 가져올 수 있다.

  • id=${member.id}
  • username=${member.username}
  • age=${member.age}

 

-회원 목록 조회 컨트롤러에서도 똑같이 위에 방법과 비슷하고 memberRepository.findAll() 을 사용해서 List를 넘겨줬다.

 

-회원 목록 조회 뷰에서는 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> 를 선해서 c태그를 이용해 request에 담긴 리스트 데이터를 쉽게 꺼내서 출력할 수 있다. members 리스트에서 member 를 순서대로 꺼내서 item 변수에 담고, 출력하는 과정을 반복한다

-MVC패턴을 사용하니까 컨트롤러는 컨트롤러 뷰는 화면만 랜더링하는 역할을 확실하게 구분하는것이 보였다.


한계점

-getRequestDispather 이나 .forward 처럼 컨트롤러에서 중복되는 코드가 많았다.

-HttpServletRequest , HttpServletResponse 등 사용하지 않는 객체들도 있고 이런 것들은 테스트 케이스를 작성하기도 어렵다.

-위에서 중복되는 코드를 메서드로 뽑아서 하면 될 것 같지만 어차피 메서드도 항상 호출을 해야한다. 만약 실수로 호출하지 않으면 문제가 또 발생한다.

 

>>이러한 한계점을 해결하기 위해서는 컨트롤러가 호출되기 전에 공통 기능을 처리 해야하는데 이러한 기능을 하는 것이 프론트 컨트롤러이다.

 

>>프론트 컨트롤러(Front Controller) 패턴이 먼저 실행되서 공통 기능을 수행하고 다른 컨트롤러로 연결해준다. (입구를 하나로 만드는 것)

 

*스프링 MVC의 핵심도 프론트 컨트롤러에 있다*

 

728x90
728x90

※이 내용은 강의 내용을 정리하고자 하는 내용이라 기본 정의나 사용법은 써놓지 않았다.

 

만약 서블릿에 대해 알고 싶다면 여기 를 참고하자!

서블릿 특징

서블릿 등록

-class 하나를 생성하고 HttpServlet 이라는 인터페이스를 상속받고 service 라는 메소드를 Override한다.

-그리고 class 위에 @WebServlet(name= , urlPatterns=) 을 추가하고 각 변수에 원하는 값을 넣는다.

 

>>service 메소드는 http 요청을 통해 urlPatterns에 있는 매핑된 url이 호출되면 자동으로 실행이 된다.

-그리고 service 메소드에 HttpServletRequest , HttpServletResponse 각 객체들이 파라미터로 들어오는데 이걸로 우리가 http 응답 메시지를 설정 할 수 있다.

 

>> response.setContentType : 타입 설정 or response.setCharacterEncoding : 인코딩 방식 설정 or response.getWirter().write() : 메시지 작성

 

* resources/application.properties 파일에 loggin.level.org.apache.coyote.http11=debug 라는 설정을 추가하면 http 요청 메시지를 로그로 확인 가능*

위에서 한 방식을 정리하면 스프링 부트로 생성하면 내장되어 있는 톰캣 서버가 있는데 이 톰캣 서버가 서블릿 객체를 생성해준다. http 요청 메시지가 들어오면 이를 기반으로 HttpServletRequest , HttpServletResponse 각 객체가 생성되고 이 객체로 http 응답을 생성할 수 있게 된다.

 

HttpServletRequest 역할 : HTTP 요청 메시지를 개발자가 직접 파싱해서 사용해도 되지만 이는 매우 불편하기 때문에 이 파싱을 대신해준다. 그 결과를 객체에 담아서 제공한다.

-로그인 상태 같은 것들을 유지해주는 기능인데 이런 세션 관리 기능도 제공한다.

-임시 저장소 기능도 한다.

-request.getMethod() request.getProtocol() , request.getRequestURI() request.getCookies() 등등으로 HTTP의 정보들을 알 수 있다!


HTTP 요청 데이터

1. GET - 쿼리파라미터

-쿼리 파라미터는 ?로 시작하고 추가 파라미터는 &로 구분한다.

-request.getParameterNames().asIterator() .forEachRemaining(paramName -> System.out.println(paramName + " = " + request.getParameter(paramName)));

>>전체 파라미터 조회 하는 방법

>>단일 파라미터 조회 하는 방법 : request.getParameter("파라미터이름")

-username=spring1&username=spring2 이렇게 이름이 같은 파라미터를 조회 할 때는 : request.getParameterValues("username") 이렇게 가져와야한다.

 

2. POST - HTML Form -> 메시디 바디에 쿼리 파라미터 형식으로 전달

-(content-type : application/x-www-form-urlencoded (메시지 바디 타입을 알려줌) message body : 쿼리 파라미터) 의 형식으로 http 메시지를 만든다.

-서버 입장에서는 GET , POST 둘 다 어차피 같은 쿼리 파라미터이기 때문에 get에서 파라미터 조회 하는 방법과 동일하다.

*HTML Form 형식으로 데이터를 보낼때는 content-type이 꼭 있어야 한다. Message body 에 있는 데이터가 어떤 형식인지 알려줘야 하기 때문이다. 하지만 url 쿼리 파라미터(get방식)로 보내면 메시지 바디를 사용하지 않기 때문에 content-type이 없다*

*postman을 사용해서 HTML Form을 만들지 않고 HTML Form에 쿼리 파라미터를 날릴 수 있다*

 

3. HTTP message body에 데이터를 직접 담아서 요청 -> HTTP API에서 주로 사용, JSON , XML , TEXT

 

-데이터 읽는 방법

 

<text>

-inputStream = request.getInputStream() -> 메시지 바디 내용을 바이트 코드로 얻을 수 있다.

-mb = StreamUtils.copyToString(inputStream,StandardCharsets.UTF_8) -> 위에서 얻은 바이트 코드를 문자로 바꿔준다(UTF-8형식으로)

>>postman 으로 raw (text) 형식으로 메시지를 보내면 인텔리j에서 출력을 확인 할 수 있었다.

 

<json>

-inputStream = request.getInputStream()

-mb = StreamUtils.copyToString(inputStream,StandardCharsets.UTF_8)

위에 text 읽기 부분과 동일하게 읽어올 수 있다. 이렇게 읽어 오면 json 형태 그대로 가져온다 {key : value ...}

*json도 어차피 문자이기 때문*

json은 보통 객체로 바꿔서 사용하기 때문에 파싱 할 수 있게 객체를 하나 생성해줘야 한다

(클래스 생성 : HelloData(필드 : username,age))

-ObjectMapper objectMapper = new ObjectMapper() -> Jackson 라이브러리에 있는 것인데 jackson이 json 라이브러리 이다.

-helloData = objectMapper.readValue(messageBody,HelloData.class) 를 하면 helloData 라는 객체로 age, username 을 가져올 수 있다.

>>postman 으로 raw(json) 형식으로 메시지를 보내면 인텔리제이에서 출력을 확인 할 수 있었다.

HttpServletResponse 역할 : HTTP 응답 메시지 생성 (HTTP 응답 코드 지정, 헤더 생성 , 바디 생성)

>>개발자들이 직접 HTTP 응답 메시지를 만들기에는 어렵기 때문에 메서드에 필요한 값들을 넣으면 HTTP 응답 메시지를 생성해준다.

[status-line] -> response.setStatus(SC_OK...) 상태 코드 설정

[response-headers]

response.setHeader(response.setHeader("Content-Type","text/plain") or response.setContentType("text/plain") .setCharacterEncoding("utf-8")

response.setHeader("Cache-Control","no-cache,no-store,must-revalidate"); //캐시 무효화

response.setHeader("my-header","hello"); header부분 이런식으로 추가 가능

[message-body] -> PrintWriter writer = response.getWriter(); writer.println(); message-body 추가 가능

-cookie 편의 메서드 : Cookie cookie = new Cookie("my cookie","good"); cookie.setMaxAge() response.addCookie(cookie)

>>이런식으로 쿠키를 만들 수 있음

 

-redirect 편의 메서드 : response.sendRedirect("/path") 이렇게 하면 redirect이 일어난다.


HTTP 응답 데이터

1. 단순 텍스트 응답 : 위에서 writer.println("ok") 이런 형식으로 하면 된다.

2. HTML 응답

-Content-Type 과 CharacterEncoding을 지정해줘야 한다. .setContentType("text/html") .setCharacterEncoding("utf-8")

-writer.println("<html>) ... writer.println("</html>");

 

>>이런식으로 자바 코드로 html을 작성하면 HTML 응답이 가능하다.

3.HTTP API - JSON

-json 형식으로 보내기 때문에 .setContentType("application/json") 으로 지정하고 .setCharacterEncoding("utf-8") 도 추가해서 한국어가 꺠지지 않도록 한다.

-위에서 봤던 Jackson 라이브러리를 사용하면 된다.

-HelloData 의 .setName , .setAge 를 해줘서 이름과 나이를 가지고 있는 객체를 만든다.

-여기 있는 객체를 json 형태로 만들어야 한다 {"key" : value , ... }

 

>>result = objectMapper.writeValueAsString(객체)

 

*json도 문자이기 때문에 위에 있는 코드 처럼 하면 json 형태로 변한다*

-reponse.getWriter().wirte(result) 하면 화면에 json 형태의 데이터가 보인다!

728x90
728x90

스프링 MVC강의를 듣고 배운 내용을 정리해 볼려고 한다.

 

웹 서버(Web Server)와 웹 애플리케이션 서버(WAS - Web Application Server) 

 

웹 서버(Web Server)란?

  • HTTP 기반으로 동작 , 정적 리소스 제공
  • 예) NGINX, APACHE

웹 애플리케이션 서버(WAS - Web Application Server)란?

  • HTTP 기반으로 동작
  • 웹 서버 기능 포함 + 정적 리소스 제공
  • 서블릿, HTTP API , JSP , Spring MVC ->프로그램 코드를 실행해서 애플리케이션 로직 수행
  • 예) 톰캣(Tomcat) Jetty, Undertow

웹 서버와 웹 애플리케이션 서버의 차이

웹 서버는 정적 리소스 파일 제공, WAS는 애플리케이션 로직 제공한다.

사실 이 두개의 용어와 경계도 모호하다.

웹 서버도 프로그램을 실행하는 기능을 포함하기도 하고 웹 애플리케이션 서버도 웹 서버의 기능을 제공한다.

그 중 WAS는 애플리케이션 코드를 실행하는데 더 특화 되어 있다.

 

그럼 WAS만 사용하면 되지 왜 굳이 웹 서버까지 사용할까?

-실제 웹 시스템 구성은 WAS와 DB만으로도 구성이 가능하다.

 

하지만 이렇게 WAS에 애플리케이션 로직, 정적 리소스를 모두 제공하면 너무 많이 역할을 담당하기에 서버 과부하의 우려가 있다. 또한 WAS 장애시 오류 화면도 노출이 불가능하다. 또한 애플리케이션 로직은 매우 비싼편인데 정적 리소스 때문에 수행하기에 어려움도 있다.

 

>> 그렇기 때문에 Web Server 에서 정적 리소스를 담당하고, 만약 애플리케이션 로직같은 동적인 처리가 필요하면 WAS에 요청을 한다. 이렇게 되면 효율적으로 리소스 관리를 할 수 있고 만약 정적 리소스가 많이 사용되면 Web Server를 증설하고 애플리케이션 리소스가 많이 사용되면 WAS를 증설하면 된다! WAS가 장애가 발생하면 Web Server에서 오류화면 HTML을 제공할 수도 있다.


서블릿

-만약 클라이언트에서 username, age를 post 방식으로 서버에 보낸다고 가정해보자. 그러면 서버에서 처리해야 하는 업무가 정말 많다.

HTTP 요청 메시지 파싱하기 , HTTP 메시지 바디 파싱,비즈니스 로직 실행(데이터베이스에 저장요청), HTTP 응답 메시지 생성 .... 이외에도 많다.

근데 여기서 의미 있는 비즈니스 로직은 데이터베이스에 저장요청 하는것 하나 뿐이다. 서블릿이 비즈니스 로직 이외에 다른 업무를 다 처리해준다.

 

서블릿은 http 요청 정보를 편리하게 사용 할 수 있는 httpservletRequest , http 응답 정보를 편리하게 제공하는 httpservletResponse 들이 있어서 개발자가 http 스펙을 매우 편리하게 사용한다.

 

HTTP 요청시

• WAS는 Request, Response 객체를 새로 만들어서 서블릿 객체 호출

• 개발자는 Request 객체에서 HTTP 요청 정보를 편리하게 꺼내서 사용

• 개발자는 Response 객체에 HTTP 응답 정보를 편리하게 입력

• WAS는 Response 객체에 담겨있는 내용으로 HTTP 응답 정보를 생성

 

 

서블릿 컨테이너

-톰캣처럼 서블릿을 지원하는 WAS를 서블릿 컨테이너라고 한다.

-서블릿 컨테이너는 서블릿 객체를 생성,초기화 등등 생명주기를 관리한다.

-서블릿 객체는 싱글톤으로 관리한다.

-동시 요청을 위한 멀티 쓰레드 처리를 지원한다.

 

그러면 서블릿 객체는 누가 호출할까?

 

>>쓰레드가 호출 해준다.

쓰레드

-애플리케이션 코드를 하나하나 순차적으로 실행하는 것을 쓰레드라고 한다.

-자바 메인 메서드를 실행하면 main함수를 실행하는데 main이라는 이름의 쓰레드가 실행되는 것

-쓰레드가 없다면 자바 애플리케이션 실행이 불가능

-동시 처리가 필요하다면 쓰레드를 추가로 생성하면 된다.

 

만약 쓰레드가 하나인데 요청이 동시에 들어오게 되면 두 개의 요청이 다 죽어버릴 수 있다.

 

그렇다면 요청 마다 쓰레드를 생성하면 되지 않을까?

>> 쓰레드는 생성 비용이 매우 비싸며 쓰레드 생성에 제한이 없다. (CPU,메로리 임계점이 넘어버리면 서버가 죽는다)

그래서 쓰레드 풀이라는게 있다.

 

쓰레드 풀

쓰레드 풀이라는 곳에 쓰레드를 담아 놓는다. 필요할 때마다 쓰레드 풀에서 꺼내서 쓰면 되는 것 , 사용이 끝나면 쓰레드 풀에 해당 쓰레드를 반납한다.

최대 쓰레드가 모두 사용중이어서 쓰레드 풀에 쓰레드가 없으면 기다리는 요청을 거절하거나 대기하도록 할 수 있다.

 

>> 쓰레드를 생성하는 비용이 절약되고 응답도 빠르다. 이미 최대치가 정해져있으므로 너무 많은 요청이 들어와도 서버는 안전하다.

그렇다면 쓰레드 풀에 쓰레드 수를 어떻게 설정하면 될까?

값이 너무 낮으면 동시 요청이 많으면 지연이 되고, 값이 너무 높으면 CPU,메모리 임계점이 초과로 서버가 죽음.

 

>>적절한 값을 설정해야 한다. 정해진 갯수는 없고 성능 테스트 (네이버에서 만든 nGrinder 툴) 꼭 해야한다 (최대한 실제 서비스와 유사하게 성능 테스트)

하지만, 이러한 멀티 쓰레드는 WAS에서 지원해준다. 그렇기 때문에 개발자는 개발할 때 싱글 쓰레드를 생각하고 소스 코드를 개발하면 된다!

 

*멀티 쓰레드 이기 때문에 싱글톤 객체(서블릿,스프링 빈)는 주의해서 사용*


HTML , HTTP API

정적 리소스

-고정된 HTML 파일, CSS, JS, 이미지 등을 제공

-주로 웹 브라우저

HTML 페이지(동적)

-동적으로 필요한 HTML 파일 생성해서 전달

>> 주문 내역을 요청하면 WAS가 DB에서 주문 정보를 조회하고 이 정보를 기반으로 동적으로 HTML을 생성해서(JSP,타임리프) 클라이언트에게 보내준다.

HTTP API

-HTML이 아닌 데이터를 전달한다 (주로 JSON 형식 사용)

-앱 클라이언트나 웹 클라이언트와 HTTP API로 통신하면 WAS에서는 데이터만 전달해주고 그 데이터를 기반으로 클라이언트에서 별도로 처리한다.

-WAS 서버 끼리 통신할때도 굳이 HTML이 필요 없으므로 HTTP API를 사용한다.

-React, Vue.js 같은 웹 클라이언트


서버사이드 렌더링, 클라이언트 사이드 렌더링

 

SSR(서버 사이드 렌더링) 

-서버에서 최종 HTML을 생성해서 클라이언트에 전달

CSR(클라이언트 사이드 렌더링)

-HTML 결과를 자바스크립트를 사용해 웹 브라우저에서 동적으로 생성 ex)구글 지도, Gmail 등등


자바 웹 기술 역사

1. 서블릿 : HTML 생성이 어려움

2. JSP

-HTML 생성은 편리하지만, 비즈니스 로직 까지는 너무 많은 역할을 담당

3. 서블릿, JSP 조합 MVC 패턴 사용

4. MVC 프레임 워크가 많이 나옴

5. 애노테이션 기반의 스프링 MVC (이 방법으로 거의 통일)

-@Controller

6. 스프링 부트의 등장

-스프링 부트는 서버를 내장


자바 뷰 템블릿 역사

 

뷰 템플릿

-HTML을 편리하게 생성하는 뷰 기능이다.

 

1. JSP - 속도가 느림, 기능 부족

2. 프리마커 - 속도 문제 해결, 다양한 기능

3. 타임리프 - 최선의 선택

728x90
728x90

@RequestMapping 애너테이션은 어디에 사용할 수 있을까?

 

@RequestMapping 애너테이션은 클라이언트의 요청Controller의 핸들러 메서드를 매핑하기 위해서 사용하는 애너테이션이다.

즉, @RequestMapping 애너테이션에 정의된 URI에 매치되는 요청을 처리할 수 있도록 해준다.

@RequestMapping 애너테이션은 클래스 레벨에 사용할 수 있고, 메서드 레벨에 사용할 수 있다.
일반적으로 공통 URI는 클래스 레벨에 정의하고, 핸들러 메서드별로 달라지는 URI는 각각의 핸들러 메서드에 정의한다.


@RequestMapping 애너테이션 단축 표현

 

@RequestMapping 애너테이션은 HTTP Method 에 해당하는 단축 표현들을 주로 사용한다.

 

  • @GetMapping: HTTP Get Method에 해당하는 단축 표현으로, 서버의 리소스를 조회할 때 사용
  • @PostMapping: HTTP Post Method에 해당하는 단축 표현으로, 서버에 리소스를 등록(저장)할 때 사용
  • @PutMapping: HTTP Put Method에 해당하는 단축 표현으로, 서버의 리소스를 수정할 때 사용. 리소스의 모든 정보를 수정할 때 사용.
  • @PatchMapping: HTTP Put Method에 해당하는 단축 표현으로, 서버의 리소스를 수정할 때 사용. 리소스의 일부 정보만 수정할 때 사용.
  • @DeleteMapping: HTTP Delete Method에 해당하는 단축 표현으로, 서버의 리소스를 삭제할 때 사용.

 

※ 일반적으로 클래스 레벨에는 @RequestMapping 애너테이션을 사용하고, 메서드 레벨에서는 단축 표현을 사용하는 것을 권장하고 있다.


@RequestMapping 애너테이션에 사용되는 Attribute

@RequestMapping 애너테이션에 사용되는 Attribute는 생각보다 많이 사용되지 않는다. 하지만 필요할 경우 그때 그때 설정해서 사용할 수 있으므로 아래 링크를 확인하시고 어떤 Attribute가 있는 살펴보면 좋다.!!

728x90
728x90

Java의 직렬화(Serialize)란?

Java를 공부하고 Spring을 쓰다보면 계속해서 Serialize를 상속받은 클래스들을 볼 수 있었다.

도대체 직렬화가 뭔지 궁금해서 정리해본다.


- 직렬화(Serialize)

  • 자바 시스템 내부에서 사용되는 Object 또는 Data를 외부의 자바 시스템에서도 사용할 수 있도록 byte 형태로 데이터를 변환하는 기술
  • JVM(Jaava Virtual Machine 이하 JVM)의 메모리에 상주(힙 또는 스택)되어 있는 객체 데이터를 바이트 형태로 변환하는 기술

- 역직렬화(Deserialize)

  • byte로 변환된 Data를 원래대로 Object나 Data로 변환하는 기술을 역직렬화(Deserialize)라고 부릅니다.
  • 직렬화된 바이트 형태의 데이터를 객체로 변환해서 JVM으로 상주시키는 형태

- 직렬화 조건

  • java.io.Serializable인터페이스를 상속받은 객체는 직렬화할 수 있는 기본조건이다.
public calss Member implements Serializable{
  private String name;
  private String email;
  private int age;

  public Member(String name, String email, int age){
    this.name = name;
    this.email = email;
    this.age = age = age;
  }
  @Override
  public String toString(){
    return String.format("Member{name='%s', email='%s', age='%s'}", name,email)
  }
}

- 직렬화(Serialize) 방법

  • java.io.ObjectOutputStream 를 사용하여 직렬화를 진행한다.
public static void main(String[] args){
    Member member = new Memer("김배민", "deliverykim@baemin.com", 25);
  byte[] serializeMember;
  try(ByeArrayOutputStream baos= new ByteArrayOutputStream()){
    try(ObjectOutputStream oos= new ObjectOutputStream(baos)){
      oos.writeObject(member);
      // serializedMember -> 직렬화된 member 객체
      serializedMember = baos.toByteArray();
    }
  }
  //바이트 배열로 생성된 직렬화 데이터를 base64로 변환
  System.out.println(Base64.getEncoder().encodeToString(serializedMember));
}

- 역직렬화(Deserialize)조건

  • 직렬화 대상이 된 객체의 클래스가 클래스 패스에 존재해야하며 import 되어 있어야 한다.
  • 중요한 점은 직렬화와 역직렬화를 진행하는 시스템이 서로 다를 수 있다는 것을 반드시 고려해야한다.
  • 자바 직렬화 대상 객체는 동일한 serialVersionUID 를 가지고 있어야 한다.
    • private static final long serialVersionUID= 1L;
  • serialVersionUID 이 왜 필요한지 자세한 내용은 아래에 추가하였다.

- 역직렬화(Deserialize)방법

  • java.io.ObjectInputStream 를 사용하여 역직렬화를 진행합니다.
public static void main(String[] args){
  //직렬화 예제에서 생성된 base64 데이터
  String base64Member="...생략";
  byte[] serializedMember = Base64.getDecoder().decode(base64Member);
  try(ByteArrayInputStream bais = new ByteArrayInputStream(serializedMember)){
    try(ObjecInputStream ois = new ObjectInputStream(bais)){
      //역직렬화된 member객체를 읽어온다.
      Object objectMember = ois.readObject();
      Member member = (Member)objectMember;
      System.out.println(member);
    }
  }
}

그렇다면, 자바의 직렬화는 왜 사용할까?

  • 복잡한 데이터 구조의 클래스의 객체라도 직렬화 기본 조건만 지키면 큰 작업 없이 바로 직렬화, 역직렬화가 가능하다.
  • 데이터 타입이 자동으로 맞춰지기 때문에 관련 부분을 큰 신경을 쓰지 않아도 된다.

그럼, 어디에 사용될까?

- 서블릿 세션(Servlet Session)

  • 세션을 서블릿 메모리 위에서 운용한다면 직렬화를 필요로 하지 않지만, 파일로 저장하거나 세션 클러스터링, DB를 저장하는 옵션 등을 선택하게 되면 세션 자체가 직렬화가 되어 저장되어 전달된다.

- 캐시(Cache)

  • Ehcache, Redis, Memcached라이브러리 시스템을 많이 사용한다.

- 자바 RMI(Remote Method Invocation)

  • 원격 시스템 간의 메시지 교환을 위해서 사용하는 자바에서 지원하는 기술

그럼, 자바의 직렬화 단점이 있을까?

- 역직렬화시 클래스 구조 변경 문제

  • 기존 멤버 클래스를 직렬화한다.
  • public class Member implements Serializable{ private String name; private String email; private int age; //생략 }
  • 직렬화한 Data
  • rO0ABXNyABp3b293YWhhbi5ibG9nLmV4YW0xLk1lbWJlcgAAAAAAAAABAgAESQADYWdlSQAEYWdlMkwABWVtYWlsdAASTGphdmEvbGFuZy9TdHJpbmc7TAAEbmFtZXEAfgABeHAAAAAZAAAAAHQAFmRlbGl2ZXJ5a2ltQGJhZW1pbi5jb210AAnquYDrsLDrr7w=
  • 멤버 클래스에서 속성을 추가한다.
    • 직렬화한 Data를 역직렬화하면 어떤 결과가 나올까? -> 결과는 java.io.InvalidClassException 이 발생한다.
    • 위에서 언급했던 것처럼 직렬화하는 시스템과 역직렬화하는 시스템이 다른 경우에 발생하는 문제이다.
    • 각 시스템에서 사용하고 있는 모델의 버전 차이가 발생했을 경우에 생기는 문제이다.
  • public class Member implements Serializable{ private String name; private String email; private int age; //phone 속성 추가 private String phone; }

- 해결하기 위해서는

  • 모델의 버젼간의 호환성을 유지하기 위해서는 SUID(serialVersionUID) 를 정의해야 한다.
  • Default는 클래스의 기본 해쉬값을 사용한다.

- 또 다른 문제

  • String->StringBuilder,int -> long으로 변경해도 역직렬화에서 Exception이 발생한다.
  • 자바 직렬화는 상당히 타입의 엄격하다는 것을 알 수 있다.
  • 멤버 변수가 빠지게 된다면 Exception대신 null 값이 들어가는 것을 확인할 수 있다.

- 직렬화 Data Size 문제

{"name":"김배민","email":"deliverykim@baemin.com","age":25}
serializedMember (byte size = 146)
json (byte size = 62)
  • 아주 간단한 객체의 내용도 2배이상의 차이를 확인할 수 있다.
  • 일반적인 메모리기반의 Cache에서는 Data를 저장할 수 있는 용량의 한계가 있기 때문에 Json 형태와 같은 경량화된 형태로 직렬화하는 것도 좋은 방법이다.

정리하며...

  • 외부 저장소로 저장되는 데이터는 짧은 만료시간의 데이터를 제외하고 자바 직렬화를 사용을 지양한다.
  • 역질렬화시 반드시 예외가 생긴다는 것을 생각하고 개발한다.
  • 자주 변경되는 비즈니스적인 데이터를 자바 직렬화를 사용하지 않는다.
  • 긴 만료 시간을 가지는 데이터는 JSON 등 다른 포맷을 사용하여 저장한다.
728x90
728x90

Controller의 핸들러 메서드는 다양한 유형의 Argument(인수)를 지원한다.

그 중에서 REST API 애플리케이션에서 자주 사용되는 유형의 Argument를 간단히 살펴보자! 

만약, 김영한님의 스프링 강의를 들었다면 더더욱 익숙할 것이다.

Method Argument 설명
@RequestParam 쿼리 파라미터, form-data 등의 Servlet request Parameter를 바인딩 해야 할 경우 사용
@RequestHeader request header를 바인딩해서 header의 key/value에 접근 가능
@RequestBody request body를 읽어서 지정한 Java 객체로 deserialization(역직렬화) 해준다
@RequestPart 'multipart/form-data' 형식의 request 데이터를 part 별로 바인딩 가능
@PathVariable @RequestMapping 에 패턴 형식으로 정의된 URL의 변수에 바인딩 가능
@MatrixVariable URL 경로 세그먼트 부분에 key/value 쌍으로 된 데이터에 바인딩 가능
HttpEntity request header와 body에 접근할 수 있는 컨테이너 객체 사용
javax.servlet.ServletRequest, 
javax.servlet.ServletResponse
로우 레벨의 ServeletRequest와 ServletResponse의 정보가 필요할 때 사용

※ 역직렬화란? byte로 변환된 Data를 원래대로 Object나 Data로 변환하는 기술


- @RequestParam

@RequestParam은 클라이언트 쪽에서 쿼리 파라미터, form data, x-www-form-urlencoded 등의 형식으로 전달되는 요청 데이터를 바인딩해서 사용할 수 있도록 해준다.

@Controller
@RequestMapping("/coffees")
public class CoffeeController {
    // ...
    @GetMapping
    public Coffee getCoffee(@RequestParam("coffeeId") int coffeeId) {
       Coffee coffee = coffeeService.getCoffee(coffeeId);
        return coffee;
    }
    // ...
}

 

- @RequestHeader

@RequestHeader는 HTTP request header의 key/value 쌍의 데이터에 접근할 수 있도록 해준다.

@GetMapping("/coffee")
public void getCoffee(
        @RequestHeader("Content-Type") String contentType, 
        @RequestHeader("Content-Length") long contentLength) { 
    //...
}​

 

- @RequestBody

@RequestBody는 HTTP request body를 읽어서 지정한 Java 객체로 변환(deserialization) 해준다.

@PostMapping("/coffees")
public void handle(@RequestBody Coffee coffee) {
    // ...
    coffeeService.save(coffee)
}​

※ @RequestBody는 특히 리소스를 등록하는 @PostMapping에서 주로 사용된다.

 

- @RequestPart

'multipart/form-data' 형식의 request 데이터를 part 별로 바인딩할 수 있도록 해준다.

@Controller
public class CoffeeController {
    @PostMapping("/coffees")
    public String postCoffee(@RequestPart("coffee") Coffee coffee,
           @RequestPart("file") MultipartFile photoFile) {

        if (!photoFile.isEmpty()) {
            byte[] bytes = file.getBytes();
            // TODO 파일 저장
            return "success";
        }
        return "failed";
    }
}​

위 코드에 대해 설명하자면, 예를 들어 클라이언트 쪽에서 커피 사진이 포함된 multpart/form-data 타입의 커피 정보를 전송한다면,   form data를 part별로 나누어서 전달 받을 수 있다.

 

- @PathVariable

@PathVariable은 @RequestMapping 에 패턴 형식으로 정의된 URL의 변수에 바인딩할 수 있도록 해준다.

@GetMapping("/members/{member-id}/coffees/{coffee-id}")
public Pet getCoffee(@PathVariable("member-id") Long memberId,
@PathVariable("coffee-id") Long coffeeId) {
    // ...
}​

위 코드와 같이 '{variable name}'과 같은 형태의 URL 변수가 여러개 있을 경우, @PathVariable을 핸들러 메서드에 순차적으로 추가해서 URL 변수의 값을 받을 수 있다.

 

- @MatrixVariable

@MatrixVariable은 URL 경로 세그먼트 부분에 key/value 쌍으로 된 데이터에 바인딩할 수 있도록 해준다.

@MatrixVariable의 사용은 예제 코드를 보자.

// 클라이언트 요청 URL
// GET /coffees/42;q=11;r=22

@GetMapping("/coffees/{coffeeId}")
public void getCoffee(@PathVariable("coffee-id") coffeeId, 
                      @MatrixVariable int q,
                      @MatrixVariable int r) {

    // coffeeId == 42
    // q == 11
    // r == 22
}​

만약에 클라이언트 쪽 요청 URL이 '/coffees/42;q=11;r=22'와 같다면 세미콜론(;) 뒤에 오는 q, r을 변수로 보고 각 변수의 값을 위와 같이 핸들러 메서드에서 전달 받을 수 있다.

 

- HttpEntity

클라이언트 요청 body와 header 정보를 HttpEntity 객체를 통해 한번에 전달 받을 수 있다.

물론 HttpServletRequest 객체를 통해서도 요청 body와 header에 접근할 수 있지만 HttpEntity 객체를 통해 조금 더 간단하게 두 정보에 접근할 수 있다.

@RestController
@RequestMapping("/v1/coffees")
public class HttpEntityExample {
    @PostMapping
    public Coffee postCoffee(HttpEntity<Coffee> entity) {
        Coffee coffee = entity.getBody();
        HttpHeaders headers = entity.getHeaders();
        
        // coffee 정보 저장
        return coffee;
    }
}​
728x90
728x90

Handler 용어의 의미

우리가 핸들(Handle) 이라고 하면 일반적으로 자동차의 핸들을 제일 먼저 떠올릴 수 있는데, 자동차의 핸들은 운전자가 직접 핸들을 움직이면서 직접적으로 자동차의 주행을 처리하는 역할을 한다.

 

Spring MVC에서는 자동차의 핸들과 마찬가지로 클라이언트의 요청을 처리하는 처리자 Handler라고 한다.

 

그렇다면 Spring MVC에서 Handler는 누구일까?


Spring MVC에서의 요청 Handler는 바로 우리가 작성하는 Controller 클래스를 의미한다. 그리고 Controller 클래스에 있는 @GetMapping@PostMapping 같은 애너테이션이 붙어 있는 메서드들을 핸들러 메서드라고 한다.

 

HandlerMapping이란 의미는 결국 사용자의 요청이 요청을 처리하는 Handler매핑해주는 역할을 하는 것이다.

 

그렇다면, 사용자의 요청과 Handler 메서드의 매핑은 어떤 기준으로 이루어질까?

 

@GetMapping(”/coffee”) 처럼 HTTP Request Method(GET, POST 등)와 Mapping URL을 기준으로 해당 Handler와 매핑이 되는데, Spring 에서는 여러가지 유형의 HandlerMapping 클래스를 제공하고 있다.

 

실무에서는 Spring에서 디폴트로 지정한 ‘RequestMappingHandlerMapping’을 대부분 사용한다고 했고 원한다면 얼마든지 HandlerMapping 전략을 바꿀 수 있다. 


Adapter의 의미

Java에서 사용하는 클래스나 인터페이스의 이름을 살펴보다 보면 흥미로운 용어들이 많이 나온다.
Adapter라는 용어도 그 중에 하나라고 볼 수 있다.

우리말로 아답터, 어댑터(Adapter, Adaptor)하면 220V 전압을 110V 전압으로 또는 그 반대로 바꿔주는 어댑터(일명, 돼지코)나 USB 충전기를 떠올릴 수 있다.

220V를 5V 전압으로 바꿔주는 USB 충전기 예

 

이처럼 Adapter는 무언가를 다른 형식이나 형태로 바꿔주는 역할을 한다.

 

그렇다면 HandlerAdater는 무엇을 바꿔줄까?

 

Spring은 객체지향의 설계 원칙을 잘 따르는 아주 유연한 구조를 가지는 프레임워크이다.

 

따라서 Spring이 제공하는 Spring MVC에서 지원하는 Handler를 사용해도 되지만 다른 프레임워크의 Handler를 Spring MVC에 통합할 수 있다.

 

이처럼 다른 프레임워크의 핸들러를 Spring MVC에 통합하기 위해서 HandlerAdapter를 사용할 수 있다.


ViewResolver의 의미

’Resolve’는 무언가를 해석하고, 해결해주다라는 뜻이 있다.

 

ViewResolver는 DispatcherServlet에서 ‘이런 이름을 가진 View를 줘’ 라고 요청하면 DispatcherServlet에서 전달한 View 이름을 해석한 뒤, 적절한 View 객체를 리턴해주는 역할을 한다.

728x90

+ Recent posts