스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 대시보드 - 인프런 | 강의 (inflearn.com)
스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 인프런 | 강의
웹 애플리케이션 개발에 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. MVC 2편에서는 MVC 1편의 핵심 원리와 구조 위에 실무 웹 개발에 필요한 모든 활용 기술들을 학습할 수 있
www.inflearn.com
API 예외 처리 복잡한 이유
1. 예외에 따라서 각각 다른 데이터 추력
2. 같은 예외라도 컨트롤러에 따라서 응답 다를 수 있음
@ExceptionHandler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResult illegalExHandler(IllegalArgumentException e) {
log.error("[exceptionHandler] ex", e);
return new ErrorResult("BAD", e.getMessage());
}
// 예외 타입 생략 -> 파라미터로 받은 예외를 처리
@ExceptionHandler
public ResponseEntity<ErrorResult> userExHandler(UserException e){
log.error("[exceptionHandler] ex", e);
ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage());
return new ResponseEntity(errorResult, HttpStatus.BAD_REQUEST);
}
// 위에서 걸러지지 않은 예외는 여기서 처리
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler
public ErrorResult exHandler(Exception e) {
log.error("[exceptionHandler] ex", e);
return new ErrorResult("EX", "내부 오류");
}
|
cs |
@ResponseStatus 안하면 정상 처리로 간주 (Status 200 OK)
@ControllerAdvice
예외 처리를 모아서 한 군데서 처리
기능과 예외처리를 분리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
@Slf4j
@RestControllerAdvice
public class ExControllerAdvice {
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResult illegalExHandler(IllegalArgumentException e) {
log.error("[exceptionHandler] ex", e);
return new ErrorResult("BAD", e.getMessage());
}
@ExceptionHandler
public ResponseEntity<ErrorResult> userExHandler(UserException e){
log.error("[exceptionHandler] ex", e);
ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage());
return new ResponseEntity(errorResult, HttpStatus.BAD_REQUEST);
}
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler
public ErrorResult exHandler(Exception e) {
log.error("[exceptionHandler] ex", e);
return new ErrorResult("EX", "내부 오류");
}
}
|
cs |
대상 컨트롤러 지정 방법
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// Target all Controllers annotated with @RestController
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {
}
// Target all Controllers within specific packages
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {
}
// Target all Controllers assignable to specific classes
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class ExampleAdvice3 {
}
|
cs |
스프링 타입 컨버터
IntegerToStringConverter
1
2
3
4
5
6
7
|
public class IntegerToStringConverter implements Converter<Integer, String> {
@Override
public String convert(Integer source) {
log.info("convert source={}", source);
return String.valueOf(source);
}
}
|
cs |
StringToIntegerConverter
1
2
3
4
5
6
7
|
public class StringToIntegerConverter implements Converter<String, Integer> {
@Override
public Integer convert(String source) {
log.info("convert source={}", source);
return Integer.valueOf(source);
}
}
|
cs |
StringToIpPortConverter
1
2
3
4
5
6
7
8
9
10
11
12
|
public class StringToIpPortConverter implements Converter<String, IpPort> {
@Override
public IpPort convert(String source) {
log.info("convert source={}", source);
// "127.0.0.1:8080"
String[] split = source.split(":");
String ip = split[0];
int port = Integer.parseInt(split[1]);
return new IpPort(ip, port);
}
}
|
cs |
IpPortToStringConverter
1
2
3
4
5
6
7
|
public class IpPortToStringConverter implements Converter<IpPort, String> {
@Override
public String convert(IpPort source) {
log.info("convert source={}", source);
return source.getIp() + ":" + String.valueOf(source.getPort());
}
}
|
cs |
타입 컨버터를 하나하나 직접 사용하면, 개발자가 직접 컨버팅 하는 것과 큰 차이가 없음
컨버전 서비스 - ConversionService
개별 컨버터를 모아두고 그것들을 묶어서 편리하게 사용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
@Test
void conversionService() {
// 등록
DefaultConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new StringToIntegerConverter());
conversionService.addConverter(new IntegerToStringConverter());
conversionService.addConverter(new StringToIpPortConverter());
conversionService.addConverter(new IpPortToStringConverter());
// 사용
assertThat(conversionService.convert("10", Integer.class)).isEqualTo(10);
assertThat(conversionService.convert(10, String.class)).isEqualTo("10");
IpPort ipPort = conversionService.convert("127.0.0.1:8080", IpPort.class);
assertThat(ipPort).isEqualTo(new IpPort("127.0.0.1", 8080));
String ipPortString = conversionService.convert(new IpPort("127.0.0.1", 8080), String.class);
assertThat(ipPortString).isEqualTo("127.0.0.1:8080");
}
|
cs |
인터페이스 분리 원칙 - ISP(Interface Segregation Principal)
인터페이스 분리 원칙은 클라이언트가 자신이 이용하지 않는 메서드에 의존하지 않아야 한다.
DefaultConversionService 는 다음 두 인터페이스를 구현했다.
ConversionService : 컨버터 사용에 초점
ConverterRegistry : 컨버터 등록에 초점
이렇게 인터페이스를 분리하면 컨버터를 사용하는 클라이언트와 컨버터를 등록하고 관리하는 클라이언트의
관심사를 명확하게 분리할 수 있다. 특히 컨버터를 사용하는 클라이언트는 ConversionService 만
의존하면 되므로, 컨버터를 어떻게 등록하고 관리하는지는 전혀 몰라도 된다. 결과적으로 컨버터를
사용하는 클라이언트는 꼭 필요한 메서드만 알게된다. 이렇게 인터페이스를 분리하는 것을 ISP 라 한다.
ISP 참고: https://ko.wikipedia.org/wiki/
%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4_%EB%B6%84%EB%
A6%AC_%EC%9B%90%EC%B9%99
스프링에 Converter 적용하기
1
2
3
4
5
6
7
8
9
10
|
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToIpPortConverter());
registry.addConverter(new IpPortToStringConverter());
registry.addConverter(new StringToIntegerConverter());
registry.addConverter(new IntegerToStringConverter());
}
}
|
cs |
스프링 기본 컨버터 제공
사용자 지정 컨버터의 우선순위가 더 높음
localhost:8080/ip-port?ipPort=127.0.0.1:8080
1
2
3
4
5
6
|
@GetMapping("/ip-port")
public String ipPort(@RequestParam IpPort ipPort) {
System.out.println("ipPort.getIp() = " + ipPort.getIp());
System.out.println("ipPort.getPort() = " + ipPort.getPort());
return "ok";
}
|
cs |
뷰 템플릿에 컨버터 적용하기
스프링: 문자를 객체로
뷰 템플릿: 객체를 문자로
${...}: 컨버터 적용 안함
${{...}}: 컨버터 적용
1
2
3
4
5
6
|
@GetMapping("/converter-view")
public String converterView(Model model) {
model.addAttribute("number", 10000);
model.addAttribute("ipPort", new IpPort("127.0.0.1", 8080));
return "converter-view";
}
|
cs |
결과
• ${number}: 10000
• ${{number}}: 10000
• ${ipPort}: hello.typeconverter.type.IpPort@59cb0946
• ${{ipPort}}: 127.0.0.1:8080
-> IpPort 객체를 String으로 변환한 뒤 출력
th:object="${form}"에서
th:field="*{ipPort}"
-> 컨버터 자동 적용
th:value="*{ipPort}"
-> 컨버터 적용 안됨
예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
@GetMapping("/converter/edit")
public String converterForm(Model model){
IpPort ipPort = new IpPort("127.0.0.1", 8080);
Form form = new Form(ipPort);
model.addAttribute("form", form);
return "converter-form";
}
@PostMapping("/converter/edit")
public String converterEdit(@ModelAttribute Form form, Model model) {
IpPort ipPort = form.getIpPort();
model.addAttribute("ipPort", ipPort);
return "converter-view";
}
@Data
static class Form{
private IpPort ipPort;
public Form(IpPort ipPort) {
this.ipPort = ipPort;
}
}
|
cs |
'오늘 배운 것' 카테고리의 다른 글
[스프링 핵심 원리 기본] 객체지향 설계와 스프링 (0) | 2021.07.28 |
---|---|
[스프링 MVC 2] 스프링 타입 컨버터(2), 파일 업로드 (0) | 2021.07.26 |
[스프링 MVC 2] API 예외 처리 (0) | 2021.07.24 |
[스프링 MVC 2] 예외 처리와 오류 페이지 (0) | 2021.07.23 |
[스프링 MVC 2] 로그인 처리2 - 필터, 인터셉터 (0) | 2021.07.22 |