Skip to content

Latest commit

 

History

History
341 lines (243 loc) · 12.6 KB

2021-01-27.md

File metadata and controls

341 lines (243 loc) · 12.6 KB

2020-01-27

느낀점

  • 편한 것이 능사는 아니다.
    • Spring Framework의 편리함을 그대로 사용하기 보다는 알고서 사용하는 것이 필요할 것 같다.

배운 것

스프링 필드 인젝션이 좋지 않은 이유

DI

  • 강한 결합
    • 객체 내부에서 다른 객체를 생성하는 것
  • 느슨한 결합
    • 외부에서 생성된 객체를 인터페이스를 통해서 넘겨받는 것
    • 런타임시에 의존관계가 결정되기 때문에 유연한 구조를 가짐

Spring의 의존성 주입 종류

수정자를 통한 주입

  • 수정자를 통한 의존관계가 주입되기 전에 해당 객체를 생성하고 사용할 수 있기 때문에 문제가 발생한다.
  • 주입이 필요한 객체가 주입이 되지 않아도 얼마든지 객체를 생성할 수 있다는 것이 문제

생성자를 통한 주입

  • 객체를 생성할 때 의존관계 주입을 강제할 수 있고 해당 객체의 필드를 final 로 선언하여 불변 객체로 만들수 있다.

필드 주입 (스프링에서 제공)

  • 수정자 주입의 단점을 동일하게 가지고 있다.
  • 스프링 컨테이너에서 주입을 담당한다.

필드 인젝션 보다 생성자 주입을 권장하는 이유

객체 필드의 불변성

  • 객체 생성과 동시에 의존성 주입을 강제할 수 있다.
  • 런타임에 객체가 변하는 것을 방지할 수 있다.

순환참조 문제

  • 순환참조가 발생하는 경우 어플리케이션이 구동되지 않아 오류를 사전에 파악할 수 있다.

  • (필드 인젝션의 경우) 만약 두 객체가 양방향으로 의존성을 갖게 되는 되는 경우 순환참조가 일어나 끝없은 호출이 일어나게 되면서

    StackOverflow
    

    가 발생됨

    • 예) 서로의 메소드를 순환 호출하고 있는 경우
  • 필드 주입이나, 수정자 주입은 객체 생성시점에는 순환참조가 일어나는지 아닌지 발견할 수 있는 방법이 없다.

테스트의 용이성

Spring MVC Exception Handling

출처: https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc

SprinBoot

  • Sprinb MVC 는 default error 페이지를 따로 제공해주고 있지는 않다.

  • 그래서 가장 일반적인 방법으로는 SimpleMappingExceptionResolver 를 사용해 기본 오류 페이지를 설정해왔습니다.

  • 그러나 Spring Boot fall-back 오류 처리 페이지를 제공한다.

  • 애플리케이션 시작시 Spring Boot는

    /error
    

    에 대한 매핑을 찾는다.

    • 매핑은 설정한 ViewResolver 에 따라서 규칙이 결정됨
  • 만약

    /error
    

    매핑을 찾지 못한 경우 Spring Boot는 자체 폴백 오류 페이지를 정의한다.

    • “Whitelabel Error Page” 라는 메시지(와 오류 정보)가 있는 오류 페이지
    • REST 요청을 받는 경우도 동일한 정보를 JSON으로 담아 리턴한다.

HTTP Status Code의 사용

  • 일반적으로 웹 요청을 처리할 때 처리되지 않은 예외가 발생하게 되면 서버는 HTTP 500 응답을 반환한다.
  • 그러나 Custom 예외인 경우 @RespnoseStatus 애노테이션을 통해서 Status Code를 지정해줄 수 있다.
@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="No such Order")  // 404
 public class OrderNotFoundException extends RuntimeException {
     // ...
 }
  • 위에서 지정된 예외가 발생하는 경우
@RequestMapping(value="/orders/{id}", method=GET)
 public String showOrder(@PathVariable("id") long id, Model model) {
     Order order = orderRepository.findOrderById(id);

     if (order == null) throw new OrderNotFoundException(id);

     model.addAttribute(order);
     return "orderDetail";
 }

Controller 기반 예외처리

@ExceptionHandler 활용

  • @ExceptionHandler 애노테이션을 컨트롤러 메소드(@RequestMapping)에 달아 주어 Exception 을 지정해줄 수 있다.
  • 사용 방법은 크게 세가지가 있다.
@Controller
public class ExceptionHandlingController {
  
