//////
Search
📃

22년 1월 3주차 회고록

회의 날짜: 2022/01/19 (목)
회의 장소: discord 라운지
참석자: 김솔배, 구연지, 안지영

[회고 사진]

[이번 스터디 공부한 내용

스프링 MVC 웹페이지 만들기

 김솔배

스프링 MVC패턴을 활용한 웹페이지를 다루는 챕터였습니다.
이미 멋사에서 진행해보았던 과정이기에 새롭게 알게된 것들이나, 중요한 개념들을 정리해 보았습니다.
실무에서는 println()이아닌 로깅라이브러리를 사용한다.
빌드되는 과정
starterloggingslf4j

로그사용하는 이유

1.
쓰레드 정보, 클래스 이름 같은 부가 정보를 함께 볼 수 있고, 출력 모양을 조정할 수 있다.
2.
로그 레벨에 따라 개발 서버에서는 모든 로그를 출력하고, 운영서버에서는 출력하지 않는 등 로그를 상황에 맞게 조절할 수 있다.
3.
시스템 아웃 콘솔에만 출력하는 것이 아니라, 파일이나 네트워크 등, 로그를 별도의 위치에 남길 수 있다.
4.
특히 파일로 남길 때는 일별, 특정 용량에 따라 로그를 분할하는 것도 가능하다.
5.
성능도 일반 System.out보다 좋다. (내부 버퍼링, 멀티 쓰레드 등등) 그래서 실무에서는 꼭 로그를 사용해야 한다.

출력 형태

System.out.println("name = " + name); → 데이터가 그냥 출력되었다.
log.info("info log = {}", name); → 자세한 정보가 나온다.
2023-01-12 01:03:17.750 : 시간
INFO : 정보
8796 : 프로세스 ID
[nio-8080-exec-1] : 실행한 쓰레드
Http요청이 오면 쓰레드가 코드를 실행을 한다.
hello.springmvc.basic.LogTestController : Controller이름
info log = Spring : 메세지 출력한것

로그 레벨

LEVEL: TRACE > DEBUG > INFO > WARN > ERROR
개발 서버는 debug 출력
운영 서버는 info 출력
application.properties 에서 설정할 수 있다.
#전체 로그 레벨 설정(기본 info) logging.level.hello.springmvc=trace
Java
복사

로그 사용법

하지 말아야하는 사용법

log.debug("data="+data)
로그 출력 레벨을 info로 설정해도 해당 코드에 있는 "data="+data가 실제 실행이 되어 버린다.
+ 연산자를 사용하면 자바에서는 값을 더해버린다.
결과적으로 문자 더하기 연산이 발생한다.
연산 : 메모리 사용, CPU 사용

추천 사용법

log.debug("data={}", data)
파라미터를 넘기는 것이여서 연산이 발생하지 않는다.

스프링 MVC 웹페이지 만들기

 구연지

프론트 컨트롤러 패턴 소개

FrontController 패턴 특징

공통로직을 FrontController(서블릿 역할)로 처리하고 하나의 서블릿으로 Cilent와 소통한다.
1.
프론트 컨트롤러 서블릿 하나로 클라이언트의 요청을 받음
2.
프론트 컨트롤러가 요청에 맞는 컨트롤러를 찾아서 호출
3.
입구를 하나로!
4.
공통 처리 가능
5.
프론트 컨트롤러를 제외한 나머지 컨트롤러는 서블릿을 사용하지 않아도 됨

스프링 웹 MVC와 프론트 컨트롤러

스프링 웹 MVC의 핵심도 바로 FrontController 이다
스프링 웹 MVC의 DispatcherServlet가 FrontController 패턴으로 구현되어 있음

프론트 컨트롤러 도입 - v1

다형성을 활용하여 만들어 보자
InterFace - ControllerV1
구현체 - MemberFormControllerV1
구현체 - MemberListControllerV1
구현체 - MemberSaveControllerV1
모든 Ciltet요청을 받는 FrontController 생성
FrontController
controllerMap
key: 매핑 URL
value: 호출될 컨트롤러
service()
먼저 requestURI 를 조회해서 실제 호출할 컨트롤러를 controllerMap 에서 찾는다.
만약 없다면 404(SC_NOT_FOUND) 상태 코드를 반환한다.
컨트롤러를 찾고 controller.process(request, response); 을 호출해서 해당 컨트롤러를 실행한다.
JSP
JSP는 이전 MVC에서 사용했던 것을 그대로 사용한다.

View분리 - v2

모든 컨트롤러에서 뷰로 이동하는 부분에 중복이 있고, 깔끔하지 않다.
String viewPath = "/WEB-INF/views/new-form.jsp"; RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath); dispatcher.forward(request, response);
Java
복사
MyView 를 만든다. (모든 컨트롤러는 MyView를 반환)
interface ControllerV2 선언 (MyView를 리턴)
구현체 - FrontControllerServletV2
이제 각 컨트롤러는 복잡한 dispatcher.forward() 를 직접 생성해서 호출하지 않아도 된다. 단순히 MyView 객체를 생성하고 거기에 뷰 이름만 넣고 반환하면 된다.
ControllerV1 을 구현한 클래스와 ControllerV2 를 구현한 클래스를 비교해보면, 이 부분의 중복이 확실하게 제거된 것을 확인할 수 있다.
MemberFormControllerV2 - 회원 등록 폼
MemberSaveControllerV2 - 회원 등록 폼
MemberListControllerV2 - 회원 등록 폼

