Spring 09 : MVC ( Controller Exception, Controller Advice, Json 요청/응답 )

LeeWonjin·2022년 9월 1일

2022 백엔드스터디

목록 보기
19/20

교재
책 : 초보 웹 개발자를 위한 스프링5 프로그래밍 입문 챕터 14, 15, 16

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>in.wonj</groupId>
  <artifactId>sp5-wonjin</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>
  
  <dependencies>
	<dependency>
  		<groupId>javax.servlet</groupId>
  		<artifactId>javax.servlet-api</artifactId>
  		<version>4.0.1</version>
  	</dependency>
  	<dependency>
  		<groupId>javax.servlet.jsp</groupId>
  		<artifactId>javax.servlet.jsp-api</artifactId>
  		<version>2.3.3</version>
  		<scope>provided</scope>
  	</dependency>
  	<dependency>
  		<groupId>javax.servlet</groupId>
  		<artifactId>jstl</artifactId>
  		<version>1.2</version>
  	</dependency>
  
  	<dependency>
  		<groupId>org.springframework</groupId>
  		<artifactId>spring-jdbc</artifactId>
  		<version>5.3.22</version>
  	</dependency>
  	<dependency>
  		<groupId>org.springframework</groupId>
  		<artifactId>spring-webmvc</artifactId>
  		<version>5.3.22</version>
  	</dependency>
  	
  	<dependency>
  		<groupId>org.apache.tomcat</groupId>
  		<artifactId>tomcat-jdbc</artifactId>
  		<version>10.0.23</version>
  	</dependency>
 	<dependency>
  		<groupId>mysql</groupId>
  		<artifactId>mysql-connector-java</artifactId>
  		<version>8.0.30</version>
  	</dependency>
  	
	<dependency>
			<groupId>javax.validation</groupId>
			<artifactId>validation-api</artifactId>
			<version>1.1.0.Final</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-validator</artifactId>
			<version>5.4.2.Final</version>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>2.9.4</version>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.datatype</groupId>
			<artifactId>jackson-datatype-jsr310</artifactId>
			<version>2.9.4</version>
		</dependency>
  </dependencies>
  
  <build>
  	<plugins>
	  	<plugin>
	  		<groupId>org.apache.maven.plugins</groupId>
	  		<artifactId>maven-compiler-plugin</artifactId>
	  		<version>3.10.1</version>
	  		<configuration>
	  			<release>18</release>
	  			<encoding>utf-8</encoding>
	  		</configuration>
	  	</plugin>
  		<plugin>
  			<groupId>org.apache.maven.plugins</groupId>
  			<artifactId>maven-war-plugin</artifactId>
  			<version>3.3.2</version>
  		</plugin>
  	</plugins>
  </build>
  
</project>

Exception Handler

컨트롤러에서 예외가 발생 했을 때 처리할 컨트롤러를 지정할 수 있다.
@ExceptionHandler 어노테이션을 붙인 메소드가 예외를 처리한다.

컨트롤러 내에서 처리

...
@Controller
public class SomethingController {
    @RequestMapping("/경로")
    public String someMethod() {
        throw new MyException();
        return "정상 처리됐을 때 View 이름"
    }
    
    @ExceptionHandler(MyException.class)
    public String handleMyException() {
        return "예외 처리 후 보여줄 View 이름"
    }
}

@ControllerAdvice (공통 예외처리)

// ControllerConfig.java
...
@Configuration
public class ControllerConfig {
    ...
    @Bean
    public SomethingController somethingController() {
        return new SomethingController();
    }
    
    @Bean
    public CommonHandler commonHandler() {
        return new CommonHandler();
    }
}

// SomethingController.java
...
@Controller
public class SomethingController {
    @RequestMapping("/경로")
    public String someMethod() {
        throw new MyException();
        return "정상 처리됐을 때 View 이름"
    }
}

// CommonHandler.java
...
@ControllerAdvice("aaa") // aaa패키지와 그 하위 패키지에 대해 적용
public class CommonHandler {
    @ExceptionHandler(MyException.class)
    public String handleMyException() {
        return "예외 처리 후 보여줄 View 이름"
    }
}

@RestControllerAdvice

@ControllerAdvice와 사용법 동일
@RestController에 대한 예외처리에 사용