	//1. 
  // Convert a predefined exception to an HTTP Status code
  @ResponseStatus(value=HttpStatus.CONFLICT,
                  reason="Data integrity violation")  // 409
  @ExceptionHandler(DataIntegrityViolationException.class)
  public void conflict() {
    // Nothing to do
  }
  
	//2. 
  // Specify name of a specific view that will be used to display the error:
  @ExceptionHandler({SQLException.class,DataAccessException.class})
  public String databaseError() {
    // Nothing to do.  Returns the logical view name of an error page, passed
    // to the view-resolver(s) in usual way.
    // Note that the exception is NOT available to this view (it is not added
    // to the model) but see "Extending ExceptionHandlerExceptionResolver"
    // below.
    return "databaseError";
  }

	//3. 일반적으로 사용되는 방법
  // Total control - setup a model and return the view name yourself. Or
  // consider subclassing ExceptionHandlerExceptionResolver (see below).
  @ExceptionHandler(Exception.class)
  public ModelAndView handleError(HttpServletRequest req, Exception ex) {
    logger.error("Request: " + req.getRequestURL() + " raised " + ex);

    ModelAndView mav = new ModelAndView();
    mav.addObject("exception", ex);
    mav.addObject("url", req.getRequestURL());
    mav.setViewName("error");
    return mav;
  }
}

Exception과 View

  • 다른 방법으로 View 자체에 Exception을 명시해주는 방법이 있다.
  • 브라우저의 경우 HTML에 stack-trace 정보가 표출 되기 때문에 권장하지 않음

Global Execption Handling

@ControllerAdvice 클래스를 사용

  • @ControllerAdvice 를 사용하면 Exception에 대해서 동일한 처리를 할 수 있지만 개별 컨트롤러가 아닌 전체 애플리케이션에 적용된다.

    • 어노테이션 기반의 인터셉터라고 볼 수 있다.
  • @ControllerAdvice 어노테이션이 달린 클래스는 controller-advice가 되며 세가지 유형의 메소드를 지원한다. (예외처리가 아닌 기능들도 제공)

    • @ExceptionHandler 어노테이션이 달린 예외처리 메소드
    • 그 밖에 것들은 예외 처리가 아니므로 생략..
  • Custom 예외 클래스에 대한 controller-advice 설정 예

      • 모든 컨트롤러에서 발생하는 throw에 대해서 일괄적으로 적용된다.
    @ControllerAdvice
    class GlobalControllerExceptionHandler {
        @ResponseStatus(HttpStatus.CONFLICT)  // 409
        @ExceptionHandler(DataIntegrityViolationException.class)
        public void handleConflict() {
            // Nothing to do
        }
    }
  • Exception 자체의 기본 핸들러를 설정해주는 예

    @ControllerAdvice
    class GlobalDefaultExceptionHandler {
      public static final String DEFAULT_ERROR_VIEW = "error";
    
      @ExceptionHandler(value = Exception.class)
      public ModelAndView
      defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
        // If the exception is annotated with @ResponseStatus rethrow it and let
        // the framework handle it - like the OrderNotFoundException example
        // at the start of this post.
        // AnnotationUtils is a Spring Framework utility class.
        if (AnnotationUtils.findAnnotation
                    (e.getClass(), ResponseStatus.class) != null)
          throw e;
    
        // Otherwise setup and send the user to a default error-view.
        ModelAndView mav = new ModelAndView();
        mav.addObject("exception", e);
        mav.addObject("url", req.getRequestURL());
        mav.setViewName(DEFAULT_ERROR_VIEW);
        return mav;
      }
    } 

Null 서바이벌 가이드

Java에서 null이 문제인 이유

  • 모호한 null의 의미
    • 초기화되지 않은, 정의되지 않음, 값이 없음, 오류 null ?
    • 모든 참조의 기본 상태 또는 값
    • 값이야 상태야 ?
    • null의 혼잡함이 혼재되어 있어서 불안하게 만든다.

Null 안정성 언어 지원

  • null 안전 연산자
    • Null 병합 연산자: 첫 인자가 null 이면 null 을 반환하고 아니면 두번째 인자를 반환
    • Null 조건 연산자: 첫 인자가 null 이면 null을 반환하고 아니면 두번째 인자의 작업을 실행하는 이항 연산자
      • lat zipCode = user?.address?.zipcode;
      • 메소드 체이닝을 가능케 해줌
  • null 안전한 타입 시스템 (Type 시스템 강화)
    • Null 가능 타입
      • null 가능을 선택적 타입 속성으로 취급
      • 기본으로 모든 타입을 null이 불가능 하도록 강제
      • 그러나 null은 모든 타입에 대입 가능한 특수한 값
    • Null 타입과 타입 결합
      • null은 Null 타입의 값
      • 기본적으로 모든 타입은 null 타입과 결합해서 Null 가능하도록 선언 가능
    • Optional 사용
      • 래핑한 컨테이너 객체(펑터)가 값이 있고 없고를 표현함
      • 주로 함수형 프로그래밍 방식이다.

