기술 면접에 대비해서 그동안 공부했던 내용을 복기하며 면접에 나올만한 내용을 정리한다.
전체 동작 구조, 주요 컴포넌트
Spring Mvc 기준
서블릿
과거에는 서블릿을 직접 작성해서 서버를 만들었다. 이를 편리하게 하기위해 JSP같은 기술도 나왔었다.
이 서블릿을 등록해 두면 알아서 생명주기를 관리하는 컨테이너가 있었는데, 대표적으로 톰캣 같은 녀석이 있다. 또 이러한 녀석들을 자바 진영에서는 WAS(Web Application Server)라고 부르기도 한다.
전체적인 동작 흐름
- HTTP 요청
- 톰캣이 이를 받고 서블릿에 배정(또는 생성 후 배정)
- 서블릿 필터
- 디스패처 서블릿이 요청에 배정 받음(프론트 컨트롤러 패턴)
- 핸들러 매퍼가 적합한 핸들러를 결정
- 핸들러 호출 전 pre-handler(인터셉터) 호출
- 핸들러 어댑터 호출
- 핸들러(컨트롤러) 실행
- post-handler(인터셉터) 호출
- 결과로 나온 ModelAndView를 통해 뷰 리졸버가 뷰를 결정
- 뷰가 Http Response를 작성
- 필터의 doFilter 뒷부분 호출
필터, 스프링 시큐리티
- 필터는 서블릿의 기술이기 때문에 스프링 컨테이너에 접근하지 못한다(boot는 접근 할 수 있다. 내장 톰캣이라).
- 따라서 DelegatorFilter를 통해 시큐리티는 이를 해결한다.
핸들러 매퍼
- 요청에 적합한 핸들러를 결정한다.
- 빈 이름 기반, url 기반 등 다양한 설정이 존재한다.
- 이 중 DefaultAnnotationHandlerMapping 이라는 것이 @RequestMapping에 반응하여 작동한다.
핸들러 인터셉터
- 핸들러 매핑이 호출한다.
- preHandle과 postHandle이 존재한다.
핸들러 어댑터, 핸들러(컨트롤러)
- Controller를 구현하여 컨트롤러를 만들 수도, 어노테이션 기반으로 만들 수 도 있다.
- 인터페이스의 유연함을 위해 어댑터가 존재한다. (어댑터 패턴)
- 이 어댑터는 Supports 류의 메서드를 통해 해당 핸들러를 다룰 수 있는지 체크한다.
뷰, 뷰 리졸버
- 컨트롤러 결과로 나온 ModelAndView 객체로 부터 http res를 작성한다.
- 만약 ModelAndView에서 뷰 오브젝트를 직접 돌려줬다면 뷰 리졸버는 필요 없다. 하지만 이름을 돌려주는 경우 뷰 리졸버가 뷰를 결정한다.
핸들러 예외 리졸버
- 컨트롤러에서 발생한 예외는 HandlerExceptionResolver 인터페이스 구현체에 의해 처리되고 ModelAndView 형태의 리턴값을 가진다.
- 기본으로 등록되어 있는 것은
- AnnotationMethodHandlerExceptionResolver : @ExceptionHandler기반 처리
- ResponseStatusExceptionResolver : @ResponseStatus 기반 처리
- DefaultHandlerExceptionResolver : 위 두 방식에서 처리하지 못한 것에 대한 처리를 담당한다.
AOP
aop 또한 면접에서 자주 질문 받았다.
작동 원리
- AOP는 프록시 패턴을 기반으로 작동한다.
- 기본적으로는 조상에 Interface가 있는 경우, DynamicProxy를 사용하고
- 인터페이스가 없는 경우, CG Lib을 통해 상속을 받는다.
- DynamicProxy를 사용한 경우, 세부 클래스타입으로 주입 받는 경우 에러가 난다.
- Spring Boot의 경우, Transaction AutoConfiguration에 의해 CG Lib이 기본전략으로 설정된다.
CG Lib
- 상속을 한다고 해서, 클래스 자체를 확장해서 처리한다고 생각하면 안된다.
- 내부에 AopUtils(?맞나 이름이)라는 클래스를 통해 처리하는데, 보면 내부에 멤버 변수로 target과 메서드를 가지고 있는다. Aspect를 전부 실행한 다음에 Java Reflection을 통해 원래 메서드를 호출한다.
- 따라서 우리가 구현한 원래 코드에 중단점을 걸고 this를 확인해보아도, 자신 그 자체가 나온다.
자동 프록시 생성기
- 그렇다면 AOP 프록시는 어디서 생성될까?
- BeanPostProcessor이다. 이것과 관련된 생성기는 자동 등록된다.
- 다 만들어진 빈에 대해 프록시를 생성하고 이를 대체시킨다.
AOP 순서
- 이 또한 매우 중요한데, 예를 들어 @Transactional이 내가 작성한 AOP 순서 앞에서 실행되나 하는 것은 매우 중요한 문제다.
- 기본적으로는 Spring Bean의 Order에 따른다. 참고로 @Transactional과 그냥 선언된 AOP의 경우 가장 낮은 우선순위를 가지며, 이는 순서가 랜덤이라는 말이다.
- @Transactional의 순서를 변경하기 위해서는 @EnableTransactionManagement(?이름 맞나 확실치 않음)의 속성으로 지정할 수 있다.
데이터 예외 추상화
- AOP를 활용하여 @Repository가 붙은 클래스에서 발생하는 예외를 DataAccessException의 자손 예외로 변환해준다.
AutoConfiguration
원리
- @Import 어노테이션을 통해 특정 설정을 로드할 수 있다.
- 이 인자로 ImportSelector 구현체를 넣을 수도 있는데, 이는 여기서 지정한 클래스 명을 로드하게 된다.
- AutoConfigurationImportSelector에서는 AutoConfiguration 대상을 선정하는데, 코드를 보면 "META-INF/spring/{클래스명}"으로되어 있고 클래스명은 AutoConfiguration이다. 따라서 해당 위치의 파일을 읽어와서 한줄씩 파싱한다.
- 따라서 거기에 명시된 클래스들을 자동 로딩하게 된다.
Conditional
- Condition 인터페이스를 구현한 것을 @Condition 어노테이션에 넣어서 만들 수 있다.
- 이는 이 빈을 등록할 것인지를 체크하는 로직이 담겨 있으며, 이는 로딩 과정에서 필터링 하게 된다.
application.properties는 어떻게 로딩되나요?
PropertySource
- 스프링에서는 각종 속성(환경변수, 실행인자 등)에 대한 설저잉 PropertySource라는 인터페이스로 추상화 되어 있으며, Environment에서 getProperty(?이름 확실치 않음)를 호출시, 우선순위 순서로 순회하며 해당 프로퍼티를 찾는다. application.properties 또한 이중 하나이다.
ApplicationContext
상속구조와 각각의 역할
ApplicationEventPublisher
- 이벤트 핸들러에 이벤트 전파
- 어노테이션 기반 구현체 자체 등록되어 있음
MessageSource
BeanFactory
- 빈 컨테이너, 가장 중요
- BeanFactory를 내부에 필드에 두고 사용하기 때문에 ApplicationContext빈 주입과 ApplicationContext 빈 주입은 "동일"하지 않다.
ResourceLoader
- URI 기반으로 리소스를 가져올 수 있다. 내부적으로 프리픽스를 기반으로 다양한 소스로 부터 가져올 수 있게 구성되어 있다.
EnvironmentCapable
- 전형적인 getter류 인터페이스
- Environment를 가져올 수 있다.
- 이 Environment를 통해 PropertySource들의 속성을 읽어올 수 있다.
Transaction
AOP 기반으로 구현되어 있으며, PlatformTransactionManager라는 인터페이르를 구현한 구현체들을 활용한다.
구현체에는 DataSource를 활용하는 구현체, Jpa 구현체 등 여러개가 있다.
Propagation
- required : 없으면 생성, 있으면 편승
- requires_new : 무조건 생성
- supports : 있으면 편승
- mandatory : 없으면 예외 던짐
- never : 있으면 예외 던짐
- nested : 중첨 트랜잭션, 부모 롤백시 같이 롤백, 본인 롤백시 본인만 롤백
Isolaction
나머지는 DB이론과 같지만, 기본인 default의 경우 DB의 자체적인 기본 트랜잭션 수준을 따른다.
Bean, BeanFactory, 생명주기
가장 핵심.
Bean 생명 주기
- 오브젝트 생성
- 초기화(연결)
- 각종 초기화 콜백 메서드 호출(InitializingBean 같은거)
이렇게 생성된다.
소멸시에도 소멸 콜백을 호출하고 제거된다.
다양한 ApplicationContext
- 다양한 ApplicationContext 구현체가 있다.
- 일부 구현체는 테스트에 적합
- 계층 관계로 부모 컨텍스트를 지정할 수 있다.
- 자식 컨텍스트에서 못찾으면 부모 컨텍스트로 간다.
다양한 주입 방법
- 생성자 주입 : 스프링 기술, 순환참조 검증 해줌
- setter 주입 : 스프링 기술, 순환참조 못 걸러줌
- @Autowired 주입 : 스프링 기술, 순환참조 못 걸러줌, BeanPostProcessor에서 주입
- @Resource : Java 스펙, 이름 기반 주입
- @Inject : Java 스펙, 타입 기반 주입
BeanPostProcessor
- 빈의 초기화 콜백(InitializingBean 등) 전과 후를 지정해 설정 가능
- AOP 자동 프록시 빈, @Autowired, (Spring data jpa 쿼리 메서드(확실치 않아용...)) 등이 여기서 초기화 된다.
스코프
기본적으로는 스프링 빈은 싱글톤으로 생성된다.
프로토 타입 스코프
- getBean 호출시 마다 새로 생성
- 싱글톤 빈과 함께 사용시 위험
- 다음과 같은 방식으로 싱글톤 빈에서 활용 가능
- ApplicationContext.getBean
- ObjectFactory<>
- Provider<>
기타 스코프
- 세션 스코프
- 애플리케이션 스코프 : 서블릿 컨텍스트에 저장된다.
어플리케이션 시작시점에 무언가 하기
- ApplicationRunner
- CommandLineRunner
- ApplicationStartedEvent(?이름 명확치 않음) 받아서 이벤트 처리
깔끔한 정리 감사합니다.
그런데 다양한 주입 방법 섹션에서 @Autowired는 순환 참조를 탐지 못한다고 되어있는데
생성자 주입의 경우에도 내부적으로는 @Autowired를 사용하니까 (다만 생성자가 1개면 생략이 가능할 뿐)
정확히는 생성자 주입 + @Autowired의 경우에는 순환 참조 탐지 O
Setter 주입 + @Autowired의 경우에는 순환 참조 탐지 X
이게 아닐까 싶어요!