하위 폴더 생성하기
Maven 프로젝트 폴더 생성 후 다음과 같은 하위 폴더들을 추가로 생성해준다.
src/main/java: java 코드 및 spring 설정 클래스 저장 위치src/main/webapp: html, css, js 파일 저장 위치src/main/webapp/WEB-INF: web.xml 파일 저장 위치src/main/webapp/WEB-INF/view: 컨트롤러 수행 결과를 보여줄 jsp 파일 위치
DB 테이블 생성
연습용으로 사용할 테이블은 다음과 같이 생성했다. (캡처본의 경우 이미 insert문으로 2개 행을 추가한 상태라 다음에 부여할 id값이 3으로 증가한 상태이다.)
이후의 연습을 위한 추가 준비 작업 목록
- 서비스 수행용 클래스 및 DAO 클래스
: 이전의 내용을 공부하면서 연습용으로 작성한 예제 코드와 그 과정에서 추가로 작성했던 java 코드들을 활용하였다.pom.xml파일 작성 (이전 내용을 공부할 때 작성했던 코드 재활용)
- Spring MVC, jsp 기반 웹 어플리케이션 개발용 모듈 추가
- JDBC 연동용 모듈 추가
- DB 커넥션 풀 기능 제공 모듈 추가
- DB 연결용 JDBC 드라이버 제공 모듈 추가
- 트랜잭션 처리 내역 확인용 로그 메시지 출력 모듈 추가
src/main/java/config폴더 내 Spring 설정 클래스 추가
(이전 내용을 공부할 때 작성했던 코드 재활용)
- 1.의 클래스에 관한 Bean 객체 Bean 추가
- DataSource 및 트랜잭션 관련 Bean 추가
- Spring MVC 관련 기본 설정 추가
src/main/webapp/WEB-INF/web.xml파일 작성
:DispatcherServlet에 관한 초기 설정 및 Spring MVC 관련 설정 내용을 웹 어플리케이션에 반영하기 위한 코드로, 3.의 설정 클래스를 등록하는 부분만 현재 실습할 내용에 맞게 수정하였다.
위 작업들을 수행한 내역과 이후의 실습 과정에서 작성했던 코드들은 모두 깃허브 주소로 남겼다.
웹 어플리케이션의 개발은 기본적으로 다음의 코드를 작성하는 것이다.
1. 특정 요청 URL을 처리할 코드
:@Controller어노테이션을 적용한 컨트롤러 클래스에서@RequestMapping,@GetMapping,@PostMapping등의 요청 매핑 어노테이션을 사용하여 메서드가 처리할 요청 경로를 지정하는 방식으로 구현하는 코드를 작성한다.
2. 처리 결과를 HTML과 같은 형식으로 응답하는 코드
:src/main/webapp/WEB-INF/view폴더 내부에 HTTP 요청에 관한 컨트롤러 클래스에서의 응답 결과를 출력할 view 코드를.jsp파일에 작성한다.(.jsp파일을 작성할 시 html 문법을 이용한다.)
@RequestMapping, @GetMapping, @PostMapping와 같은 요청 매핑 어노테이션은 위의 2가지 작업 중 첫번째 작업에서 주로 활용된다. 이 때, 요청 매핑 어노테이션은 단일 컨트롤러 클래스에 포함된 여러 개의 메서드에 각각 적용될 수도 있다.
따라서 회원 가입 기능처럼 여러 개의 단계를 거쳐 하나의 기능이 완성되는 경우, 요청 매핑 어노테이션을 사용하여 관련 요청 경로를 1개의 컨트롤러 클래스에서 처리하도록 구현할 수 있다.
다만 Spring MVC에서는 클래스에 적용된 요청 매핑 어노테이션과 메서드에 적용된 요청 매핑 어노테이션의 경로를 합쳐 요청 경로를 탐색하기 때문에, 이를 고려하지 않고 경로를 지정하면 HTTP Status 404 에러가 발생한다.
@RequestMapping("경로명")
이 어노테이션의 method 속성값으로 별도의 HTTP 메서드를 지정하지 않은 경우 GET/POST 메서드에 관계없이 이 어노테이션에서 지정한 경로와 일치하는 요청을 처리한다.@GetMapping("경로명") = @RequestMapping(value="경로명", method="RequestedMethod.GET)
이 어노테이션에서 지정한 경로에 관한 GET 메서드 요청을 처리한다.@PostMapping("경로명") = @RequestMapping(value="경로명", method="RequestedMethod.POST)
이 어노테이션에서 지정한 경로에 관한 GET 메서드 요청을 처리한다.
또한 동일한 요청 경로에 대해 @GetMapping @PostMapping을 사용하여 각기 다른 메서드가 HTTP GET 메서드와 HTTP POST 메서드를 처리하도록 설정할 수 있다.
@Controller
public class RegistController {
...생략
@PostMapping("/register/step2")
public String handleStep2(@RequestParam(value="agree", defaultValue = "false") Boolean agree) {
if (!agree) {
return "register/step1";
}
return "register/step2";
}
@GetMapping("/register/step2")
public String handleStep2Get() {
return "redirect:register/step1";
}
... 생략
}
회원 가입 페이지처럼 여러 개의 단계를 거쳐 단일한 기능이 완성되는 경우, 이전 단계에 해당되는 웹페이지에서 다음 단계로 진행하기 위한 조건을 충족해야 다음 단계를 수행할 수 있는 페이지로 넘어갈 수 있다. 컨트롤러 클래스의 메서드를 통해 회원 가입 페이지의 이러한 메커니즘을 구현할 때 사용되는 것이 바로 요청 파라미터이다. 컨트롤러의 메서드에서 요청 파라미터를 사용하는 방법은 다음의 두 가지가 있다.
HttpServletRequest타입을 매개변수로 직접 사용하기
컨트롤러의 메서드에서 해당 타입의getParameter()메서드를 호출하여 파라미터의 값을 구한다.@RequestParam사용하기
요청 파라미터의 개수가 적을 때 해당 어노테이션을 사용하여 간단히 요청 파라미터의 값을 구할 수 있다. 이 어노테이션의 속성은 다음과 같다.
@RequestParam의 속성
지정한 요청 매핑 어노테이션이 지원하지 않는 HTTP 메서드로 요청이 들어왔을 때, 별도의 조치가 없다면 HTTP Status 405 에러가 발생한다. 이처럼 잘못된 전송 방식으로 요청이 들어왔다면 에러 화면을 출력하는 대신 알맞은 경로로 리다이렉트하는 방식으로 조치하는 경우가 많다.
컨트롤러에서 특정 페이지로 리다이렉트할 때는 컨트롤러 메서드의 리턴할 view 이름으로 redirect:경로를 지정하는 방식으로 간단히 수행할 수 있다.
@Controller
public class RegistController {
... 코드 생략
@GetMapping("/register/step2")
public String handleStep2Get() {
return "redirect:register/step1";
}
}
요청 매핑 관련 어노테이션을 적용한 메서드가 redirect:로 시작하는 경로를 리턴할 경우, 나머지 경로를 이용하여 리다이렉트할 경로를 구한다.
redirect:뒤의 경로가/로 시작
: 웹 어플리케이션을 기준으로 리다이렉트할 이동 경로를 생성한다.
(redirect:/register/step1반환 시 이동 경로는/[웹 어플리케이션 경로]/register/step1)redirect:뒤의 경로가/로 시작하지 않음
: 현재 경로를 기준으로 한 상대 경로를 사용하여 리다이렉트할 경로를 생성한다.
(redirect:step1반환 시
이동 경로는http://localhost:8080/[현재 요청 경로의 상위 폴더 경로]/step1)redirect:URL
: 해당 URL이 가리키는 경로로 리다이렉트한다.
커맨드(command) 객체는 요청 파라미터의 값을 전달받을 수 있는 setter를 갖춤으로써 요청 매핑 어노테이션이 적용된 메서드의 파라미터로 사용될 수 있는 객체이다. Spring은 요청 파라미터의 값을 커맨드 객체에 저장해주는 기능을 제공하여 요청 파라미터의 개수가 많을 때도 각각의 파라미터값을 읽어오는 작업의 번거로움을 줄인다.
View 코드에서의 커맨드 객체는 ${커맨드객체_이름[.사용_속성]}와 같은 형식으로 표기된다. 만약 별도의 설정이 없다면 커맨드 객체의 이름은 해당 커맨드 객체의 클래스명에서 첫 글자만 소문자로 변경한 것과 동일하다.
관련 어노테이션
@ModelAttribute("변경할 커맨드 객체 이름")어노테이션
: 이 어노테이션은 컨트롤러 메서드의 커맨드 객체로 사용할 파라미터에 적용한다. 해당 어노테이션을 사용하여 커맨드 객체의 이름을 별도로 지정할 수 있다.
홈페이지 화면이 단순히 환영 문구와 회원가입 페이지로 이동하는 링크만 제공한다고 가정했을 때, 홈페이지에 관한 요청은 특별한 로직 없이 요청 경로를 받아 View 이름을 리턴하는 방식으로 처리될 것이다. 이러한 컨트롤러 코드는 단순히 요청 경로를 View 이름과 연결하는 것에 불과하다.
이처럼 특별한 로직을 필요로 하지 않고 View 이름만 반환받으면 되는 요청 경로일 경우, 컨트롤러에서 별도의 메서드를 구현하는 대신 WebMvcController 인터페이스의 addViewController() 메서드를 오버라이딩하는 방식을 취한다.
@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB-INF/view/", ".jsp");
}
/* addViewController() : "/main" 요청 경로에 대한 view 이름으로 "main"을 사용 */
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/main").setViewName("main");
}
}
### HTTP Status 404 - Not Found
404 에러는 요청 경로를 처리할 컨트롤러 또는 WebMvcConfigurer 인터페이스를 이용한 설정이 누락되었거나 view 이름에 해당하는 jsp 파일이 존재하지 않는 경우에 발생하는 에러로, 해당 에러가 발생했다면 다음의 사항들을 확인해야 한다.
- 요청 경로가 올바른지 확인하기
- 컨트롤러에 설정한 경로가 올바른지 확인하기
- 컨트롤러 클래스를 Bean으로 등록했는지 확인하기
- 컨트롤러 클래스에
@Controller어노테이션을 적용했는지 확인하기- view 이름에 해당하는
.jsp파일이 존재하는지 확인하기
### HTTP Status 405 - Method Not Allowed
405 에러는 지원하지 않는 전송 방식(HTTP method)을 사용한 경우 발생하는 에러로, 만약 해당 에러가 발생했다면 POST 메서드만 처리하는 요청 경로에 GET 메서드 요청을 보내는 등의 문제가 있었는지를 확인해야 한다.
### HTTP Status 400 - Bad Request
400 에러는 요청 파라미터의 값이 올바르지 않은 경우 발생하는 에러로, 해당 에러가 발생했다면 다음의 사항들을 확인해야 한다.
@RequestParam(value="파라미터명", required=true)로 설정했을 때, value 속성의 값으로 지정한 요청 파라미터명을 지정된 view 코드에서 찾을 수 없는 경우- 복사할 요청 파라미터의 값이 그 값을 저장할 커맨드 객체의 프로퍼티 타입과 불일치하는 경우
그리고 400 에러가 발생했을 경우, 웹 브라우저에 표시되는 에러만으로는 정확한 원인을 찾기 어렵기 때문에 콘솔창에 출력된 로그를 참고하는 것이 좋다.
### HTTP Status 500 - Internal Server Error
500 에러는 Server가 웹 브라우저에서의 요청을 처리하는 도중 발생한 문제로 인한 에러로, 책의 내용대로 직접 실습해보는 과정에서 추가로 경험했던 오류들이다.
1.
org.apache.jasper.JasperException
jasper를 통한 jsp 코드를 컴파일하는 과정에서 taglib를 제대로 인식하지 못하는 것으로 인해 발생했던 오류로, jsp에서의 taglib 문법이 변경되어 과거 Spring5를 사용했을 당시의 taglib 문법이 더 이상 유효하지 않게 되어 발생한 문제였다. (jsp taglib 인식 오류 해결 방법)
2. Spring MVC<form:form>태그의modelAttribute속성값 인식 문제
: 컨트롤러의 해당 요청 경로를 처리하는 메서드에서 지정한 속성값을 이름으로 삼은 Model이 제대로 추가되었는지, 혹은modelAttribute의 속성값이 되는 이름이 올바른지 확인하기
3. 커맨드 객체(View 코드의 ${} 부분)의 값 미출력 문제
: 해당 문제가 발생한 view 코드에 대한 요청을 처리하는 컨트롤러 메서드의 매개변수에@ModelAttribute을 사용하여 별도의 커맨드 객체명을 지정했는지, 혹은 해당 jsp 코드에서 사용한 커맨드 객체명이 올바르게 작성되었는지 확인한다.
용어 정리
- 중첩 프로퍼티 : 일정한 값을 갖는 프로퍼티를 다시 보유하는 프로퍼티
- 콜렉션 프로퍼티: List 등 콜렉션 타입의 값을 갖는 프로퍼티
Spring MVC는 커맨드 객체가 콜렉션 타입의 프로퍼티를 가졌거나 중첩 프로퍼티를 보유한 경우에도 다음 규칙을 따라 HTTP 요청 파라미터의 값을 알맞게 커맨더 객체에 설정해주는 기능을 제공한다.
프로퍼티이름[인덱스]형식 : List 타입 프로퍼티의 값 목록으로 처리프로퍼티이름.프로퍼티이름형식 : 중첩 프로퍼티 값을 처리
View에 데이터를 전달하는 컨트롤러는 다음의 2가지 작업을 수행한다.
- 요청 매핑 어노테이션이 적용된 메서드의 파라미터로 Model 추가
- Model 파라미터의
addAttribute()메서드로 View에서 사용할 데이터 전달
addAttribute() 메서드의 첫번째 파라미터는 속성 이름으로, View 코드는 이 이름을 통해 데이터에 접근하고, jsp는 ${속성명}과 같은 표현식을 사용하여 속성값에 접근한다.
컨트롤러에서 ModelAndView를 사용하면 다음의 두 작업을 한번에 처리할 수 있다.
- Model을 이용해서 View에 전달할 데이터 설정
:ModelAndView의addObject()메서드를 사용한다. 이 메서드의 사용법은Model의addAtribute()메서드와 동일하다.- 결과를 보여줄 View 이름 리턴
:ModelAndView의setViewName()을 통해 결과를 보여줄 View의 이름을ModelAndView객체에 저장한다. view의 이름을 리턴하고자 할 때는 생성한ModelAndView객체를 대신 반환한다.
아래의 두 메서드는 동일한 코드이다.
/* ModelAndView 객체를 직접적으로 사용하지 않은 코드 */
@GetMapping
public String form(Model model) {
List<Question> questions = createQuestions();
model.addAttribute("questions", questions);
return "survey/surveyForm";
}
/* ModelAndView 객체를 직접적으로 사용한 코드 */
@GetMapping
public ModelAndView form() {
List<Question> questions = createQuestions();
ModelAndView mav = new ModelAndView();
mav.addObject("questions", questions);
mav.setViewName("survey/surveyForm");
return mav;
}