Json 요청/응답 처리

REST Client

Boomerang - SOAP & REST Client
위 크롬 익스텐션으로 간편하게 요청을 날릴 수 있다.

@RestController

JSON요청을 받는 컨트롤러는 @RestController어노테이션을 붙여 표시한다.

...
@RestController
public class SomethingController {
   @RequestMapping
   (method)
   ...
}

요청 받기

요청매핑 메소드의 커맨드객체 인수에 @RequestBody어노테이션을 붙여 JSON요청 데이터를 받을 수 있다.

요청을 넣는 클라이언트는 application/json 타입으로 컨텐츠를 보내야 한다.

...
@RestController
public class SomethingController {
    @PostMapping("경로")
    public String test(@RequestBody CommandObject cmd, Model model) {
        model.addAttribute("data", cmd.getData());
        return "JSP View이름"
    }
}

HttpServletResponse로 응답

...
@RestController
public class SomethingController {
    @PostMapping("경로")
    public String test(HttpServletResponse res) {
        try {
            res.setHeader("MYNAME", "WONJIN");
            res.setStatus(HttpServletResponse.SC_CREATED);
        } catch (Exception e) {
            res.sendError(HttpServletResponse.SC_NOT_FOUND);
        }
    }
}

응답으로 JSON 전송 : 클래스 바로 리턴

아래 두 의존을 추가하고 클래스를 반환하면 Jackson에 의해 객체를 JSON으로 변환해 응답한다.

  • jackson-databind
  • jackson-datatype-jsr310

이 때, 클래스의 필드에 @JsonIgnore를 붙이면 응답JSON에서 제외한다.
아래 코드는 txt필드를 포함하고 chk는 제외한 JSON을 전송한다.

...
@RestController
public class SomethingController {
    @PostMapping("test")
    public TestCmd sendJson(@RequestBody TestCmd testCmd) {
        return testCmd;
    }
}

// TestCmd.java
package test;

import javax.validation.constraints.AssertTrue;
import org.hibernate.validator.constraints.NotBlank;

public class TestCmd {
	private String txt;
    
    @JsonIgnore
	private Boolean chk;
	// setter getter 생략
}

List<Object>를 리턴하면 json객체의 배열로 응답한다.

...
@RestController
public class SomethingController {
    @PostMapping("test")
    public List<Something> sendJson() {
        List<Something> list = /* initializing, add, .. */
        return list;
    }
}

응답으로 JSON 전송 : ResponseEntity

JSON으로 변환할 클래스를 ResponseEntity의 body에 담아 전송할 수도 있다.
정상 응답과 에러응답 모두 일관되게 JSON응답으로 처리할 수 있다.

에러 응답은 아래 코드처럼 하드코딩 하지 않고, 공통 응답으로 분리할 수 있다.

  • @ExceptionHandler로 에러 응답을 할 수 있다.
  • @RestControllerAdvice로 별도 클래스로 분리할 수 있다.
...
class ErrRes {
    private String msg;
    public String getMsg() { return msg; }
    public void setMsg(String msg) { this.msg = msg; }
}

@RestController
public class SomethingController {
    @PostMapping("test")
    public ResponseEntity<Object> sendJson(@RequestBody TestCmd testCmd) {
        try {
            return ResponseEntity.status(HttpStatus.OK)
                .body(testCmd);
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND)
                .body(new ErrRes("아무것도없잖아"));
        }
    }
}

body가 없는 경우 .body() 대신 .build()로 문장을 끝내거나 특정 상태 메소드를 사용한다.
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/ResponseEntity.html

...
@RestController
public class SomethingController {
    @PostMapping("test")
    public ResponseEntity<Object> sendJson(@RequestBody TestCmd testCmd) {
        try {
            return ResponseEntity.status(HttpStatus.OK).build();
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
        }
    }
    
    @PostMapping("test_2")
    public ResponseEntity<Object> sendJson(@RequestBody TestCmd testCmd) {
        try {
            return ResponseEntity.ok() // 200
        } catch (Exception e) {
            return ResponseEntity.noContent(); // 204
            return ResponseEntity.badRequest(); // 400
            return ResponseENTITY.notFound(); // 404
        }
    }
}
profile
노는게 제일 좋습니다.

0개의 댓글