Model 추가 -v3

서블릿 종속성 제거

컨트롤러가 서블릿 기술을 전혀 사용하지 않도록 변경
HttpServlet → Map으로 받는다. 의존제거
request객체를 Model로 사용하지말고 별도의 Model 구현

뷰이름 중복 제거

컨트롤러는 뷰의 논리 이름을 반환하고, 실제 물리 위치의 이름은 프론트 컨트롤러에서 처리하도록 단순화
이렇게 해두면 향후 뷰의 폴더 위치가 함께 이동해도 프론트 컨트롤러만 고치면 된다
V3구조

ModelView

서블릿의 종속성을 제거하기 위해 Model을 직접 만들고, 추가로 View 이름까지 전달하는 객체를 만들어보자
참고로 ModelView 객체는 다른 버전에서도 사용하므로 패키지를 frontcontroller 에 둔다.

뷰 리졸버

논리이름 → 물리이름으로 반환해준다.

렌더링 (render)

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

v3를 변경하여 개발자들이 매우 편리하게 개발할 수 있는 v4 버전을 개발해 보자.
ModelView를 버리고, String만 반환한다. → viewName만 반환
Model객체는 파라미터로 전달이 된다.

유연한 컨트롤러1 - v5

ControllerV3 와 Controller V4, Controller V5 각각 다르게 사용하고 싶을땐 어떻게 해야할까?

어댑터 패턴

어댑터 패턴을 사용해서 프론트 컨트롤러가 다양한 방식의 컨트롤러를 처리할 수 있도록 변경해보자
1.
핸들러 매핑정보: 핸들러 조회 (Controller V4)
2.
핸들러 어댑터 목록 : 다양한 종류의 컨트롤러 호출가능
3.
핸들러 어댑터 : 조회한 컨트롤러를 넘겨준다.
4.
핸들러(컨트롤러) : Controller V4 호출
5.
핸들러 어댑터 : ModelView를 FrontController에 반환한다.

유연한 컨트롤러2 -v5

