- 편한 것이 능사는 아니다.
- Spring Framework의 편리함을 그대로 사용하기 보다는 알고서 사용하는 것이 필요할 것 같다.
- 강한 결합
- 객체 내부에서 다른 객체를 생성하는 것
- 느슨한 결합
- 외부에서 생성된 객체를 인터페이스를 통해서 넘겨받는 것
- 런타임시에 의존관계가 결정되기 때문에 유연한 구조를 가짐
수정자를 통한 주입
- 수정자를 통한 의존관계가 주입되기 전에 해당 객체를 생성하고 사용할 수 있기 때문에 문제가 발생한다.
- 주입이 필요한 객체가 주입이 되지 않아도 얼마든지 객체를 생성할 수 있다는 것이 문제
생성자를 통한 주입
- 객체를 생성할 때 의존관계 주입을 강제할 수 있고 해당 객체의 필드를 final 로 선언하여 불변 객체로 만들수 있다.
필드 주입 (스프링에서 제공)
- 수정자 주입의 단점을 동일하게 가지고 있다.
- 스프링 컨테이너에서 주입을 담당한다.
객체 필드의 불변성
- 객체 생성과 동시에 의존성 주입을 강제할 수 있다.
- 런타임에 객체가 변하는 것을 방지할 수 있다.
순환참조 문제
-
순환참조가 발생하는 경우 어플리케이션이 구동되지 않아 오류를 사전에 파악할 수 있다.
-
(필드 인젝션의 경우) 만약 두 객체가 양방향으로 의존성을 갖게 되는 되는 경우 순환참조가 일어나 끝없은 호출이 일어나게 되면서
StackOverflow
가 발생됨
- 예) 서로의 메소드를 순환 호출하고 있는 경우
-
필드 주입이나, 수정자 주입은 객체 생성시점에는 순환참조가 일어나는지 아닌지 발견할 수 있는 방법이 없다.
테스트의 용이성
출처: https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
-
Sprinb MVC 는 default error 페이지를 따로 제공해주고 있지는 않다.
-
그래서 가장 일반적인 방법으로는
SimpleMappingExceptionResolver
를 사용해 기본 오류 페이지를 설정해왔습니다. -
그러나 Spring Boot fall-back 오류 처리 페이지를 제공한다.
-
애플리케이션 시작시 Spring Boot는
/error
에 대한 매핑을 찾는다.
- 매핑은 설정한
ViewResolver
에 따라서 규칙이 결정됨
- 매핑은 설정한
-
만약
/error
매핑을 찾지 못한 경우 Spring Boot는 자체 폴백 오류 페이지를 정의한다.
- “Whitelabel Error Page” 라는 메시지(와 오류 정보)가 있는 오류 페이지
- REST 요청을 받는 경우도 동일한 정보를 JSON으로 담아 리턴한다.
- 일반적으로 웹 요청을 처리할 때 처리되지 않은 예외가 발생하게 되면 서버는 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";
}
@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 정보가 표출 되기 때문에 권장하지 않음
@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의 의미
- 초기화되지 않은, 정의되지 않음, 값이 없음, 오류 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 가능 타입
-
기본으로 null을 쓰지 말자
- null이 필요할 때만 null일 수 있도록
- 공통 코딩 관례나 스타일 가이드에 포함
- 정적분석/컴파일러 확장 기본 null 불가 표시법
- InteliJ: @ParametersAreNonNullByDefault
-
null 문맥을 제한된 범위 안에 가두자 = 좋은 객체지향 프로그래밍을 하자
- 큰 문제는 제어 가능한 작은 문제로 나누어 정복하고 다시 통합한다.
- 좋은 캡슐화
- 높은 응집성: 한가지 책임만 갖는다.
- 모든 필드가 객체와 생애주기가 같다.
- 모든 메서드가 모든 필드를 대상으로 작업한다.
- 낮은 결합
- 디미터 법칙, '묻지말고 시켜라'
- 인터페이스에 의존
- 높은 응집성: 한가지 책임만 갖는다.
- 디미터 법칙
- 객체의 과도합 결합을 낮출 수 있는 형식적인 규칙
- 내부 구현을 외부로 유출되지 않게 막아줌
- "도트를 오직 하나만 사용하라"
- 예) DDD 에그리것
- Root Aggregate 은 하위 entity, domain 들을 보호하고 있다. (관문 역할을 함)
- 외부 Aggregate 내부의 객체를 참조하지 못한다.
-
API에 null을 최대한 쓰지 말아라
- null을 반환하지 말라
- 빈 컬렉션이나 Null 객체를 활용하라
- 반환값이 없을 수도 있음을 명시적으로 Optional로 표현하라 (다만 성능이 중요하다면 고려를 해봐야 함)
- 오류 상황에는 null을 반환하지 말고 예외를 던져라
- 선택적 매개변수는 null 대신 정적 다형성 (overload)를 사용해서 표현하라
- null을 반환하지 말라
-
null 객체를 활용하자
- 인터페이스는 동일하지만 아무일도 하지 않는 일종의 더미 객체
- 이 객체와 협력하는 객체는 더미라는 사실을 몰라야 함
- 디미터 법칙과 '묻지 말고 시켜라'를 잘 지켜야 유용함
-
Null을 명시적으로 표현하자
-
계약에 의한 설계를 적용하자
- 형식적 규약 외에 사전 조건과 사후 조건과 유지 조건을 포함
- 자바에서는 단정문(Assertion)으로 구현할 수 있음
-
구조체에는 펑터를 활용하자
- 자바는 순쑤 OOP 언어가 아니다, 자바 클래스의 인스턴스가 모두 객체일 필요는 없다.
- 순수하게 데이터 자체를 공개하고 싶은 것
- map() = 펑터 = Optional
- 정적 분석 또는 어노테이션 프로세싱
- 어노테이션 프로세서(Lombok, Nullaway)
- 인텔리제이 제공 어노테이션
- JSR 305의 @Parameter... 지원
- 메서드, 클래스나 패키지에 지정 가능
- 매개변수를 기본으로 Null 이 아닌 것으로 설정 (필드는 없음)
- JSR 305의 @Parameter... 지원
- 스프링 제공 어노테이션
- JSR-305 지원 외 스프링 5에서 추가 널 안정성 지원
- @NonNull, @Nullable: aㅐ개변수, 반환갓, 필드에 지정가능
- @NonNullApi, @NonNullField
- 패키지에 지정가능
- JSR-305 지원 외 스프링 5에서 추가 널 안정성 지원