01.스프링MVC

  • 현시점 국내 IT서비스 실무에서 가장 많이 활용되는 웹 개발 기술

  • Spring MVC 아키텍처

mvc

  • 핵심은 M(Model), V(View), C(Controller)
    • Model: 로직 안에서 이동하고 있는 데이터
    • View: 디스플레이 데이터 또는 프리젠테이션
    • Controller: 비즈니스 로직을 처리하고 모델과 뷰를 응답으로 준다.


02. HTTP 요청/응답

스프링 기본 HTTP요청 매핑

  • Controller/RestController의 차이
    • Controller: 응답값이 기본적으로 HTML을 주도록 되어 있음
    • RestController: 응답값으로 Rest API 요청에 대한 응답(주로 JSON)을 주도록 되어 있음
  • 매핑 어노테이션
    • @RequestMapping: GET, POST 등 요청 방식을 직접 지정
1
2
3
4
5
6
7
8
9
10
@Slf4j
@RestController
public class SampleController {
    
    @RequestMapping(value = "/order/1", method = RequestMethod.GET)
    public String getOrder() {
        log.info("Get some order");
        return "orderId:1, orderAmount:1000";
    }
}
  • 축약형 매핑 어노테이션
    • @GetMapping : 데이터를 가져옴
    • @PostMapping : 데이터를 전송함
    • @PutMapping : 전체 수정
    • @PatchMapping : 일부 수정
    • @DeleteMapping : 삭제


스프링 HTTP 요청 파라미터 전송

  • Get,Delete
    • PathVariable : 요새는 id를 path에 넣는 것을 선호
      • @PathVariable(”id”) String identity;
      • 하지만 이름이 같으면 생략 가능
      • 여러개를 넣을 수 있음
    • query-params : 추가적인 정보들 입력
      • 게시판의 검색 필터 페이징에서 많이 사용
      • @RequestParam 사용법
        • PathVariable처럼 이름을 동일하게 하면 자동으로 받아줌
        • required, defaultValue 옵션 설명
        • 사실 없어도 자동으로 나옴
        • Map, MultiValueMap으로 요청 받는 방법
1
2
3
4
5
6
7
8
9
10
11
12
@GetMapping("/order/{orderId}")
public String getOrder(@PathVariable("orderId") String orderId) {
    log.info("Get some order information : " + orderId);
    return "orderId: " + orderId + ", orderAmount:100";
}

@GetMapping("/order")
public String searchOrder(@RequestParam("orderId") String orderId,
                          @RequestParam("orderAmount") Integer orderAmount) {
    log.info("Search order information : " + orderId + ", orderAmount : " + orderAmount);
    return "orderId: " + orderId + ", orderAmount: " + orderAmount;
}
  • Post, Put, Patch
    • @RequestBody : http body 정보를 편리하게 받을 수 있음
      • 주로 사용하는 메시지 포맷은 JSON(현재 사실상의 표준)
    • @RequestHeader : http header 정보를 편리하게 받을 수 있음
1
2
3
4
5
6
7
8
9
10
11
12
13
@PostMapping("/order")
    public String getOrder(@RequestBody CreateOrderRequest createOrderRequest,
                           @RequestHeader String userAccountId) {
        log.info("Create order : " + createOrderRequest + ", userAccountId : " + userAccountId);
        return "orderId:" + createOrderRequest.getOrderId()
                + ", orderAmount:" + createOrderRequest.getOrderAmount();
    }

    @Data
    public static class CreateOrderRequest {
        private String orderId;
        private Integer orderAmount;
    }


03. 필터, 인터셉터

img

  • 필터
    • 스프링 외부의 서블릿에서 제공하는 공통처리 기능
    • 스프링 내로 요청이 들어오기 전과 스프링의 요청이 나갈 때 처리 가능
    • 조금 더 low level 처리가 가능
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
27
28
29
30
31
32
@Slf4j
@Component
public class LogFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {

        log.info("Hello LogFilter : " + Thread.currentThread());
        // 외부 -> filter (-> 처리 ->) filter -> 외부
        chain.doFilter(request, response);
        log.info("Bye LogFilter : " + Thread.currentThread());
    }
}

// 필터를 거는 순서, url패턴등을 조절
// @Component를 제거하면 단순 클래스기때문에 인식하지 못함
// 그래서 Component 제거 후 다른 클래스로 필터 발생을 하게 설정
@Configuration
public class WebConfig {

