반응형

스프링 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(10String.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
반응형

+ Recent posts