1. 스프링 Web MVC의 특징
- 스프링 Web MVC는 웹 MVC 패턴으로 구현된 구조
- 기본적인 흐름과 Controller, View, Model 등의 용어도 그대로 사용
- 스프링 MVC가 기존 구조와 다른 부분
- Front-Controller패턴을 이용해서 모든 흐름의 사전 / 사후 처리 가능
- 어노테이션을 적극 활용, 최소한의 코드로 많은 처리 가능
- HttpServletRequest / HttpServletResponse 이용하지 않아도 될 만큼 추상화된 방식으로 개발 가능
- 스프링 MVC의 전체 흐름
1) DispatcherServlet과 Front Controller
- 스프링 MVC의 모든 요청은 반드시 DispatcherServlet이라는 존재를 통해서 실행됨
- Front-Controller 패턴을 이용하면 모든 요청이 반드시 하나의 객체를 지나서 처리되어 모든 공통적인 처리를 Front-Controller에서 처리 가능
- 스프링 MVC에서 DispatcherServlet이라는 객체가 Front-Controller 역할 수행
- Front-Controller가 사전 / 사후에 대한 처리를 하게 되면 중간에 매번 다른 처리를 하는 부분만 별도로 처리하는 구조를 만들게 됨(이 부분이 Controller이고 @Controller를 이용해서 처리)
실습
1) 스프링 MVC 사용하기
- 프로젝트의 webapp 폴더 > resources 폴더 생성: 이미지나 html 파일 같은 정적인 파일을 서비스하기 위한 경로
- webapp 폴더 > WEB-INF > servlet-context.xml 생성
<!-- servlet-context.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc = "http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 스프링 MVC 설정을 어노테이션 기반으로 처리한다는 의미, 스프링 MVC의 여러 객체들을 자동을 ㅗ스프링의 Bean으로 등록하게 하는 기능 -->
<mvc:annotation-driven></mvc:annotation-driven>
<!-- 이미지나 html 파일 같은 정적인 파일 경로 지정 -->
<!-- "/resources" 경로로 들어오는 요청은 정적인 파일을 요구하는 것으로 판단하고 스프링 MVC에서 처리하지 않는다는 의미 -->
<mvc:resources mapping = "/resources/**" location = "/resources/"></mvc:resources>
<!-- InternalResourceViewResolver는 스프링 MVC에서 제공하는 View를 어떻게 결정하는지에 대한 설정 담당 -->
<bean class = "org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name = "prefix" value = "/WEB-INF/views/"></property>
<property name = "suffix" value = ".jsp"></property>
</bean>
</beans>
2) web.xml의 DispatcherServlet 설정
- 스프링 MVC 실행을 위해 Front-Controller 역할을 하는 DispatcherServlet 설정
<!-- web.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
...
<!-- DispatcherServlet 로딩 시 servlet-context.xml을 이용하도록 설정 -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/servlet-context.xml</param-value>
</init-param>
<!-- Tomcat 로딩 시 클래스를 미리 로딩해두기 위한 설정 -->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- DispatcherServlet이 모든 경로의 요청에 대한 처리를 담당하기 때문에 '/'fh wlwjd -->
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
실습
2) 스프링 MVC Controller
- 스프링 MVC Controller의 다른 점
- 상속이나 인터페이스를 구현하는 방식을 사용하지 않고 어노테이션만으로 처리 가능
- 오버라이드 없이 필요한 메서드 정의
- 메서드의 파라미터를 기본 자료형이나 객체 자료형을 마음대로 지정
- 메서드의 리턴타입도 void, String, 객체 등 다양한 타입 사용 가능
- org.zerock.springex 프로젝트 내에 controller 패키지 추가 > SampleController 클래스 추가
// SampleController
package org.zerock.springex.controller;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
// 해당 클래스가 MVC에서 Controller 역할을 한다는 의미, 스프링의 Bean으로 처리되기 위해 사용
@Controller
@Log4j2
public class SampleController {
// GET 방식으로 들어오는 요청을 처리하기 위해 사용("/hello"라는 경로를 호출할 때 동작)
@GetMapping("/hello")
public void hello() {
log.info("hello........" );
}
}
3) servlet-context.xml의 component-scan
- controller 패키지에 존재하는 Controller 클래스들을 스프링으로 인식하기 위해 @Controller 어노테이션이 추가된 클래스들의 객체들이 스프링의 Bean으로 설정되게 만들어야 함
- servelt-context.xml의 component-scan을 다음과 같이 적용
<!-- servlet-context.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc = "http://www.springframework.org/schema/mvc"
xmlns:context = "http://www.springframework.org/schema/context"
xsi:schemaLocation=
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
...
<context:component-scan base-package = "org.zerock.springex.controller" />
</beans>
- "/hello" 경로의 화면을 간단하게 작성(webapp > WEB-INF > views 폴더 생성 > hello.jsp 파일 생성)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>Hello JSP</h1>
</body>
</html>
4) @RequestMapping과 파생 어노테이션들
- @RequestMapping은 특정 경로의 요청을 지정하기 위해 사용
- 클래스 선언부에도 사용할 수 있고, Controller의 메서드에도 사용할 수 있음
- Servlet 중심의 MVC에서는 Servlet을 상속받아서 doGet() / doPost()와 같은 제한적인 메서드를 오버라이드해서 사용했지만, 스프링 MVC의 경우 하나의 Controller를 이용해서 여러 경로의 호출 처리 가능
- org.zerock.springex > controller 패키지 > TodoController 작성
// TodoController
package org.zerock.springex.controller;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping("/todo")
@Log4j2
public class TodoController {
@RequestMapping("/list")
public void list() {
log.info("tood list..........");
}
@RequestMapping(value = "/register", method = RequestMethod.GET)
public void register() {
log.info("todo register");
}
}
- @RequestMapping을 이용하는 것만으로 여러 Controller를 하나의 클래스로 묶을 수 있고, 각 기능마다 메서드 단위로 설계할 수 있게 되어 많은 양의 코드 줄일 수 있음
- 스프링 4버전 이후에는 @GetMapping / @PostMapping 어노테이션으로 GET/ POST 방식 구분해서 처리 가능
- 예를 들어, "/todo/register"는 GET 방식으로 화면을 보여주고, POST 방식으로 처리하므로 다음과 같이 설계
// TodoController
package org.zerock.springex.controller;
import ...
@Controller
@RequestMapping("/todo")
@Log4j2
public class TodoController {
// 클래스 선언부에서 RequestMapping의 value가 "/todo"이고 list() 메서드에서 ReuestMapping의 value가 "/list"이므로
// 최종 경로는 "/todo/list"가 됨
@RequestMapping("/list")
public void list() {
log.info("tood list..........");
}
// @RequestMapping(value = "/register", method = RequestMethod.GET)
@GetMapping("/register")
public void registerGET() {
log.info("GET todo register...............");
}
@PostMapping("/register")
public void registerPOST() {
log.info("POST todo register...............");
}
}
2. 파라미터 자동 수집과 변환
- 파라미터 자동 수집은 DTO, VO 등을 메서드의 파라미터로 설정하면 자동으로 전달되는 HttpServletRequest의 파라미터들을 수집해주는 기능
- 단순 문자열만이 아니라 숫자, 배열, 리스트, 첨부 파일도 가능
- 파라미터 수집 동작 기준
- 기본 자료형의 경우 자동으로 형 변환처리 가능
- 객체 자료형의 경루 setXXX()를 통해 처리
- 객체 자료형의 경우 생성자가 없거나 파라미터가 없는 생성자가 필요(Bean)
실습
3) 단순 파라미터의 자동 수집
- SampleController에서의 예시
// SampleController
package org.zerock.springex.controller;
import ...
// 해당 클래스가 MVC에서 Controller 역할을 한다는 의미, 스프링의 Bean으로 처리되기 위해 사용
@Controller
@Log4j2
public class SampleController {
...
@GetMapping("/ex1")
public void ex1(String name, int age) {
log.info("ex1.......");
log.info("name: " + name);
log.info("age: " + age);
}
}
- 주소를 "http://localhost:8080/ex1?name=AAA&age=16"로 설정하면 자동으로 name은 문자열 AAA로, age는 숫자 16으로 파라미터를 수집해와서 로그에 출력
- @RequestParam
- 요청에 전달된 파라미터 이름을 기준으로 동작하지만, 간혹 파라미터가 전달되지 않으면 문제 발생할 수 있음
- 이 때 @RequestParam이라는 어노테이션 고려
- @RequestParam은 defaultValue라는 속성이 있어서 '기본값'을 지정할 수 있음
package org.zerock.springex.controller;
import ...
// 해당 클래스가 MVC에서 Controller 역할을 한다는 의미, 스프링의 Bean으로 처리되기 위해 사용
@Controller
@Log4j2
public class SampleController {
...
@GetMapping("/ex2")
public void ex2(@RequestParam(name = "name", defaultValue = "AAA") String name,
@RequestParam(name = "age", defaultValue = "20") int age) {
log.info("ex2.......");
log.info("name: " + name);
log.info("age: " + age);
}
}
- 주소에 "http://localhost:8080/ex2"만 입력하고 파라미터를 주지 않아도 기본값으로 파라미터를 받아서 로그로 출력함
- Formatter를 이용한 파라미터의 커스텀 처리
- 기본적으로 HTTP는 문자열로 데이터를 전달하기 때문에 Controller는 문자열을 기준으로 특정 클래스의 객체로 처리하는 작업이 진행
- 이때 날짜 등 특정 타입을 처리하는 Formatter 이용 가능
- Formatter는 문자열을 포맷을 이용해서 특정 객체로 변환하는 경우 사용
- controller 패키지 > formatter 패키지 작성 > LocalDateFormatter 클래스 작성
- Formatter에는 parse()와 print() 메서드 존재
// LocalDateFormatter
package org.zerock.springex.controller.formatter;
import org.springframework.format.Formatter;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class LocalDateFormatter implements Formatter<LocalDate> {
@Override
public LocalDate parse(String text, Locale locale) {
return LocalDate.parse(text, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
}
@Override
public String print(LocalDate object, Locale locale) {
return DateTimeFormatter.ofPattern("yyyy-MM-dd").format(object);
}
}
- Formatter를 servlet-context.xml에 적용하기 위해 FormattingConversionServiceFactoryBean 객체를 스프링의 Bean으로 등록하고 이 안에 작성한 LocalDateFormatter를 추가해야 함
<!-- servlet-context.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<beans ...>
<!-- conversionService라는 Bean을 등록한 후에 스프링 MVC를 처리할 때 <mvc:annotation-driven에 이를 이용한다는 것을 명시해야 함 -->
<mvc:annotation-driven conversion-service = "conversionService"></mvc:annotation-driven>
...
<bean id = "conversionService" class = "org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name = "Formatters">
<set>
<bean class = "org.zerock.springex.controller.formatter.LocalDateFormatter"/>
</set>
</property>
</bean>
</beans>
- ex3 경로에 날짜를 파라미터로 받는 메서드 추가후 날짜 형변환 정상 작동 여부 확인
// SampleController
package org.zerock.springex.controller;
import ...
@Controller
@Log4j2
public class SampleController {
...
@GetMapping("/ex3")
public void ex3(LocalDate dueDate) {
log.info("ex3.......");
log.info("dueDate: " + dueDate);
}
}
- 주소로 "http://localhost:8080/ex3?dueDate=2020-10-10"를 작성하면 날짜형태로 잘 받아서 로그에 출력함을 확인
1) 객체 자료형의 파라미터 수집
- 기본 자료형과 달리 객체 자료형을 파라미터로 처리하기 위해서는 객체가 생성되고 setXXX()을 이용해서 처리
- Lombok을 활용하면 @Setter나 @Data를 이용하는 것이 간단
- 프로젝트에 dto 패키지 추가 > TodoDTO 클래스 추가
// TodoDTO
package org.zerock.springex.dto;
import lombok.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.time.LocalDate;
@ToString
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class TodoDTO {
private Long tno;
private String title;
private LocalDate dueDate;
private boolean finished;
private String writer;
}
※ javax.validation을 사용하려면 build.gradle에 다음을 의존성으로 추가해야함
implementation group: 'javax.validation', name: 'validation-api', version: '2.0.1.Final'
- TodoController의 '/todo/register'를 POST 방식으로 처리하는 메서드에 TodoDTO를 파라미터로 적용
package org.zerock.springex.controller;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.zerock.springex.dto.TodoDTO;
@Controller
@RequestMapping("/todo")
@Log4j2
public class TodoController {
@RequestMapping("/list")
public void list() {
log.info("tood list..........");
}
// @RequestMapping(value = "/register", method = RequestMethod.GET)
@GetMapping("/register")
public void registerGET() {
log.info("GET todo register...............");
}
// TodoDTO를 파라미터로 적용
// 자동으로 형변환되기 때문에 다양한 타입의 멤버 변수들의 처리가 자동으로 이루어짐
@PostMapping("/register")
public void registerPOST(TodoDTO todoDTO) {
log.info("POST todo register...............");
log.info(todoDTO);
}
}
- WEB-INF/views에 todo 폴더 생성 > register.jsp 파일 추가
<!-- register.jsp -->
<%@ taglib prefix ="form" uri = "http://www.springframework.org/tags/form" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action = "/todo/register" method = "post">
<div>
Title: <input type = "text" name = "title">
</div>
<div>
DueDate: <input type = "date" name = "dueDate" value = "2023-01-17">
</div>
<div>
Writer: <input type = "text" name = "writer">
</div>
<div>
Finished: <input type = "checkbox" name = "finished">
</div>
<div>
<button type = "submit">Register</button>
</div>
</form>
</body>
</html>
- 'localhost:8080/todo/register'에 들어가면 위와 같은 입력 화면이 나오고, 각 입력창에 데이터를 입력한 뒤, Register 버튼을 누르면 로그에 해당 데이터가 출력됨
2) Model이라는 특별한 파라미터
- 웹 MVC와 스프링 MVC 모두 Model이라고 부르는 데이터를 JSP에 전달해야함
- Servlet 방식에서는 request.setAttribute()를 이용해서 데이터를 담아 JSP에 전달
- 스프링 MVC 방식에서는 Model이라는 객체를 이용해서 처리 가능
- Model에서 addAttribute() 메서드를 이용해서 View에 전달할 이름과 값을 지정
// SampleController
package org.zerock.springex.controller;
import ...
@Controller
@Log4j2
public class SampleController {
...
@GetMapping("/ex4")
public void ex4(Model model) {
log.info("----------------");
model.addAttribute("message", "Hello World");
}
}
- 데이터를 받을 ex4.jsp 파일도 WEB-INF/views 폴더에 생성
<!-- ex4.jsp -->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri ="http://java.sun.com/jsp/jstl/core" prefix = "c" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>${message}</h1>
<h1><c:out value = "${message}"></c:out></h1>
</body>
</html>
- Model에 담긴 데이터는 HttpServletRequest의 setAttribute()와 동일한 동작을 수행하여 JSP에서 EL을 이용해 별다른 처리없이 사용 가능
- 스프링 MVC의 Controller는 파라미터로 getter / setter를 이용하는 Java Beans의 형식의 사용자 정의 클래스가 파라미터인 경우 자동으로 화면까지 객체 전달
- 예를 들어, 파라미터로 TodoDTO를 받는 경우, 다음과 같이 작성하고, JSP에서 ${todoDTO}를 통해 이용 가능
@GetMapping("/ex4_1")
public void ex4Extra(TodoDTO todoDTO, Model model) {
log.info(todoDTO);
}
- 이때 자동으로 생성된 todoDTO라는 이름 외에 다른 이름을 사용하고 싶으면 @ModelAttribute()를 사용하여 지정가능
@GetMapping("/ex4_1")
public void ex4Extra(@ModelAttribute("dto") TodoDTO todoDTO, Model model) {
log.info(todoDTO);
}
- 이렇게 하면 ${dto}로 사용가능
3) RedirectAttributes와 리다이렉션
- POST 방식으로 어떤 처리를 하고 Redirect 해서 GET 방식으로 특정 페이지 이동하는 PRG 패턴 처리를 위해 스프링 MVC에서는 RedirectAttributes라는 타입 사용
- RedirectAttributes의 중요 메서드
- addAttribute(키, 값): Redirect할 때 쿼리 스트링 값 적용
- addFlashAttribute(키, 값): 일회용으로 데이터만 전달하고 삭제되는 값 지정
- SampleController에서 ex5()는 RedirectAttributes를 파라미터로 추가하고 addAttribute와 addFlashAttribute를 사용해서 name과 result라는 이름을 가진 값들을 전달
// SampleController
package org.zerock.springex.controller;
import ...
@Controller
@Log4j2
public class SampleController {
...
@GetMapping("/ex5")
public String ex5(RedirectAttributes redirectAttributes) {
redirectAttributes.addAttribute("name", "ABC");
redirectAttributes.addFlashAttribute("result", "success");
// redirect를 위해 "redirect:"라는 접두어를 붙여 문자열로 반환
return "redirect:/ex6";
}
@GetMapping("/ex6")
public void ex6() {
}
}
- ex5에서 넘겨준 값을 받은 ex6의 jsp 화면 작성
<!-- ex6.jsp -->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>ADD FLASH ATTRIBUTE: ${result}</h1>
</body>
</html>
- ex5 호출하면 name과 result라는 이름을 가진 값들이 생성되어 ex6으로 redirect됨
- ex6의 화면이 나오고 쿼리 스트링으로 준 name의 값 "ABC"가 주소창에 전달되어 있고, 화면에 ${result}로 출력한 result의 값인 "success"가 출력되어 있음
- addFlashAttribute는 일회용으로 전달하고 사라지므로, 해당 페이지를 새로고침하면, "success"가 사라짐을 확인
4) 다양한 리턴 타입
- 스프링 MVC에서 Controller내에 선언하는 메서드의 리턴 타입을 다양하게 사용 가능
- void: 화면이 따로 있는 경우, @RequestMapping값 과 @GetMapping 등 메서드에서 선언된 값을 그대로 View의 이름으로 사용, 주로 상황에 관계없이 동일한 화면을 보여줄 때 사용
- 문자열: 화면이 따로 있는 경우, 상황에 따라 다른 화면 보여줄 때 사용, 다음과 같은 특별한 접두어 사용가능
- redirect: 리다이렉션을 이용하는 경우, 주로 forward 대신 redirect 이용
- forward: 브라우저의 URL은 고정하고 내부적으로 다른 URL로 처리하는 경우
- 객체나 배열, 기본 자료형: JSON 타입 활용 시
- ResponseEntity: JSON 타입 활용 시
5) 스프링 MVC에서 주로 사용하는 어노테이션
- Controller 선언부에 사용하는 어노테이션
- @Controller: 스프링 Bean의 처리됨을 명시
- @RestController: REST 방식의 처리를 위한 Controller임을 명시
- @RequestMapping: 특정한 URL 패턴에 맞는 Controller인지를 명시
- 메서드 선언부에 사용하는 어노테이션
- @GetMapping / @PostMapping / @DeleteMapping / @PutMapping ...: HTTP 전송방식에 따라 해당 메서드를 지정하는 경우 사용, 일반적으로 @GetMapping과 @PostMapping을 주로 사용
- @RequestMapping: GET / POST 방식 모두 지원하는 경우 사용
- @ResponseBody: REST 방식에서 사용
- 메서드의 파라미터에 사용하는 어노테이션
- @RequestParam: Request에 있는 특정한 이름의 데이터를 파라미터로 받아서 처리하는 경우 사용
- @PathVariable: URL 경로의 일부를 변수로 삼아서 처리하기 위해 사용
- @ModelAttribute: 해당 파라미터는 반드시 Model에 포함되어 다시 View로 전달됨을 명시(주로 기본 자료형이나 Wrapper 클래스, 문자열에 사용)
- 기타: @SessionAttribute, @Valid, @RequestBody 등
3. 스프링 MVC의 예외 처리
- 스프링 MVC에서 예외를 처리하는 가장 일반적인 방법은 @ControllerAdvice를 이용하는 것
- @ControllerAdvice는 Controller에서 발생하는 예외에 맞게 처리할 수 있는 기능 제공
- @ControllerAdvice가 선언된 클래스 역시 스프링의 Bean으로 처리됨
- 실습을 위해 controller 패키지 > exception 패키지 > CommonExceptionAdvice 클래스 작성
// CommonExceptionAdvice
package org.zerock.springex.controller.exception;
import lombok.extern.log4j.Log4j2;
import org.springframework.web.bind.annotation.ControllerAdvice;
@ControllerAdvice
@Log4j2
public class CommonExceptionAdvice {
}
1) @ExceptionHandler
- @ControllerAdvice의 메서드들에는 특별하게 @ExceptionHandler라는 어노테이션 사용 가능
- 이를 이용해서 전달되는 Exception 객체들을 지정하고 메서드의 파라미터에서 이를 이용 가능
- 고의로 예외를 발생시키는 코드 작성하여 실험
// SampleController
package org.zerock.springex.controller;
import ...
@Controller
@Log4j2
public class SampleController {
...
// p1에는 문자열이, p2에는 숫자가 전달되어야 함
@GetMapping("/ex7")
public void ex7(String p1, int p2) {
log.info("p1........" + p1);
log.info("p2........" + p2);
}
}
- 해당 코드를 작성하고, p2에 숫자가 아닌 문자열을 쿼리 스트링으로 전달해주면 예외가 발생
- "localhost:8080/ex7?p1=AAA&p2=BBB"를 주면 BBB는 int형이 아니므로 에러 코드 400이 발생
- 해결을 위해 CommonExceptionAdvice에 NumberFormatException을 처리하도록 지정
// CommonExceptionAdvice
package org.zerock.springex.controller.exception;
import lombok.extern.log4j.Log4j2;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
@Log4j2
public class CommonExceptionAdvice {
// 문자열이나 JSON 데이터를 그대로 전송할 때 사용되는 어노테이션
@ResponseBody
// @ExceptionHandler를 가진 모든 메서드는 해당 타입의 예외를 파라미터로 전달받을 수 있음
@ExceptionHandler(NumberFormatException.class)
// exceptNumber()는 @ResponseBody를 이용해서 만들어진 문자열을 그대로 브라우저에 전송하는 방식 이용
public String exceptNumber(NumberFormatException numberFormatException) {
log.error("--------------------------------");
log.error(numberFormatException.getMessage());
return "NUMBER FORMAT EXCEPTION";
}
}
2) 범용적인 예외처리
- 예외 처리의 상위 타입인 Excpetion 타입을 처리하도록 구성
// CommonExceptionAdvice
package org.zerock.springex.controller.exception;
import lombok.extern.log4j.Log4j2;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Arrays;
@ControllerAdvice
@Log4j2
public class CommonExceptionAdvice {
// 문자열이나 JSON 데이터를 그대로 전송할 때 사용되는 어노테이션
@ResponseBody
// @ExceptionHandler를 가진 모든 메서드는 해당 타입의 예외를 파라미터로 전달받을 수 있음
@ExceptionHandler(Exception.class)
// exceptCommon은 Exception 타입을 처리하여 사실상 거의 모든 예외를 처리하는 용도로 사용 가능
public String exceptCommon(Exception exception) {
log.error("--------------------------------");
log.error(exception.getMessage());
// <ul>로 시작하는 buffer 문자열 작성
StringBuffer buffer = new StringBuffer("<ul>");
// 예외 처리 메세지가 발생할 때마다 <li>와 함께 <ul></ul> 안에 리스트 형태로 해당 메세지를 추가
buffer.append("<li>" + exception.getMessage() + "</li>");
// 에러가 났을 때, 현재의 함수나 메서드 명도 같이 출력하여 더 자세히 디버깅할 수 있도록 함
Arrays.stream(exception.getStackTrace()).forEach(stackTrackElement -> {
buffer.append("<li>" + stackTrackElement + "</li>");
});
// 마지막에는 리스트 형식을 끝내도록 </ul> 추가
buffer.append("</ul>");
return buffer.toString();
}
}
3) 404 에러 페이지와 @ResponseStatus
- 서버 내부가 아닌 시작부터 잘못된 URL을 호출할 때 404 예외 발생
- @ControllerAdvice에 작성하는 메서드에 @ResponseStatus를 이용하면 404상태에 맞는 화면을 별도로 작성 가능
// CommonExceptionAdvice
package org.zerock.springex.controller.exception;
import ...
@ControllerAdvice
@Log4j2
public class CommonExceptionAdvice {
...
// 404 에러 대비
@ExceptionHandler(NoHandlerFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public String notFound() {
return "custom404";
}
}
- custom404의 페이지를 jsp 파일로 작성
<!-- custom404 -->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>Oops! 페이지를 찾을 수 없습니다!</h1>
</body>
</html>
- web.xml에서는 DispatcherServlet의 설정을 조정해야함
- <servlet> 태그 내에 <init-param>을 추가하고 throwExceptionIfNoHandlerFound라는 파라미터 설정 추가
<!-- web.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<web-app ...>
...
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/servlet-context.xml</param-value>
</init-param>
<!-- 404에러 처리용으로 추가 -->
<init-param>
<param-name>throwExceptionIfNoHandlerFound</param-name>
<param-value>true</param-value>
</init-param>
<!-- 여기까지 -->
<load-on-startup>1</load-on-startup>
</servlet>
...
</web-app>
'back-end > Java' 카테고리의 다른 글
[자바 웹 개발 워크북] 4.4 - 스프링 Web MVC 구현하기(2) (0) | 2023.02.14 |
---|---|
[자바 웹 개발 워크북] 4.4 - 스프링 Web MVC 구현하기(1) (0) | 2023.02.09 |
[자바 웹 개발 워크북] 4.2 - MyBatis와 스프링 연동 (0) | 2023.01.13 |
[자바 웹 개발 워크북] 4.1 - 의존성 주입과 스프링 (0) | 2023.01.12 |
[자바 웹 개발 워크북] 3.3 - 리스너 (0) | 2023.01.11 |