null 안전한 좋은 코딩 법

  1. 기본으로 null을 쓰지 말자

    • null이 필요할 때만 null일 수 있도록
    • 공통 코딩 관례나 스타일 가이드에 포함
    • 정적분석/컴파일러 확장 기본 null 불가 표시법
      • InteliJ: @ParametersAreNonNullByDefault
  2. null 문맥을 제한된 범위 안에 가두자 = 좋은 객체지향 프로그래밍을 하자

    • 큰 문제는 제어 가능한 작은 문제로 나누어 정복하고 다시 통합한다.
    • 좋은 캡슐화
      • 높은 응집성: 한가지 책임만 갖는다.
        • 모든 필드가 객체와 생애주기가 같다.
        • 모든 메서드가 모든 필드를 대상으로 작업한다.
      • 낮은 결합
        • 디미터 법칙, '묻지말고 시켜라'
        • 인터페이스에 의존
    • 디미터 법칙
      • 객체의 과도합 결합을 낮출 수 있는 형식적인 규칙
      • 내부 구현을 외부로 유출되지 않게 막아줌
      • "도트를 오직 하나만 사용하라"
      • 예) DDD 에그리것
        • Root Aggregate 은 하위 entity, domain 들을 보호하고 있다. (관문 역할을 함)
        • 외부 Aggregate 내부의 객체를 참조하지 못한다.
  3. API에 null을 최대한 쓰지 말아라

    • null을 반환하지 말라
      • 빈 컬렉션이나 Null 객체를 활용하라
      • 반환값이 없을 수도 있음을 명시적으로 Optional로 표현하라 (다만 성능이 중요하다면 고려를 해봐야 함)
      • 오류 상황에는 null을 반환하지 말고 예외를 던져라
    • 선택적 매개변수는 null 대신 정적 다형성 (overload)를 사용해서 표현하라
  4. null 객체를 활용하자

    • 인터페이스는 동일하지만 아무일도 하지 않는 일종의 더미 객체
    • 이 객체와 협력하는 객체는 더미라는 사실을 몰라야 함
    • 디미터 법칙과 '묻지 말고 시켜라'를 잘 지켜야 유용함
  5. Null을 명시적으로 표현하자

    • null은 참조 갑사이 없음을 나타내는 암시적 표현방법

    • java.util.Optional 은 값이 없음을 명시적으로 표현하는 컨테이너 객체

    • 값 기반 객체 (Value Based Object) 를 준비중이지만 직렬화가 불가능

      • 그리하여 메서드 반환값으로만 사용하기를 권고
    • 컨테이너 객체라서 박싱/언박싱이 일어날 수 있음

      • 이럴때는 기본 값 Optional을 쓰길 권장

      https://s3-us-west-2.amazonaws.com/secure.notion-static.com/314eaf18-3753-459b-bf2b-12362bf198e3/Untitled.png

  6. 계약에 의한 설계를 적용하자

    • 형식적 규약 외에 사전 조건과 사후 조건과 유지 조건을 포함
    • 자바에서는 단정문(Assertion)으로 구현할 수 있음
  7. 구조체에는 펑터를 활용하자

    • 자바는 순쑤 OOP 언어가 아니다, 자바 클래스의 인스턴스가 모두 객체일 필요는 없다.
    • 순수하게 데이터 자체를 공개하고 싶은 것
    • map() = 펑터 = Optional

null 안정성을 도와주는 자바 도구

  • 정적 분석 또는 어노테이션 프로세싱
    • 어노테이션 프로세서(Lombok, Nullaway)
  • 인텔리제이 제공 어노테이션
    • JSR 305의 @Parameter... 지원
      • 메서드, 클래스나 패키지에 지정 가능
      • 매개변수를 기본으로 Null 이 아닌 것으로 설정 (필드는 없음)
  • 스프링 제공 어노테이션
    • JSR-305 지원 외 스프링 5에서 추가 널 안정성 지원
      • @NonNull, @Nullable: aㅐ개변수, 반환갓, 필드에 지정가능
      • @NonNullApi, @NonNullField
        • 패키지에 지정가능