티스토리 뷰

반응형

오늘 한 내용 6.3 ~ 9전까지

 

Controller의 파라미터 수집

Controller를 작성할 때 가장 편리한 기능 : 파라미터가 자동으로 수집됨 (request.getParameter())를 사용하지 않아도 됨

SampleController의 메서드가 SampleDTO를 파라미터로 사용하게 되면 자동으로 setter 메서드가 동작하여 파라미터를 수집함

//SampleDTO 클래스 

package org.zerock.domain;

import lombok.Data;

@Data
public class SampleDTO {
	private String name;
	private int age;
}

@Data 

Lombok의 @Data 어노테이션을 이용하면 getter/setter, equals(), toString() 등의 메서드를 자동으로 생성하여 편리함

//SampleController 일부

@GetMapping("/ex01")
public String ex01(SampleDTO dto) {
	log.info("" + dto);
	return "ex01";
}

==> 결과물 (http://localhost:9009/sample/ex01&name=AAA&age=10) : SampleDTO 객체 안의 name, age 속성이 제대로 수집

특히, 자동으로 타입을 변환해서 처리한다는 점을 주목할 것.

==> /sample/ex01만 넣었을 때 로그
INFO : org.zerock.controller.SampleController - SampleDTO(name=null, age=0) 

==>/sample/ex01?name=AAA&age=10 넣었을 때 로그
INFO : org.zerock.controller.SampleController - SampleDTO(name=AAA, age=10)

파라미터의 수집과 변환

Controller가 파라미터를 수집하는 방식 : 파라미터 타입에 따라 자동으로 변환하는 방식

SampleDTO에는 int 타입으로 선언된 age가 자동으로 숫자로 변환됨

ex02() 메서드 : 파라미터에 @RequestParam 어노테이션을 사용함

@RequestParam

파라미터로 사용된 변수의 이름과 전달되는 파라미터의 이름이 다른 경우 유용하게 사용됨

@RequestParam("가져올 데이터의 이름") [데이터타입] [가져온데이터를 담을 변수]
//SampleController 일부

@GetMapping("/ex02")
public String ex02(@RequestParam("name") String name, @RequestParam("age") int age) {
	log.info("name : " + name);
	log.info("age: " + age);
	return "ex02";
}

==>결과물 (http://localhost:9009/sample/ex02?name=ABC&age=99)

INFO : org.zerock.controller.SampleController - name : ABC

INFO : org.zerock.controller.SampleController - age: 99

 


리스트, 배열 처리

동일한 이름의 파라미터가 여러 개 전달되는 경우 : ArrayList<> 등을 이용해서 처리가 가능

(1) 리스트의 경우

스프링은 파라미터의 타입을 보고 객체를 생성함 : 파라미터 타입이 List<>와 같이 실제적인 클래스 타입으로 지정함

아래의 경우 'ids'라는 이름의 파라미터가 여러 개 전달되더라도 ArrayList<String>이 생성되어 자동으로 수정됨

//SampleController 일부

@GetMapping("/ex02List")
public String ex02List(@RequestParam("ids") ArrayList<String> ids) {
	log.info("ids: " + ids);
	return "ex02List";
}

==>결과물 (http://localhost:9009/sample/ex02List?ids=111&ids=222&ids=333)

INFO : org.zerock.controller.SampleController - ids: [111, 222, 333]

 

(2) 배열의 경우

//SampleController 일부

@GetMapping("/ex02Array")
public String ex02Array(@RequestParam("ids") String[] ids) {
	log.info("array ids: " + Arrays.toString(ids));
	return "ex02Array";
}

==>결과물 (http://localhost:9009/sample/ex02Array?ids=111&ids=222&ids=333)

INFO : org.zerock.controller.SampleController - array ids: [111, 222, 333]

객체 리스트

전달하는 데이터가 SampleDTO와 같이 객체 타입이고, 여러개를 처리해야 한다면 객체의 리스트를 포함하는 새로운 클래스를 설계하고, 그 클래스를 파라미터로 사용하는 메서드를 작성하여 한번에 처리할 수 있는 방법

SampleDTOList는 SampleDTO의 리스트를 포함하는 클래스 

//SampleDTOList 클래스

package org.zerock.domain;

import java.util.ArrayList;
import java.util.List;

import lombok.Data;

@Data
public class SampleDTOList {
	private List<SampleDTO> list;
	
	public SampleDTOList() {
		list = new ArrayList<>();
	}
}

SampleController 에서는 SampleDTOList 타입을 파라미터로 사용하는 메서드를 작성

//SampleController

@GetMapping("/ex02Bean")
public String ex02Bean(SampleDTOList list) {
	log.info("list dtos : " + list);
	return "ex02Bean";
}

파라미터는 '[인덱스]'와 같은 형식으로 전달해서 처리할 수 있음

✅ 톰캣 버전에 따라 '[ ]'를 특수문자로 허용하지 않을 경우 => '['는 '%5B'로 ']'는 '%5D'로 변경하기

==>결과물 (http://localhost:9009/sample/ex02Bean?list[0].name=AAA&list[1].name=ABC&list[2].age=99)

INFO : org.zerock.controller.SampleController - list dtos : SampleDTOList(list=[SampleDTO(name=AAA, age=0), SampleDTO(name=ABC, age=0), SampleDTO(name=null, age=99)])

출력된 결과를 보면 3개의 SampleDTO 객체가 생성됨 / '[ ]'안에 인덱스 번호에 맞게 객체의 속성값이 세팅되어 있음


@InitBinder

파라미터의 수집을 다른 용어로 binding(바인딩)이라고 한다.

변환이 가능한 데이터는 자동으로 변환되지만, 경우에 따라 파라미터를 변환해서 처리해야 하는 경우도 있다.

스프링 Controller에서는 파라미터를 바인딩할 때 자동으로 호출되는 @InitBinder를 이용해 파라미터의 변환을 처리할 수 있다.

예)전송된 데이터 객체는 무조건 String인데, 그 데이터를 Date 타입으로 받을 때 사용할 수 있음

 

//TodoDTO 클래스

package org.zerock.domain;

import java.util.Date;

import lombok.Data;

@Data
public class TodoDTO {

	private String title;
	private Date dueDate; //java.util.Date 타입
}

dueDate 변수의 타입이 java.util.Date 타입이기 때문에, 사용자가 '2022-10-28'과 같이 들어오는 데이터를 변환하고자 할때 문제가 발생할 수 있음 => @InitBinder를 이용해서 해결할 수 있음

//SampleController

@InitBinder
public void initBinder(WebDataBinder binder) {
	SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
	binder.registerCustomEditor(java.util.Date.class, new CustomDateEditor(dateFormat, false));
}
@GetMapping("/ex03")
public String ex03(TodoDTO todo) {
	log.info("todo: " + todo);
	return "ex03";

 

==>결과물 (http://localhost:9009/sample/ex03?title=test&dueDate=2022-10-28)

INFO : org.zerock.controller.SampleController - todo: TodoDTO(title=test, dueDate=Fri Oct 28 00:00:00 KST 2022)

만약 @InitBinder가 처리되지 않는다면 브라우저에는 400에러가 발생함


@DateTimeFormat

@InitBinder를 이용해서 날짜를 변환할 수도 있지만, 파라미터로 사용되는 인스턴스 변수에 @DateTimeFormat을 적용해서 변환이 가능함

@DateTimeFormat을 이용하는 경우 @InitBinder는 필요하지 않음

 

//TodoDTO 수정

package org.zerock.domain;

import java.util.Date;

import org.springframework.format.annotation.DateTimeFormat;

import lombok.Data;

@Data
public class TodoDTO {

	private String title;
	
	@DateTimeFormat(pattern="yyyy/MM/dd")
	private Date dueDate;
}
@GetMapping("/ex03")
public String ex03(TodoDTO todo) {
	log.info("todo: " + todo);
	return "ex03";
}

문자열로 'yyyy/MM/dd'의 형식이 맞다면 자동으로 날짜 타입으로 변환이 됨 =>InitBinder보다 훨씬 더 쉬운 설정

==>결과물 (http://localhost:9009/sample/ex03?title=test&dueDate=2022/10/28)

INFO : org.zerock.controller.SampleController - todo: TodoDTO(title=test, dueDate=Fri Oct 28 00:00:00 KST 2022)

Model이라는 데이터 전달자

Controller의 메서드를 작성할 때, 특별히 Model 타입을 파라미터로 지정할 수 있음 => View에 전달해야 하는 데이터는 주로 Model 객체에 담아서 전달 (Model은 JSP에 컨트롤러에서 생성된 데이터를 담아서 전달하는 역할)

메서드의 파라미터에 Model 타입이 있는 경우 => 스프링이 알아서 Model 타입의 객체를 만들어서 메서드에 주입함

: 개발자 입장에서는 필요한 데이터를 담아주는 작업만으로 모든 작업이 완료됨

Model은 모델2에서 사용하는 request.setAttribute()와 유사한 역할을 함

Model을 사용해야 하는 경우는 주로 Controller에 전달된 데이터를 이용해서 추가적인 데이터를 가져와야하는 상황에서.

그냥 다 필요없고, 컨트롤러 단에서 처리 다 한 후에 view단으로 데이터 보낼때 어떻게 해야하는지를 말함.


@ModelAttribute 어노테이션

웹 페이지의 구조는 Request에 전달된 데이터를 가지고, 필요하다면 추가적인 데이터를 생성해서 화면으로 전달하는 방식

Model의 경우 파라미터로 전달된 데이터는 존재하지 않지만, 화면에서 필요한 데이터를 전달하기 위해 사용

(예, 페이지 번호는 파라미터로 전달되지만 결과 데이터를 전달하려면 Model 데이터에 담아서)

스프링 MVC의 컨트롤러는 기본적으로 자바빈 규칙에 맞는 객체는 다시 화면으로 객체를 전달함 

좁은 의미에서 자바빈의 규칙은 단순히 생성자가 없거나, 빈 생성자를 가져야 하거나, get/set 클래스를 의미함 => SampleDTO 같은 경우 자바빈 규칙에 맞기 때문에 자동으로 다시 화면까지 전달됨 (전달될 때, 클래스명의 앞글자는 소문자로 처리) 

기본 자료형의 경우 파라미터로 선언하더라도 기본적으로 화면까지 전달되지 않음 ==> 밑의 예시

쉽게 말하자면, 컨트롤러는 자바빈 규칙에 맞는 것들은 view 단으로 전달을 잘 하지만, 기본 자료형의 경우 화면단으로 전달이 안된다는 말.

 

ex04는 SampleDTO타입과 int 타입의 데이터를 파라미터로 사용함

//SampleController 

@GetMapping("/ex04")
public String ex04(SampleDTO dto, int page) {
	log.info("dto: " + dto);
	log.info("page: " +page);
	return "/sample/ex04"; //jsp파일의 위치를 말함
}

결과 확인을 위한 jsp 파일

//ex04.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

<h2>SampleDTO ${sampleDTO }</h2>
<h2>Page ${page }</h2>

</body>
</html>

 

==>결과물 (http://localhost:9009/sample/ex04?name=AAA&age=22&page=5)

로그 기록
INFO : org.zerock.controller.SampleController - dto: SampleDTO(name=AAA, age=22)

INFO : org.zerock.controller.SampleController - page: 5

화면에 SampleDTO만이 전달되었고, int 타입으로 선언된 Page는 전달되지 않음

 

@ModelAttribute

강제로 전달받은 파라미터를 Model에 담아서 전달하도록 할 때 필요한 어노테이션. 

@ModelAttribute가 걸린 파라미터는 타입에 관계없이 무조건 Model 에 담아서 전달되므로, 파라미터로 전달된 데이터를 다시 화면에서 사용해야 할 경우 유용하게 사용됨

기존의 코드에서 int 타입의 데이터가 화면까지 전달되지 않았기 때문에 @ModelAttribute를 추가하면 page까지 잘 나오는 것을 확인할 수 있음

@ModelAttribute가 붙은 파라미터는 화면까지 전달이 잘 되므로, 브라우저를 통해 호출하면 아래와 같이 ${page}까지 출력됨

기본 자료형에 @ModelAttribute를 적용할 경우 @ModelAttribute("page")와 같이 값(value)을 지정하도록!

//SampleController 수정

@GetMapping("/ex04")
public String ex04(SampleDTO dto, @ModelAttribute("page") int page) {
	log.info("dto: " + dto);
	log.info("page: " + page);
	return "/sample/ex04";
}

==>결과물 (http://localhost:9009/sample/ex04?name=AAA&age=22&page=5)


@RedirectAttributes

스프링 MVC가 자동으로 전달해주는 타입인 Redirect Attributes

일회성으로 데이터를 전달하는 용도 => 기존의 response.sendRedirect()를 사용할 때랑 동일한 용도

Model과 같이 파라미터로 선언해서 사용

addFlashAttribute(이름, 값) 메서드를 이용 : 화면에 한 번만 사용하고 다음에는 사용되지 않는 데이터를 전달하기 위함

request 영역에 저장한 객체들은 redirect하면 못가져다 씀 => 같은 요청이 아니기 때문. 그래서 redirect 할 수 있게 가져다 쓰는 방법이 바로 이 어노테이션.

더보기

request는 브라우저에서 서버로 요청

response는 request한 내용을 서버에서 받아서 해석한 다음 적절한 데이터를 브라우저로 전송

get 방식 : ?name=AAA&age=11 과 같이 url에 직접 쓰는것
post 방식 : 데이터영역을 request header에 담아서, 서버로 보내는 것

redirect : 특정 페이지로 이동하라고 서버가 브라우저에게 말해주는 것

[servlet에서 특정 URL, 페이지로 이동하라고 하는 두 가지 방식]
1) Dispatcher 방식 : forward() ==> 클라이언트가 요청하면서 전송한 데이터를 그대로 유지. 주소 변경X(같은 request영역 공유)
2) Redirect 방식: sendRedirect() ==> 새로운 페이지로 완전히 이동하기 때문에 기존 데이터를 그대로 사용할 수 X


Controller의 리턴 타입

스프링 MVC 구조가 상속,인터페이스 ==> 어노테이션 사용하는 방식으로 변하고 나서 가장 큰 변화는 리턴 타입이 자유로워졌다는 것

[Controller의 메서드가 사용할 수 있는 리턴 타입]

▲ String: jsp를 이용하는 경우, jsp 파일의 경로와 파일 이름을 나타내기 위해 사용

▲ void : 호출하는 URL과 동일한 이름의 jsp를 의미

▲ VO, DTO 타입 : 주로 JSON 타입의 데이터를 만들어서 반환하는 용도

▲ ResponseEntity 타입 : response할 때 Http 헤더 정보와 내용을 가공하는 용도

▲ Model, ModelAndView : Model로 데이터를 반환하거나 화면까지 같이 지정하는 경우에 사용

▲ HttpHeaders : 응답에 내용 없이 Http 헤더 메시지만 전달하는 용도로 사용


void 타입

메서드의 리턴 타입을 void로 지정하는 경우 ==> 일반적인 경우 해당 URL의 경로를 그대로 jsp 파일의 이름으로 사용함

결론은 /ex05라는 jsp를 찾기 때문에 에러가 발생하게 됨 

//SampleController

@GetMapping("/ex05")
public void ex05() {
	log.info("ex05........................");
}

에러 원인

=> sample/ex05.jsp 가 존재하지 않기 때문

==> servlet-context.xml의 설정과 같이 맞물려 URL 경로를 View로 처리하기 때문에 생기는 결과

//servlet-context.xml 

<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
	<beans:property name="prefix" value="/WEB-INF/views/" />
	<beans:property name="suffix" value=".jsp" />
</beans:bean>

String 타입

메서드의 리턴 타입을 String 타입으로 지정하는 경우 ==> 상황에 따라 다른 화면을 보여줄 필요가 있을 경우 (if ~ else 와 같은 경우)

일반적으로 String 타입은 현재 프로젝트의 경우 JSP 파일의 이름을 의미함

프로젝트 생성 시 기본으로 만든 HomeController 코드를 보면, String을 반환 타입으로 사용함

==> home() 메서드는 'home'이라는 문자열을 리턴했기 때문에 경로가 '/WEB-INF/views/home.jsp' 경로가 됨

@RequestMapping(value = "/", method = RequestMethod.GET)
public String home(Locale locale, Model model) {
	logger.info("Welcome home! The client locale is {}.", locale);
	
	Date date = new Date();
	DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
	
	String formattedDate = dateFormat.format(date);
	
	model.addAttribute("serverTime", formattedDate );
	
	return "home";
}

 

[String 타입에 붙이는 키워드]

▲ redirect : 리다이렉트 방식으로 처리하는 경우

▲ forward : 포워드 방식으로 처리하는 경우


객체 타입

메서드 리턴 타입이 객체 타입인 경우 : VO(Value Object)나 DTO(Data Transfer Object) 타입 등 복합적 데이터가 들어간 객체 타입으로 지정할 수 있음 ==> JSON 데이터를 만들어내는 용도로 사용함 : 프론트엔드 부분 할 때 많이 사용할 것! 

VO : get 메소드만 있는 것 (Value Object) ==>  값만 가져다 씀

DTO : set, get 다 있는 것 (Data Transfer Object) ==> 데이터를 전송하기 위함

이 둘을 합쳐서 자바빈!

pom.xml에 추가

<!-- 브라우저에 자동으로 JSON 타입으로 객체 변환해서 전달함-->
<dependency>
	<groupId>com.fasterxml.jackson.core</groupId>
	<artifactId>jackson-databind</artifactId>
	<version>2.13.3</version>
</dependency>

pom.xml에 저 부분을 넣어주면, 알아서 'jackson-databind'.jar를 다운받음 ==> JSON 타입으로 객체 변환할 수 있게 됨

@GetMapping("/ex06")
public @ResponseBody SampleDTO ex06() {
	log.info("ex06........................");
	SampleDTO dto = new SampleDTO();
	dto.setAge(28);
	dto.setName("AAA");
	
	return dto;
}

 

@RequestBody 

이 어노테이션이 붙은 파라미터에는 http요청의 본문(body)이 그대로 전달된다.

일반적인 GET/POST의 요청 파라미터라면 @RequestBody를 사용할 일이 없을 것이다.

반면에 xml이나 json기반의 메시지를 사용하는 요청의 경우에 이 방법이 매우 유용하다.

HTTP 요청의 바디내용을 통째로 자바객체로 변환해서 매핑된 메소드 파라미터로 전달해준다. 

@ResponseBody 

자바객체를 HTTP요청의 바디내용으로 매핑하여 클라이언트로 전송한다.

@ResponseBody 가 붙은 파라미터가 있으면 HTTP요청의 미디어타입과 파라미터의 타입을 먼저 확인한다.

* dispatcher-servlet.xml 의 <annotation-drvien> 태그 내에 선언하는 <message-converter> 에서 확인.

출처: https://cheershennah.tistory.com/179 [Today I Learned. @cheers_hena 치얼스헤나:티스토리]

 

결과


ResponseEntity 타입

스프링MVC에서는 HttpServletRequest, HttpServletResponse를 직접 핸들링하지 않아도 HTTP 프로토콜의 헤더를 다룰 수 있게 되어 있음

==> ResponseEntity를 통해 원하는 헤더 정보나 데이터를 전달할 수 있음

ResponseEntity는 HttpHeaders 객체를 같이 전달할 수 있고, 이를 통해 원하는 HTTP 헤더 메시지를 가공하는 것이 가능함

ex07()의 경우 브라우저에 JSON 타입이라는 헤더 메시지와 200 OK 라는 상태 코드를 전송함

@GetMapping("/ex07")
public ResponseEntity<String> ex07() {
	log.info("/ex07..........");
    
	// {"name": "홍길동"}
	String msg = "{\"name\": \"홍길동\"}";
    
	HttpHeaders header = new HttpHeaders();
	header.add("Content-Type", "application/json;charset=UTF-8");
    
	return new ResponseEntity<>(msg, header, HttpStatus.OK);
}

파일 업로드 처리

파일 업로드를 하기 위해선, 전달되는 파일 데이터를 분석해야 한다. Servlet 3.0(Tomcat 7.0) 이후 기본적으로 업로드되는 파일을 처리할 수 있는 기능이 추가 되어 있다. 추가적인 라이브러리가 필요하지 않다.

Spring Legacy Project로 생성되는 경우, Servlet 2.5를 기준으로 생성되기 때문에 3.0 이후에 지원되는 설정을 사용하기 어려움

➡️ 예제는 일반적으로 많이 사용하는 commons-fileupload를 이용함

//pom.xml에 추가 : commons-fileupload 라이브러리

<!-- 파일 업로드 처리 -->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.1</version>
</dependency>

➡️ 라이브러리를 추가한 후, 파일이 임시로 업로드될 폴더를 작성 : /Users/user/Documents/upload/tmp

 

➡️ servlet-context.xml 설정

스프링 MVC의 특정한 객체를 설정해서 파일을 처리함. 다른 객체와 달리, 파일 업로드의 경우 반드시 id 속성의 값을 'multipartResolver'로 정확하게 지정해야함

//servlet-context.xml
<beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
	<beans:property name="defaultEncoding" value="utf-8"></beans:property>
	<!-- 1024 * 1024 * 10 bytes 10MB -->
	<beans:property name="maxUploadSize" value="104857560"></beans:property>
	<!-- 1024 * 1024 * 2 bytes 2MB -->
	<beans:property name="maxUploadSizePerFile" value="2097152"></beans:property>
	<beans:property name="uploadTempDir" value="file:/Users/user/Documents/upload/tmp"></beans:property>
	<beans:property name="maxInMemorySize" value="10485756"></beans:property>
</beans:bean>

- maxUploadSize : 한 번의 Request로 전달될 수 있는 최대의 크기를 의미

- maxUploadSizePerFile: 하나의 파일 최대 크기

- maxInMemorySize : 메모리상에서 유지하는 최대의 크기를 의미함 : 이 크기 이상의 데이터는 uploadTempDir에 임시파일로 보관됨

- uploadTempDir : 절대 경로를 이용하기 위해 URI형태로 제공해야 함: "file:/"로 시작하도록함

- defaultEncoding : 업로드 하는 파일의 이름이 한글일 경우 깨지는 문제를 해결함

 

➡️ SampleController에서 get 방식으로 파일을 업로드할 화면 처리하기

@GetMapping("/exUpload")
public void exUpload() {
	log.info("/exUpload..........");
}

 

➡️ /WEB-INF/views/sample/exUpload.jsp 파일 작성

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

<form action="/sample/exUploadPost" method="post" enctype="multipart/form-data">

<div>
	<input type='file' name='files'>
</div>
<div>
	<input type='file' name='files'>
</div>
<div>
	<input type='file' name='files'>
</div>
<div>
	<input type='file' name='files'>
</div>
<div>
	<input type='file' name='files'>
</div>
<div>
	<input type='submit'>
</div>

</form>
</body>
</html>

 

➡️ SampleController에 추가하기 : exUpload.jsp의 action속성값이 '/sample/exUploadPost'로 작성되었으므로, 이에 맞는 메서드를 작성해야 함

@PostMapping("/exUploadPost")
public void exUploadPost(ArrayList<MultipartFile> files) {
	files.forEach(file -> {
		log.info("--------------------------");
		log.info("name" + file.getOriginalFilename());
		log.info("size" + file.getSize());
	});
}

스프링 MVC는 전달되는 파라미터가 동일한 이름으로 여러 개 존재하면 배열로 처리가 가능 => 파라미터를 MultipartFile의 배열 타입으로 작성함 

 

결과


Controller의 Exception 처리

스프링 MVC에서 Controller를 작성할 때, 예외 상황의 처리를 다음과 같은 방식으로 처리할 수 있다.

- @ExceptionHandler 와 @ControllerAdvice를 이용한 처리

- @ResponseEntity를 이용하는 예외 메시지 구성

원래는 컨트롤러마다 적었어야했는데, 이제는 한번만 만들어 놓으면 굳이 다 만들 필요가 없음


@ControllerAdvice

AOP를 이용하는 방식 (Aspect - Oriented- Programming) : 핵심적인 로직은 아니지만, 프로그램에서 필요한 '공통적인 관심사는 분리'하자는 개념

Controller를 작성할 때 메서드의 모든 예외사항을 전부 핸들링하면 너무 많은 양의 코드를 작성해야 하지만, AOP 방식을 이용하면 공통적인 예외사항에 대해서 별도로 @ControllerAdvice를 이용해서 분리하는 방식

 

//CommonExceptionAdvice 클래스

package org.zerock.exception;

import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import lombok.extern.log4j.Log4j;

@ControllerAdvice
@Log4j
public class CommonExceptionAdvice {
	
	@ExceptionHandler(Exception.class)
	public String except(Exception ex, Model model) {
		log.error("Exception............." + ex.getMessage());
		model.addAttribute("exception", ex); //발생된 예외를 영역에 저장
		log.error(model); //저장된 모델도 출력
		return "error_page"; //JSP 파일의 경로
	}

}

@ControllerAdvice

해당 객체가 스프링의 컨트롤러에서 발생하는 예외를 처리하는 존재임을 명시하는 용도로 사용함

 

@ExceptionHandler

해당 메서드가 ( ) 안의 예외 타입을 처리한다는 것을 의미함. 이 어노테이션 속성으로 Exception 클래스 타입을 지정할 수 있음.

위와 같은 경우 Exception.class를 지정하였기에, 모든 예외에 대한 처리가 except()만을 이용해서 처리할 수 있음

특정한 타입의 예외를 다루고 싶다면  Exception.class 대신 구체적인 예외 클래스를 지정하면됨

 

➡️ servlet-context.xml에서 org.zerock.exception 패키지 내용을 조사하도록 시켜야함

<context:component-scan base-package="org.zerock.exception" /> 추가시키기

 

➡️/WEB-INF/views 폴더 내에 JSP 파일인 error_page.jsp 작성하기

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page session="false" import="java.util.*" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

<h4><c:out value="${exception.getMessage()}"></c:out></h4>

<ul>
	<c:forEach items="${exception.getStackTrace()}" var="stack">
		<li><c:out value="${stack}"></c:out></li>
	</c:forEach>
</ul>
</body>
</html>

404 에러 페이지

WAS 구동 중 가장 흔한 에러 코드는 '404'와 '500' 이다.

500 메시지 : Internal Server Error 이므로 @ExceptionHandler를 이용해서 처리할 수 있음

404 메시지 : 잘못된 URL을 호출할 때 보이므로 500과는 다르게 처리하는 것이 좋음

➡️ 스프링 MVC의 모든 요청은 DispatcherServlet을 이용해서 처리하기 때문에 404 에러도 같이 처리할 수 있도록 web.xml을 수정함

//web.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/spring/appServlet/servlet-context.xml</param-value>
	</init-param>
	<init-param>
		<param-name>throwExceptionIfNoHandlerFound</param-name>
		<param-value>true</param-value>
	</init-param>
	<load-on-startup>1</load-on-startup>	
</servlet>

 

➡️ CommonExceptionAdvice.java 클래스에 메서드 추가하기

@ExceptionHandler(NoHandlerFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public String handle404(NoHandlerFoundException ex) {
	return "custom404";
}

 

@ResponseStatus

응답 코드를 전달함 

NOT_FOUND는 404를 전달함 

 

➡️custom404.jsp 페이지 작성

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>해당 URL은 존재하지 않습니다.</h1>
</body>
</html>
반응형
댓글