핸들러 매핑과 핸들러 어댑터
Controller 인터페이스
과거 버전 스프링 컨트롤러 (현재는 사용X)
org.springframework.web.servlet.mvc.Controller
public interface Controller {
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
throws Exception;
}
초기 스프링은 이런 딱딱한 형식의 인터페이스 제공
@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;
}
}
빈의 이름 "/springmvc/old-controller"으로 URL 매핑 (BeanNameUrlHandlerMapping)
Controller 인터페이스를 지원하는 어댑터에 의해 handleRequest() 를 호출 (SimpleControllerHandlerAdapter)
그 결과를 디스패처 서블릿에 반환
컨트롤러가 호출되려면 다음 2가지가 필요하다.
1. HandlerMapping(핸들러 매핑)
핸들러 매핑에서 이 컨트롤러를 찾을 수 있어야 한다.
예) 스프링 빈의 이름으로 핸들러를 찾을 수 있는 핸들러 매핑이 필요하다.
2. HandlerAdapter(핸들러 어댑터)
핸들러 매핑을 통해서 찾은 핸들러를 실행할 수 있는 핸들러 어댑터가 필요하다.
예) Controller 인터페이스를 실행할 수 있는 핸들러 어댑터를 찾고 실행해야 한다.
스프링은 이미 필요한 핸들러 매핑과 핸들러 어댑터를 대부분 구현해두었다.
개발자가 직접 핸들러 매핑과 핸들러 어댑터를 만드는 일은 거의 없다.
스프링 부트가 자동 등록하는 핸들러 매핑과 핸들러 어댑터
(실제로는 더 많지만, 중요한 부분 위주로 설명하기 위해 일부 생략)
HandlerMapping
요청을 수행할 핸들러(컨트롤러)를 찾는다.
0(순위) = RequestMappingHandlerMapping : 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = BeanNameUrlHandlerMapping : 스프링 빈의 이름으로 핸들러를 찾는다.
HandlerAdapter
HandlerAdapter 의 supports() 를 순서대로 호출한다. (해당 어댑터가 지원하는 타입의 핸들러인지)
디스패처 서블릿이 건네 준 핸들러를 실행
0 = RequestMappingHandlerAdapter : 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = HttpRequestHandlerAdapter : HttpRequestHandler 처리 (서블릿과 비슷한 스타일)
2 = SimpleControllerHandlerAdapter : Controller 인터페이스 (애노테이션X, 과거에 사용) 처리
뷰 리졸버
스프링 부트가 자동 등록하는 뷰 리졸버
(실제로는 더 많지만, 중요한 부분 위주로 설명하기 위해 일부 생략)
1 = BeanNameViewResolver : 빈 이름으로 뷰를 찾아서 반환한다. (예: 엑셀 파일 생성 기능에 사용)
2 = InternalResourceViewResolver : JSP를 처리할 수 있는 뷰를 반환한다.
InternalResourceViewResolver
스프링 부트는 InternalResourceViewResolver 라는 뷰 리졸버를 자동으로 등록하는데, 이때 application.properties 에 등록한 spring.mvc.view.prefix , spring.mvc.view.suffix 설정 정보를 사용해서 등록한다.
--application.properties
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
--in handler(controller)
return new ModelAndView("new-form");
JSP처럼 포워드 forward() 를 호출해서 처리할 수 있는 InternalResourceView 를 반환한다.
참고로 권장하지는 않지만 설정 없이 다음과 같이 전체 경로를 주어도 동작하기는 한다.
ex. return new ModelAndView("/WEB-INF/views/new-form.jsp");
참고: InternalResourceViewResolver 는 만약 JSTL 라이브러리가 있으면 InternalResourceView 를 상속받은 JstlView 를 반환한다. JstlView 는 JSTL 태그 사용시 약간의 부가 기능이 추가된다.
참고: 다른 뷰는 실제 뷰를 렌더링하지만, JSP의 경우 forward() 통해서 해당 JSP로 이동(실행)해야 렌더링이 된다. JSP를 제외한 나머지 뷰 템플릿들은 forward() 과정 없이 자바 코드로 바로 렌더링 된다.
참고: Thymeleaf 뷰 템플릿을 사용하면 ThymeleafViewResolver 를 등록해야 한다. 최근에는 라이브러리만 추가하면 스프링 부트가 이런 작업도 모두 자동화해준다.
스프링 MVC - 시작하기
스프링이 제공하는 컨트롤러는 애노테이션 기반으로 동작해서, 매우 유연하고 실용적이다.
@RequestMapping 기반의 애노테이션 컨트롤러 (가장 우선 순위가 높다)
- RequestMappingHandlerMapping
- RequestMappingHandlerAdapter
@Controller
public class SpringMemberFormControllerV1 {
@RequestMapping("/springmvc/v1/members/new-form")
public ModelAndView process() {
return new ModelAndView("new-form");
}
}
@Controller :
스프링이 자동으로 스프링 빈으로 등록한다. (내부에 @Component 애노테이션이 있어서 컴포넌트 스캔의 대상이 됨)
스프링 MVC에서 애노테이션 기반 컨트롤러로 인식한다. (이후 핸들러 매핑의 대상이 됨)
@RequestMapping : 요청 정보를 매핑한다. 해당 URL이 호출되면 이 메서드가 호출된다. 애노테이션을 기반으로 동작하기 때문에, 메서드의 이름은 임의로 지으면 된다.
ModelAndView : 모델과 뷰 정보를 담아서 반환하면 된다.
RequestMappingHandlerMapping 은 스프링 빈 중에서 @RequestMapping 또는 @Controller 가 클래스 레벨에 붙어 있는 경우에 매핑 정보로 인식한다.
(@RequestMapping+@Component or @Controller)
스프링 MVC - 컨트롤러 통합
@RequestMapping 가 클래스 단위가 아니라 메서드 단위에 적용된 것을 확인할 수 있다.
따라서 하나의 컨트롤러 클래스에 여러 URL 매핑 정보를 포함시킬 수 있다.
조합
조합을 통해 매핑 URL의 중복을 제거할 수 있다.
클래스 레벨 @RequestMapping("/springmvc/v2/members")
메서드 레벨 @RequestMapping("/new-form") -> 조합 결과: /springmvc/v2/members/new-form
메서드 레벨 @RequestMapping("/save") -> 조합 결과: /springmvc/v2/members/save
메서드 레벨 @RequestMapping -> 조합 결과: /springmvc/v2/members
스프링 MVC - 실용적인 방식
Remind. MVC 프레임워크 만들기 v3: ModelView를 직접 생성하여 반환하여 불편 -> v4에서 개선
스프링 MVC는 개발자가 편리하게 개발할 수 있도록 수 많은 편의 기능을 제공한다.
실무에서는 지금부터 설명하는 방식을 주로 사용한다.
Model 파라미터
org.springframework.ui.Model을 파라미터로 받는다. 스프링이 객체를 생성해서 넣어줌
model.setAttribute("member", member)
ViewName 직접 반환
뷰의 논리 이름을 반환할 수 있다.
리턴 타입: String
@RequestParam 사용
스프링은 HTTP 요청 파라미터를 @RequestParam 으로 받을 수 있다.
@RequestMapping 메서드의 파라미터로 사용하며, request.getParameter() 와 유사하다.
GET 쿼리 파라미터, POST Form 방식을 모두 지원
@RequestMapping -> @GetMapping, @PostMapping
@RequestMapping 은 요청 HTTP Method도 제약할 수 있다.
(Get, Post, Put, Delete, Patch 모두 가능)
예) URL: /new-form, HTTP Method: GET
@RequestMapping(value = "/new-form", method = RequestMethod.GET)
or
@GetMapping("/new-form")
스프링 MVC - 기본 기능
프로젝트 생성
Packaging: Jar 선택!
JSP를 사용하지 않기 때문에 Jar를 사용하는 것이 좋다. 스프링 부트를 사용하면 이 방식을 주로 사용하게 될 것이다. Jar 는 항상 내장 서버(톰캣 등)를 사용하고, webapp 경로를 사용하지 않는다. 내장 서버 사용에 최적화 되어 있으며, 최근에는 주로 Jar를 많이 사용한다.
War에선 내장 서버는 사용 가능하나, 주로 외부 서버에 배포하는 목적으로 사용한다.
Welcome 페이지 생성
스프링 부트에 Jar 를 사용하면,
/resources/static/index.html => Welcome 페이지 처리
(스프링 부트가 지원하는 정적 컨텐츠 위치에 /index.html 이 있으면 됨)
로깅 간단히 알아보기
운영 시스템에서는 System.out.println() 같은 시스템 콘솔을 사용해서 필요한 정보를 출력하지 않고, 별도의 로깅 라이브러리를 사용해서 로그를 출력한다. 실무에서는 항상 로그를 사용!
로깅 라이브러리
스프링 부트 라이브러리를 사용하면 스프링 부트 로깅 라이브러리( spring-boot-starter-logging )가 함께 포함된다.
스프링 부트 로깅 라이브러리는 기본으로 다음 로깅 라이브러리를 사용한다
- SLF4J(인터페이스) - http://www.slf4j.org
- Logback(구현체) - http://logback.qos.ch
로그 선언
import org.slf4j.Logger;
private Logger log = LoggerFactory.getLogger(getClass());
private static final Logger log = LoggerFactory.getLogger(Xxx.class);
@Slf4j : 롬복 사용 가능
로그 호출
log.info("hello") : 로그 사용
System.out.println("hello") : 시스템 콘솔로 직접 호출
로그 사용의 장점
- 쓰레드 정보, 클래스 이름 같은 부가 정보를 함께 볼 수 있고, 출력 모양을 조정할 수 있다.
- 로그 레벨에 따라 개발 서버에서는 모든 로그를 출력하고, 운영서버에서는 출력하지 않는 등 로그를 상황에 맞게 조절할 수 있다.
- 시스템 아웃 콘솔에만 출력하는 것이 아니라, 파일이나 네트워크 등 로그를 별도의 위치에 남길 수 있다. 특히 파일로 남길 때는 일별, 특정 용량에 따라 로그를 분할하는 것도 가능하다.
- 성능도 일반 System.out보다 좋다. (내부 버퍼링, 멀티 쓰레드 등등)
테스트
@Controller: 반환 값이 String 이면 뷰 이름으로 인식. 해당 이름으로 뷰를 찾고 렌더링 하는 작업 수행.
@RestController: 반환한 String 을 HTTP 메시지 바디에 바로 입력 (@ResponseBody 와 관련)
출력 포멧
시간, 로그 레벨, 프로세스 ID, 쓰레드 명, 클래스명, 로그 메시지

로그 레벨 설정
hello.springmvc 패키지와 그 하위 로그 레벨 설정
1. application.properties
logging.level.hello.springmvc=debug
2. hello.springmvc.*
log.trace("trace log={}", name);
log.debug("debug log={}", name);
log.info("info log={}", name);
log.warn("warn log={}", name);
log.error("error log={}", name);
3. console
... DEBUG ...
... INFO ...
... WARN ...
... ERROR ...
LEVEL: TRACE > DEBUG > INFO > WARN > ERROR
개발 서버는 debug 출력
운영 서버는 info 출력
디폴트는 info
@Slf4j 클래스 레벨 : logger 객체를 직접 정의하지 않고 로그 메서드 사용 가능