어댑터를 변경하는 로직이 가장 중요하다.
어댑터가 호출하는 ControllerV4 는 뷰의 이름을 반환한다. 그런데 어댑터는 뷰의 이름이 아니라 ModelView 를 만들어서 반환해야 한다.
여기서 어댑터가 꼭 필요한 이유가 나온다.
ControllerV4 는 뷰의 이름을 반환했지만, 어댑터는 이것을 ModelView로 만들어서 형식을 맞추어 반환한다. 마치 110v 전기 콘센트를 220v 전기 콘센트로 변경하듯이!
역할구현이 분리가 잘 되어있다.
핸들러 어댑터 - 인터페이스

 구연지

새로 배웠던 내용
1. 로깅에 대한 디테일한 내용 2. @RestController에서 캐쉬 또는 Header를 받을 수 있음 3. HTTP 메시지 컨버터

1. 로깅

SLF4J: 로그 라이브러리 들의 인터페이스
로그 레벨: TRACE(모든 로그 출력) > DEBUG > INFO > WARN > ERROR(에러만 출력)
개발 서버는 주로 debug
운영 서버는 info 출력
application.properties에서 패키지 별로 로그 레벨 설정 가능
로그 출력을 할 때 log.debug("data = {}", data)의 형태를 하면 더하기 연산이 일어나지 않지만 log.debug("data="+data) 의 형태를 사용하면 의미 없는 문자 더하기 연산이 발생하므로 이 방법 지양하기
파일이나 네트워크 등 별도의 위치에 로그 남기기
로그에 대한 설정을 하기 위해 logback.xml 만들기
<configuration> <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>./application_log/application.log</file> -> 로그를 생성하고 싶은 파일 위치 <encoder> <pattern>%d{yyyyMMdd HH:mm:ss.SSS} [%thread] %-5level [%logger{0}:%line] - %msg %n</pattern> </encoder> -> 로그 파일의 기록 형태 <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>application.log.%d{yyyy-MM-dd}.gz</fileNamePattern> -> 로그의 날짜가 변경되면(20일에서 21일이 되면) 로그를 .gz형태로 저장하도록 함 <maxHistory>30</maxHistory> -> 최대 보관 기관: 30이면 30일이 지난 경우 지워줌 <totalSizeCap>5GB</totalSizeCap> -> 전체 파일의 최대 크기: 전체 파일을 5GB까지만 저장 <maxFileSize>10MB</maxFileSize> -> 개별 사이즈 최대 용량 <cleanHistoryOnStart>true</cleanHistoryOnStart> -> application을 시작할 때 백업된 로그 삭제하는지 </rollingPolicy> </appender> <root level="info"> <appender-ref ref="file" /> </root> </configuration>
XML
복사

2. @RestController에서 받을 수 있는 값

@Slf4j @RestController public class RequestHeaderController { @RequestMapping("/headers") public String headers(HttpServletRequest request, HttpServletResponse response, HttpMethod httpMethod, Locale locale, @RequestHeader MultiValueMap<String, String> headerMap, @RequestHeader("host") String host, @CookieValue(value = "myCookie", required = false) String cookie ) { log.info("request={}", request); log.info("response={}", response); log.info("httpMethod={}", httpMethod); log.info("locale={}", locale); log.info("headerMap={}", headerMap); log.info("header host={}", host); log.info("myCookie={}", cookie); return "ok"; } }
Java
복사
(1) Locale : 로회
(2)@RequestHeader MultiValueMap<String, String>headerMap : 모든 Header를 MultiValueMap(하나의 키에 여러 value를 가짐) 형식으로 반환
(3) @RequestHeader("host") String host : "host"라는 이름의 Header 값을 반환
(4) @CookieValue(value = "myCookie", required = false) String cookie: myCookie라는 이름의 쿠키 값 반환

3. HTTP 메시지 컨버터

