ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [인프런 - 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술] 6장. 스프링 MVC - 기본 기능
    Engineering WIKI/Spring Boot 2023. 9. 27. 11:45
    • 해당 내용은, '인프런 - 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 (유료 - 69,300) 김영한님 강의'를 개인적으로 개념정리를 위해 정리한 내용입니다.

    로깅 간단히 알아보기

    • 로깅 라이브러리
      • 스프링 부트 라이브러리를 사용하면 스프링 부트 로깅 라이브러리( spring-boot-starter-logging )가 함께 포함된다.
      • 스프링 부트 로깅 라이브러리는 기본으로 다음 로깅 라이브러리를 사용한다.
      • 로그 라이브러리는 Logback, Log4J, Log4J2 등등 수 많은 라이브러리가 있는데, 그것을 통합해서 인터페이스로 제공하는 것이 바로 SLF4J 라이브러리다.
      • 쉽게 이야기해서 SLF4J는 인터페이스이고, 그 구현체로 Logback 같은 로그 라이브러리를 선택하면 된다.
      • 실무에서는 스프링 부트가 기본으로 제공하는 Logback을 대부분 사용한다.
    • 로그 선언
      • private Logger log = LoggerFactory.getLogger(getClass());
      • private static final Logger log = LoggerFactory.getLogger(Xxx.class)
      • @Slf4j : 롬복 사용 가능
    • 로그 호출
      • log.info("hello")
      • 시스템 콘솔로 직접 출력하는 것 보다 로그를 사용하면 다음과 같은 장점이 있다. 실무에서는 항상 로그를 사용해야 한다
    • LEVEL: TRACE > DEBUG > INFO > WARN > ERROR
      • 개발 서버는 debug 출력
      • 운영 서버는 info 출력
      • @Slf4j 로 변경
    • 매핑 정보
      • @RestController
      • @Controller 는 반환 값이 String 이면 뷰 이름으로 인식된다. 그래서 뷰를 찾고 뷰가 랜더링 된다.
      • @RestController 는 반환 값으로 뷰를 찾는 것이 아니라, HTTP 메시지 바디에 바로 입력한다.
      • 따라서 실행 결과로 ok 메세지를 받을 수 있다. @ResponseBody 와 관련이 있는데, 뒤에서 더 자세히 설명한다.
    • 로그 레벨 설정
      • application.properties
    #전체 로그 레벨 설정(기본 info)
    logging.level.root=info
    #hello.springmvc 패키지와 그 하위 로그 레벨 설정
    logging.level.hello.springmvc=debug
    • 로그 사용시 장점
      • 쓰레드 정보, 클래스 이름 같은 부가 정보를 함께 볼 수 있고, 출력 모양을 조정할 수 있다.
      • 로그 레벨에 따라 개발 서버에서는 모든 로그를 출력하고, 운영서버에서는 출력하지 않는 등 로그를 상황에 맞게 조절할 수 있다.
      • 시스템 아웃 콘솔에만 출력하는 것이 아니라, 파일이나 네트워크 등, 로그를 별도의 위치에 남길 수 있다.
      • 특히 파일로 남길 때는 일별, 특정 용량에 따라 로그를 분할하는 것도 가능하다.
      • 성능도 일반 System.out보다 좋다. (내부 버퍼링, 멀티 쓰레드 등등) 그래서 실무에서는 꼭 로그를 사용해야 한다.

    HTTP 요청 - 기본, 헤더 조회

    • MultiValueMap
      • MAP과 유사한데, 하나의 키에 여러 값을 받을 수 있다.
      • HTTP header, HTTP 쿼리 파라미터와 같이 하나의 키에 여러 값을 받을 때 사용한다.
        • keyA=value1&keyA=value2
      MultiValueMap<String, String> map = new LinkedMultiValueMap();
      map.add("keyA", "value1");
      map.add("keyA", "value2");
      //[value1,value2]
      List<String> values = map.get("keyA");
      

    HTTP 요청 파라미터 - @RequestParam

    스프링이 제공하는 @RequestParam 을 사용하면 요청 파라미터를 매우 편리하게 사용할 수 있다.

    /**
     * @RequestParam 사용
     * - 파라미터 이름으로 바인딩
     * @ResponseBody 추가
     * - View 조회를 무시하고, HTTP message body에 직접 해당 내용 입력
     */
    
    @ResponseBody
    @RequestMapping("/request-param-v2")
    public String requestParamV2(
     @RequestParam(required = true, defaultValue = "guest") String memberName,
     @RequestParam("age") int memberAge) 
    {
    	 log.info("username={}, age={}", memberName, memberAge);
    	 return "ok";
    }
    • @RequestParam : 파라미터 이름으로 바인딩
    • @ResponseBody : View 조회를 무시하고, HTTP message body에 직접 해당 내용 입력
    • @RequestParam의 name(value) 속성이 파라미터 이름으로 사용
    • @RequestParam("username") String memberName
      • request.getParameter("username")
    • 파라미터를 Map으로 조회하기 - requestParamMap
    /**
     * @RequestParam Map, MultiValueMap
     * Map(key=value)
     * MultiValueMap(key=[value1, value2, ...]) ex) (key=userIds, value=[id1, id2])
     */
    
    @ResponseBody
    @RequestMapping("/request-param-map")
    public String requestParamMap(@RequestParam Map<String, Object> paramMap) {
    	 log.info("username={}, age={}", paramMap.get("username"), paramMap.get("age"));
    	 return "ok";
    }
    • 파라미터를 Map, MultiValueMap으로 조회할 수 있다
    • @RequestParam Map ,
      • Map(key=value)
    • @RequestParam MultiValueMap
      • MultiValueMap(key=[value1, value2, ...] ex) (key=userIds, value=[id1, id2])
    • 파라미터의 값이 1개가 확실하다면 Map 을 사용해도 되지만, 그렇지 않다면 MultiValueMap 을 사용하자.

    HTTP 요청 파라미터 - @ModelAttribute

    • 실제 개발을 하면 요청 파라미터를 받아서 필요한 객체를 만들고 그 객체에 값을 넣어주어야 한다. 보통 다음과 같이 코드를 작성할 것이다.
    @RequestParam String username;
    @RequestParam int age;
    
    HelloData data = new HelloData();
    
    data.setUsername(username);
    data.setAge(age);
    • 스프링은 이 과정을 완전히 자동화해주는 @ModelAttribute 기능을 제공한다.

     

    • 먼저 요청 파라미터를 바인딩 받을 객체를 만들자
    • HelloData
    package hello.springmvc.basic;
    
    import lombok.Data;
    
    @Data
    public class HelloData {
     private String username;
     private int age;
    }
    • 롬복 @Data
      • @Getter , @Setter , @ToString , @EqualsAndHashCode , @RequiredArgsConstructor 를 자동으로 적용해준다.
    • @ModelAttribute 적용 - modelAttributeV1
    /**
     * @ModelAttribute 사용
     * 참고: model.addAttribute(helloData) 코드도 함께 자동 적용됨, 뒤에 model을 설명할 때 자세히 설명
     */
    @ResponseBody
    @RequestMapping("/model-attribute-v1")
    public String modelAttributeV1(@ModelAttribute HelloData helloData) {
    	log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
    	return "ok";
    }
    • 마치 마법처럼 HelloData 객체가 생성되고, 요청 파라미터의 값도 모두 들어가 있다
    • 스프링MVC는 @ModelAttribute 가 있으면 다음을 실행한다.
      • HelloData 객체를 생성한다.
      • 요청 파라미터의 이름으로 HelloData 객체의 프로퍼티를 찾는다. 그리고 해당 프로퍼티의 setter를 호출해서 파라미터의 값을 입력(바인딩) 한다.
      • 예) 파라미터 이름이 username 이면 setUsername() 메서드를 찾아서 호출하면서 값을 입력한다.

    HTTP 응답 - 정적 리소스, 뷰 템플릿

      • 스프링(서버)에서 응답 데이터를 만드는 방법은 크게 3가지이다.
        • 정적 리소스
          • 예) 웹 브라우저에 정적인 HTML, css, js를 제공할 때는, 정적 리소스를 사용한다.
        • 뷰 템플릿 사용
          • 예) 웹 브라우저에 동적인 HTML을 제공할 때는 뷰 템플릿을 사용한다.
        • HTTP 메시지 사용
          • HTTP API를 제공하는 경우에는 HTML이 아니라 데이터를 전달해야 하므로, HTTP 메시지 바디에 JSON 같은 형식으로 데이터를 실어 보낸다.
        • 정적 리소스
          • 스프링 부트는 클래스패스의 다음 디렉토리에 있는 정적 리소스를 제공한다.
            • /static , /public , /resources , /META-INF/resources
          • src/main/resources 는 리소스를 보관하는 곳이고, 또 클래스패스의 시작 경로이다. 따라서 다음 디렉토리에 리소스를 넣어두면 스프링 부트가 정적 리소스로 서비스를 제공한다.
          • 정적 리소스 경로
            • src/main/resources/static
            • 다음 경로에 파일이 들어있으면
              • src/main/resources/static/basic/hello-form.html
            • 웹 브라우저에서 다음과 같이 실행하면 된다.
            • http://localhost:8080/basic/hello-form.html
            • 정적 리소스는 해당 파일을 변경 없이 그대로 서비스하는 것이다
        • 뷰 템플릿
          • 뷰 템플릿을 거쳐서 HTML이 생성되고, 뷰가 응답을 만들어서 전달한다. 일반적으로 HTML을 동적으로 생성하는 용도로 사용하지만, 다른 것들도 가능하다. 뷰 템플릿이 만들 수 있는 것이라면 뭐든지 가능하다.
        • 스프링 부트는 기본 뷰 템플릿 경로를 제공한다
          • 뷰 템플릿 경로
            • src/main/resources/templates
          • 뷰 템플릿 생성
            • src/main/resources/templates/response/hello.html

    HTTP 메시지 컨버터

    • HTTP 메시지 컨버터 위치
    • HTTP 메시지 컨버터는 어디쯤 있을까? HTTP 메시지 컨버터를 사용하는 @RequestBody 도 컨트롤러가 필요로 하는 파라미터의 값에 사용된다.
    • @ResponseBody 의 경우도 컨트롤러의 반환 값을 이용한다.
    • 요청의 경우 @RequestBody 를 처리하는 ArgumentResolver 가 있고, HttpEntity 를 처리하는 ArgumentResolver 가 있다. 이 ArgumentResolver 들이 HTTP 메시지 컨버터를 사용해서 필요한 객체를 생성하는 것이다. 
    • 응답의 경우 @ResponseBody 와 HttpEntity 를 처리하는 ReturnValueHandler 가 있다. 그리고 여기에서 HTTP 메시지 컨버터를 호출해서 응답 결과를 만든다.
    • 스프링 MVC는 @RequestBody @ResponseBody 가 있으면 RequestResponseBodyMethodProcessor (ArgumentResolver) HttpEntity 가 있으면 HttpEntityMethodProcessor (ArgumentResolver)를 사용한다.

    궁금한 점

    jar vs war

    • JAR, WAR 모두 Java의 -jar 옵션 (java -jar)을 이용해 생성된 압축(아카이브 파일)로 애플리케이션을 쉽게 배포하고 동작시킬수 있도록 관련 파일을 패키징 한 것이다.
    • JAR (JAVA Archive)
      • JAVA 어플리케이션이 동작할 수 있도록 자바 프로젝트를 압축한 파일
      • Class (JAVA리소스, 속성 파일), 라이브러리 파일을 포함함
      • JRE(JAVA Runtime Environment)만 있어도 실행 가능함 (java -jar 프로젝트네임.jar)
    • WAR (Web Application Archive)
      • Servlet / Jsp 컨테이너에 배치할 수 있는 웹 애플리케이션(Web Application) 압축파일 포맷
      • 웹 관련 자원을 포함함 (JSP, Servlet, JAR, Class, XML, HTML, Javascript)
      • 사전 정의된 구조를 사용함 (WEB-INF, META-INF)
      • 별도의 웹서버(WEB) or 웹 컨테이너(WAS) 필요
      • 즉, JAR파일의 일종으로 웹 애플리케이션 전체를 패키징 하기 위한 JAR 파일이다.

    JSON은 객체인가요 문자인가요?

    • JSON은 규격화 한 문자열 입니다.
    • 규격화 했다는 것은 일정한 규칙에 맞게 작성했다는 의미입니다. 이 문자열을 해석해서 객체로 변환, 사용하기 쉽게 만드는 것을 '파싱'한다고 표현합니다.

    스프링 동작과정

    • 스프링 부트 구동 시점에 스프링 컨테이너와 서블릿 컨테이너는 각각이 필요한 기본 빈들을 생성하고, 필요한 초기화 작업과 DI 작업을 수행
    • 클라이언트로부터 URL 호출 시, 서블릿 컨테이너가 request를 파싱하고, HttpServletRequest와 Response 객체를 생성 후, 파싱된 메세지를 HttpServletRequest에 전달함. 이후, 쓰레드가 DispatcherServlet 객체의 service()를 호출(service()는 실제로 부모 클래스에서 오버라이딩 되어 있고 그 내부에서 중요한 메서드인 doDispatch()가 호출됨)
    • 이후 DispatcherServlet은 미리 초기화 해둔 handlerMappings의 handlerMapping 구현 클래스를 순서대로 하나씩 꺼내와서 검사하는데, 1순위인 RequestMappingHandlerMapping 클래스의 조회 전략은 @Controller 클래스 중, 메서드 레벨에 @RequestMapping이 붙어 있고, request의 URL 정보를 토대로 일치하는지 검사. 있다면 해당 핸들러를 반환(컨트롤러) 하지만, 없다면 서블릿 예외를 던지고 애초에 @Controller 자체가 없다면 2순위로 검사 위임하지 않음
    • 해당 핸들러를 getHandlerAdapter()를 통해서 해당 핸들러를 지원하는 어댑터가 있다면 해당 어댑터를 반환하여 HandlerAdapter ha에 할당하고, 지원하는 어댑터가 없는 경우 서블릿 예외를 던짐
    • handlerAdapter의 handler()를 수행하는데 실제 컨트롤러 메서드를 호출하기 전에, 해당 메서드가 필요로 하는 매개변수 정보를 (@RequestBody인지, HttpEntity인지) ArgumentResolver에 제공하고, 각각에 특화된 Http메세지 컨버터를 사용해서 필요한 객체를 생성
    • 컨트롤러가 결과값을 반환
    • 메서드 레벨에 @ResponseBody가 붙어있다면 RetrunValueHandler가 HttpMessengerConverter를 이용해서 반환값을 응답 메세지 바디부에 실어서 ViewResolver등을 거치지 않고 즉시 요청 송신자에 반환
    • 위의 경우가 아니면, ReturnValueHandler는 반환 값의 타입에 따라 적절한 HttpMessengerConverter를 통해 응답 메세지를 생성 후, ViewResolver가 동작하는데 이는 View 객체를 생성하고 DispatcherServlet가 이를 이용하여 render()하고 최종적으로 클라이언트에게 반환.

     

Designed by Tistory.