    @Bean
    public FilterRegistrationBean<Filter> loggingFilter() {
        FilterRegistrationBean<Filter> filterFilterRegistrationBean 
            = new FilterRegistrationBean<>();
        
        filterFilterRegistrationBean.setFilter(new LogFilter());
        filterFilterRegistrationBean.setOrder(1);
        filterFilterRegistrationBean.addUrlPatterns("/order/*");

        return filterFilterRegistrationBean;
    }
}
  • 인터셉터
    • 스프링에서 제공하는 공통처리 기능
    • 실제 매핑된 Handler 정보 확인 가능(어떤 것이 실제 내 요청을 처리하는지도 확인 가 능)
    • 조금 더 상세한 조건식과, 세부적인 스펙(pre, post, after)를 통해 구체적인 시점에 구 체적인 동작 가능
    • AOP와 비교한다면 AOP는 인터셉터보다 더 구체적인 조건(어노테이션, 파라미터, 주소 등)과 동작 위치(afterThrowing 등)을 갖음
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@Slf4j
public class LogInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {

        log.info("preHandle LogInterceptor : " + Thread.currentThread());
        log.info("preHandle handler : " + handler);

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request,
                           HttpServletResponse response,
                           Object handler, ModelAndView modelAndView) throws Exception {
        log.info("postHandle LogInterceptor : " + Thread.currentThread());
    }

    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler, Exception ex) throws Exception {
        log.info("afterHandle LogInterceptor : " + Thread.currentThread());

        if (ex != null) {
            log.error("afterCompletion exception : " + ex.getMessage());
        }
    }
}

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public FilterRegistrationBean<Filter> loggingFilter() {
        FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
        filterFilterRegistrationBean.setFilter(new LogFilter());
        filterFilterRegistrationBean.setOrder(1);
        filterFilterRegistrationBean.addUrlPatterns("/order/*");

        return filterFilterRegistrationBean;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor())
                .order(1)
                .addPathPatterns("/**")
                .excludePathPatterns("/css/*", "/images/*");
    }
}


04. 예외처리

  • 예외: 프로그램이 예상치 못한 상황을 만났을 때 오류를 발생시키는 것

@ExceptionHandler

  • 컨트롤러 기반 예외처리
  • HTTP Status code를 변경하는 방법
    • @ResponseStatus
    • ResponseEntity 활용
  • 예외처리 우선순위
    1. 해당 Exception이 정확히 지정된 Handler
    2. 해당 Exception의 부모 예외 Handler
    3. 이도 저도 아니면 그냥 Exception(모든 예외의 부모)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Slf4j
@RestController
public class SampleController {
    // business methods
    @ResponseStatus(value = HttpStatus.FORBIDDEN)
    @ExceptionHandler(IllegalAccessException.class)
    public ErrorResponse handleIllegalAccessException(IllegalAccessException e) {
        log.error("Illegal Exception : ", e);
        return new ErrorResponse("ACCESS_DENIED", "Illegal Exception occurred.");
    }
}

@AllArgsConstructor
@Data
public class ErrorResponse {
    private String errorCode;
    private String message;
}

@RestControllerAdvice

  • 어플리케이션의 전역적 예외 처리
  • @ControllerAdvice랑 차이는?
    • Controller vs RestController 차이와 동일
    • ControllerAdvice: 기본적으로 view를 응답하는 방식
    • RestControllerAdvice: REST API용으로 객체를 응답하는 방식(주로 JSON)
  • 스프링 백엔드 개발에서 현재 가장 많이 활용되는 기술(일관적인 예외 및 응답처리)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(IllegalAccessException.class)
    public ResponseEntity<ErrorResponse> handleIllegalAccessException(
        IllegalAccessException e) {

        log.error("IllegalAccessException ins occurred.", e);
        return ResponseEntity
                .status(HttpStatus.FORBIDDEN)
                .body(new ErrorResponse(ErrorCode.TOO_BIG_ID_ERROR, "IllegalAccessException is occurred."));
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleException(Exception e) {
        log.error("Exception ins occurred.", e);
        return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(new ErrorResponse(ErrorCode.INTERNAL_SERVER_ERROR, "Exception is occurred."));
    }
}

카테고리:

업데이트:

댓글남기기