JSON 데이터를 메시지 바디에서 직접 읽거나 쓰는 경우 컨버터를 사용하면 편리
package org.springframework.http.converter; public interface HttpMessageConverter<T> { // 컨버터가 메시지를 읽고 쓸 수 있는지 체크: 해당 클래스나 미디어 타입을 지원하는지 체크 boolean canRead(Class<?> clazz, @Nullable MediaType mediaType); boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType); List<MediaType> getSupportedMediaTypes(); // 메시지를 읽고 씀 T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException; void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException; }
Java
복사
(1) ByteArrayHttpMessageConverter: byte 타입의 데이터를 처리
클래스타입: byte[], 미디어 타입: 모든 타입
@RequestBody byte[] data
(2) StringHttpMessageConverter: String 형태로 데이터 처리
클래스 타입: String , 미디어타입: 모든 타입
@RequestBody String data
(3) MappingJackson2HttpMessageConverter
클래스 타입: 객체 또는 HashMap , 미디어타입 application/json
@RequestBody HelloData data
ArgumentResolver: 핸들러가 작동할 때 필요한 다양한 객체를 생성해서 넘겨줌
supportsParameter() 를 통해 해당 파라미터를 지원하는지 체크
resolveArgument() 를 통해 실제 객체 생성
ReturnValueHandler: 응답 데이터를 HTTP 메시지의 형태로 반환함

 안지영

로깅

요청 매핑

HTTP 요청 - 기본, 헤더 조회

HTTP 요청 파라미터

HTTP 요청 메시지

HTTP 응답

 김재근

스프링 MVC - 기본기능

 [어려웠던 부분]

 김솔배
모든 내용이 대부분 이미 배운것들이지만, 혼자 해보라고하면 못할것 같습니다.
Plain Text
복사
 구연지
크게 어려웠던 부분은 없었지만 무심코 기계적으로 사용했던 부분에서 내가 알지 못했던 것들이 여러개가 나와서 좋으면서도 걱정이 되었다.
Plain Text
복사
 안지영
핸들러 어댑터에 대해서 저번 시간에 들었었는데 그새 까먹어서 이해하기가 어려웠다. 양이 많아지다 보니까 좀 헷갈리는게 생겨서 어려웠다
Plain Text
복사
 김재근
기본적인것부터 차근차근 짚어나가는데 슬슬 비슷한 클래스 내용만 나와서 용어들이 햇갈리기 시작했어요.....
Plain Text
복사

 [금주의 꿀팁 & 인사이트!]

 김솔배
새로고침을 할때는 리다이렉트를 통해서 중복처리되는것을 막을 수 있다.
Plain Text
복사
 구연지
로그 레벨에 맞게 로깅을 하고 파일에 저장해보자!
Plain Text
복사
 안지영
비즈니스 로직도 중요하지만 기본적인 요청, 응답에 관한 내용들을 정확히 알아야 나중에 문제가 생기지 않을 것 같다.
Plain Text
복사
 김재근
어댑터와 핸들러의 종류가 이렇게 많을줄 몰랐습니다... 해당 개념을 더 잘 이해하면 이후에 문제가 생겼을 때 어느 부분에서 생겼는지 빠르게 알 수 있을것 같습니다!
Plain Text
복사

 [금주의 느낀 점]

 김솔배
개발의 끝은 있을까...?
Plain Text
복사
 구연지
저번 시간과 이번 시간 모두 배웠던 내용이지만 강의를 들으면서 그 속을 들여다보니 모르는 내용들이 많았었다. 깊게 공부를 해야한다는 것이 어떤 의미인지 더욱 와닿는 2주였고, 인프런 강의를 들을 후에도 복습을 하면서 내 것으로 만드는 시간이 필요할 것 같다.
Plain Text
복사
 안지영
지금까지 내가 작성했던 코드들도 사실은 무엇인지 모르고 사용한게 많지 않았나라는 생각이 들었고, 정말 더 많이 공부해야겠다는 생각이 들었다. 찬찬히 인강을 들으면서 기본적인 개념을 알아야 좋은 개발자가 될 수 있을 것 같다.
Plain Text
복사
 김재근
괜히 갓영한 갓영한 하는게 아니구나 라는걸 매번 체감합니다..!
Java
복사