1. spring MVC 정의
2. Controller에서의 Request
3. Controller에서의 Response
3.5. 그 외 요청,응답 파라미터
4. Spring MVC 핵심
5. Spring 동작 순서
6. Spring_Controller 동작 이해
7. ViewResolver (뷰 리졸버)
8. @RequestMapping
9. Parameter
10. 서버에서 클라이언트로 응답
11. HTTP 메세지 컨버터
12. 요청 매핑 핸들러 어댑터 구조
13. 로깅( Logging )
- servlet에서 모든 로직(비즈니스, 출력 로직)을 만들다 보니 개발자가 작성하기 힘듬
- 템플릿 엔진으로 비즈니스,출력 로직을 모두 작성, 근데 가독성 굉장히 떨어짐
- controller, view로 나눠서 MVC 모델1을 사용
- 근데 Controller에서 비즈니스로직+호출까지 담당하니 가독성 떨어짐
- 그래서 Controller,Service,DTO,DAO,View 작업을 나눈 MVC 모델2를 사용
- 현재는 MVC 하면 모델2
- 함께 사용할 템플릿 엔진 : JSP, Thymeleaf
- url?username=hello&age=20으로 메세지 바디없이 url에 쿼리 파라미터를 포함해서 전달
- URL 뒤에 ?로 쿼리 파라미터를 시작, &로 파라미터를 구분해서 보낸다.
- content-type이 없다.
- 주로 검색, 필터, 페이징 등에서 사용
- http 요청시 'content-type: application/x-www-form-urlencoded'으로 전달
- 메세지 body에 쿼리 파라미터로 전달 username=hello&age=20
- 웹 브라우저가 결과를 캐시하고 있어서 과거 작성했던 HTML 결과가 보이는 경우가 있기 때문에 웹브라우저의 새로고침을 하면된다. OR 서버 재시작 필수
- 클라이언트에서는 get과 post를 전달하는 방식에 차이가 있지만 서버는 똑같이 쿼리 파라미터로 받기 때문에 둘다 request.getParameter()로 받으면 된다.
- 주로 회원 가입, 상품 주문, HTML Form 사용
- HTTP API에서 주로 사용, JSON, XML, TEXT
- 데이터 형식은 주로 JSON 사용
- POST, PUT, PATCH (근데 주로 POST만 사용)
- body에 데이터를 InputStream을 사용해서 직접 읽을 수 있다.
// inputstream으로 byte 코드를 반환
ServletInputStream inputStream = request.getInputStream();
// 반환 받은 byte 코드를 utf_8 charset으로 지정해서 문자로 담기
String messageBody = StreamUtils.copyToString(inputStream,StandardCharsets.UTF_8);
// body 출력
System.out.println("messageBody = " + messageBody);
// 응답
response.getWriter().write("ok");
// 반환할 데이터 타입 지정
response.setHeader("content-type", "application/json");
//Jackson 라이브러리가 제공하는 메서드를 사용해서 json 객체를 문자로 변경
//{"username":"kim","age":20}
String result = objectMapper.writeValueAsString(data);
response.getWriter().write(result);
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
ModelAndView mv = null;
// 1. 핸들러 조회
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 2. 핸들러 어댑터 조회 - 핸들러를 처리할 수 있는 어댑터
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 3. 핸들러 어댑터 실행 -> 4. 핸들러 어댑터를 통해 핸들러 실행 -> 5. ModelAndView 반환
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
// 뷰 렌더링 호출
render(mv, request, response);
}
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
View view;
String viewName = mv.getViewName();
// 6. 뷰 리졸버를 통해서 뷰 찾기, 7. View 반환
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
// 8. 뷰 렌더링
view.render(mv.getModelInternal(), request, response);
}
@Component("/springmvc/old-controller")
public class OldController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("OldController.handleRequest");
return null;
}
}
Controller 인터페이스를 통해 controller 클래스를 구현하고 @Component로 해당 url을 스프링 빈으로 등록했고, 이제 이 빈 이름으로 url을 매핑한다.
이때!! HandlerMapping과 HandlerAdapter는 아래 우선순위에 따라 순서대로 찾아 동작한다.
0 = RequestMappingHandlerMapping : 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = BeanNameUrlHandlerMapping : 스프링 빈의 이름으로 핸들러를 찾는다.
0 = RequestMappingHandlerAdapter : 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = HttpRequestHandlerAdapter : HttpRequestHandler 처리
2 = SimpleControllerHandlerAdapter : Controller 인터페이스(애노테이션X, 과거에 사용) 처리
- HandlerMapping 을 순서대로 실행해서, 핸들러를 찾는다.
- 이 경우 빈 이름으로 핸들러를 찾아야 하기 때문에 이름 그대로 빈 이름으로 핸들러를 찾아주는
BeanNameUrlHandlerMapping 가 실행에 성공하고 핸들러인 OldController 를 반환한다.
- HandlerAdapter 의 supports() 를 순서대로 호출한다.
- SimpleControllerHandlerAdapter 가 Controller 인터페이스를 지원하므로 대상이 된다.
- 디스패처 서블릿이 조회한 SimpleControllerHandlerAdapter 를 실행하면서 핸들러 정보도 함께 넘겨준다.
- SimpleControllerHandlerAdapter 는 핸들러인 OldController 를 내부에서 실행하고, 그 결과를 반환한다.
- HandlerAdapter 의 supports() 를 순서대로 호출한다.
- HttpRequestHandlerAdapter 가 HttpRequestHandler 인터페이스를 지원하므로 대상이 된다.
- 디스패처 서블릿이 조회한 HttpRequestHandlerAdapter 를 실행하면서 핸들러 정보도 함께 넘겨준다.
- HttpRequestHandlerAdapter 는 핸들러인 MyHttpRequestHandler 를 내부에서 실행하고, 그 결과를 반환한다.
1 = BeanNameViewResolver : 빈 이름으로 뷰를 찾아서 반환한다. (예: 엑셀 파일 생성 기능에 사용)
2 = InternalResourceViewResolver : JSP를 처리할 수 있는 뷰를 반환한다.
- 핸들러 어댑터를 통해 컨트롤러에서 return 한 논리 뷰 이름을 획득한다.
- 전달받은 뷰 이름으로 viewResolver를 순서대로 호출한다.
- BeanNameViewResolver 는 전달받은 뷰 이름의 스프링 빈으로 등록된 뷰를 찾아야 하는데 없다.
- InternalResourceViewResolver 가 호출된다.
- 이 뷰 리졸버는 InternalResourceView 를 반환한다.
- InternalResourceView 는 JSP처럼 포워드 forward() 를 호출해서 처리할 수 있는 경우에 사용한다.
- view.render() 가 호출되고 InternalResourceView 는 forward() 를 사용해서 JSP를 실행한다.
실무에서 가장 많이 사용하는 방식의 컨트롤러이다.
@RequestMapping은 RequestMappingHandlerMapping, RequestMappingHandlerAdapter을 줄여서 사용한다.
코드로 빠르게 이해!!
@Controller
public class SpringMemberFormControllerV1 {
@RequestMapping("/springmvc/v1/members/new-form")
public ModelAndView process() {
return new ModelAndView("new-form");
}
}
@Component //컴포넌트 스캔을 통해 스프링 빈으로 등록
@RequestMapping
public class SpringMemberFormControllerV1 {
@RequestMapping("/springmvc/v1/members/new-form")
public ModelAndView process() {
return new ModelAndView("new-form");
}
}
@Controller
@RequestMapping("/springmvc/v2/members")
Spring은 HTTP 요청 파라미터를 @RequestParam으로 받을 수 있다.
@RequestParam("username") = request.getParameter("username")
@RequestParam("username") String username
@RequestParam String username
파라미터의 필수 여부
@RequestParam(required = false) String username ( 아무것도 작성안하면 됌 or true로 작성하면 필수 )
근데 파라미터 필수값을 없을때 ""빈 문자로 처리되서 등록되기 때문에 주의해서 사용
int는 null을 받을 수 없기 때문에 Integer로 바꿔서 받을 수 있게 만들어야 함
0 = ByteArrayHttpMessageConverter
1 = StringHttpMessageConverter
2 = MappingJacksonHttpMessageConverter
요청
이렇게 파라미터를 유연하게 처리할 수 있는 이유가 바로 ArgumentResolver 덕분이다.
애노테이션 기반 컨트롤러를 처리하는 RequestMappingHandlerAdaptor 는 바로 이 ArgumentResolver 를 호출해서 컨트롤러(핸들러)가 필요로 하는 다양한 파라미터의 값(객체)을 생성한다. 그리고 이렇게 파리미터의 값이 모두 준비되면 컨트롤러를 호출하면서 값을 넘겨준다
ReturnValueHandler
요청
응답
스프링 MVC는
Private Logger log = LoggerFactory.getLogger(getClass());
private static final Logger log = LoggerFactory.getLogger(Xxx.class)
// lombok 사용
@Slf4j
LEVEL: TRACE > DEBUG > INFO > WARN > ERROR
#전체 로그 레벨 설정(기본 info)
logging.level.root=info
#hello.springmvc 패키지와 그 하위 로그 레벨 설정
logging.level.hello.springmvc=debug
개발 서버는 debug 출력
운영 서버는 info 출력
로그 사용법
- 쓰레드 정보, 클래스 이름 같은 부가 정보를 함께 볼 수 있고, 출력 모양을 조정할 수 있다.
- 로그 레벨에 따라 개발 서버에서는 모든 로그를 출력하고, 운영서버에서는 출력하지 않는 등 로그를 상황에
맞게 조절할 수 있다.- 시스템 아웃 콘솔에만 출력하는 것이 아니라, 파일이나 네트워크 등, 로그를 별도의 위치에 남길 수 있다.
특히 파일로 남길 때는 일별, 특정 용량에 따라 로그를 분할하는 것도 가능하다.- 성능도 일반 System.out보다 좋다. (내부 버퍼링, 멀티 쓰레드 등등) 그래서 실무에서는 꼭 로그를 사용해야 한다.