자바 스프링 면접질문 정리

Hyun·2024년 7월 18일
1

면접질문 정리

목록 보기
6/6

https://github.com/VSFe/Tech-Interview/blob/main/07-JAVA_SPRING.md 의 면접질문들에 대한 답을 나름대로 정리한 포스팅

JVM

  • Java Virtual Machine

  • 자바로 작성된 프로그램(.class)이 돌아갈 수 있도록 만들어주는 프로그램

    • 자바 바이트코드 뿐 아니라 바이트코드를 생성할 수 있는 다른 언어로 된 프로그램도 모두 실행할 수 있다.
  • 바이트코드 프로그램을 다양한 OS 환경에서 실행 가능하게 해주며, 프로그램이 사용하는 메모리를 관리한다.


그럼, 자바 말고 다른 언어는 JVM 위에 올릴 수 없나요?

  • 바이트코드를 생성할 수 있다면 다른 언어로 된 프로그램도 실행할 수 있다.

  • JVM 패밀리에는 코틀린, 스칼라, 그루비, 클로져, JRuby, Jython등이 있다.


반대로 JVM 계열 언어를 일반적으로 컴파일해서 사용할 순 없나요?

  • 일반적으로는 바이트코드로 컴파일한 후 JVM에서 실행하지만 바이트코드 외에도 다른 형식으로 컴파일될 수 있다.

  • 코틀린의 경우 Kotlin Native 컴파일러를 사용하여 기계어로 컴파일할 수 있다.

  • 그러나 JVM은 바이트코드를 실행하는데 최적화되어 있으므로 기계어로 컴파일하는 것이 권장되지 않는다.


JVM을 사용함으로써 얻을 수 있는 장점과 단점에 대해 설명해 주세요.

  • 장점

    • 한번 작성한 코드를 여러 운영 체제에서 실행할 수 있다. (플랫폼 독립성)

    • JVM은 런타임에 메모리 관리와 예외 처리를 제공하여 프로그램의 안전성을 높인다.

    • JIT 컴파일러와 같은 기술을 통해 런타임 성능을 향상시킬 수 있다.

    • 풍부한 생태계를 갖고 있어 다양한 라이브러리와 프레임워크를 사용할 수 있다.

  • 단점

    • JIT 컴파일러에 의해 최적화되기 전엔, 네이티브 코드에 비해 초기 성능이 떨어질 수 있다. (cold start)

    • JVM 자체의 메모리 관리를 위해 추가적인 메모리 공간이 필요하다.


JVM과 내부에서 실행되고 있는 프로그램은 부모 프로세스 - 자식 프로세스 관계를 갖고 있다고 봐도 무방한가요?

  • JVM은 하나의 독립적인 프로세스이며, 내부에서 동작하는 자바 애플리케이션은 스레드를 사용하여 처리한다.

  • 따라서 부모 프로세스 - 자식 프로세스 관계가 아니라 단일 프로세스 - 스레드 관계이다.


final 키워드

  • final 키워드는 변수, 메서드, 클래스에 사용할 수 있으며 선언된 대상의 변경을 금지한다.

  • 컴파일러가 변수나 메서드를 변경할 수 없다는 것을 알고 코드를 더 효과적으로 최적화할 수 있다.

    • 스레드 안전성을 보장하고, 협업 시 디자인 의도를 표현할 수 있다.
  • final 변수

    • 한번 초기화된 이후 값을 변경할 수 없다.

    • 변수 선언 시 또는 클래스 생성자에서 반드시 초기화해야 한다.

    • 객체의 경우 재할당을 막지만 내부 상태 변화는 막을 수 없다.

  • final 메서드

    • 하위 클래스에서 메서드를 재정의할 수 없다.
  • final 클래스

    • 클래스를 상속하거나 확장하거나 수정할 수 없다.

그렇다면 컴파일 과정에서, final 키워드는 다르게 취급되나요?

  • final 키워드를 사용한다고 해서 큰 성능차이가 발생하진 않는다.

    • final 키워드를 사용한 경우 컴파일러 과정에서 최적화를 수행해 조금 더 빠르긴 하다.

    • 컴파일러는 변수를 호출하는 대신 실제 값을 하드 코딩하는 방식으로 최적화 수행

  • 따라서 성능 개선보다는 협업 시 디자인 의도를 표현하기 위해 사용한다.


인터페이스 & 추상 클래스

  • 인터페이스

    • 클래스가 반드시 구현해야 하는 메서드의 집합을 정의한 것으로 메서드 시그니처만 포함하며 구현은 하지 않는 것이 일반적이다.

    • 한 클래스가 여러 인터페이스를 구현할 수 있다. (다중 상속)

    • 인터페이스는 다른 인터페이스를 상속할 수 있다.

    • 인터페이스에 정의된 메서드를 각 구현 클래스의 목적에 맞게 기능을 구현해야 한다.

  • 추상 클래스

    • 하나 이상의 추상 메서드를 갖고 있는 클래스

    • 추상 클래스를 상속한 서브 클래스는 추상 메서드 구현이 강제된다.

    • 한 클래스는 하나의 추상 클래스만 상속할 수 있다. (다중 상속 불가)

    • 추상 클래스는 다른 클래스를 상속할 수 있다.

    • 서브 클래스들이 공통으로 가져야하는 메서드와 필드가 많은 경우 주로 사용한다.


왜 클래스는 단일 상속만 가능한데, 인터페이스는 2개 이상 구현이 가능할까요?

  • 클래스의 다중 상속을 허용하게 되면 두 개 이상의 부모 클래스로부터 동일한 메서드를 상속받을 때, 어떤 메서드를 사용할지 모호해진다. (다이아몬드 문제)

  • 반면 인터페이스는 구현을 제공하지 않으므로 다이아몬드 문제가 발생하지 않는다.

  • 디폴트 메서드가 충돌하는 경우에는 명시적으로 어느 인터페이스의 메서드를 사용할지 오버라이딩해야 한다.


리플렉션

  • 리플렉션은 자바에서 런타임에 클래스의 메타데이터를 조회하거나 수정할 수 있는 기능이다.

  • 이를 통해 컴파일 시간에는 알 수 없었던 클래스 정보에 접근하거나 동적으로 객체를 생성하고 메서드를 호출할 수 있다.

  • 잘못 사용하면 성능 저하를 일으킬 수 있다.

  • 라이브러리나 프레임워크가 개발자가 생성한 객체가 어떤 타입인지 컴파일 시점에는 알 수 없기에 이와 관련된 문제를 동적으로 해결하기 위해서 리플렉션을 사용한다.


리플렉션의 단점

  • 리플렉션 API는 컴파일 시점이 아닌 런타임 시점에 클래스를 분석한다.

    • 따라서 JVM에서 최적화할 수 없기 때문에 일반 메서드 호출보다 성능이 떨어진다.
  • 리플렉션은 런타임 시점에 클래스 정보를 알게 된다.

    • 따라서 컴파일 시점에 제공하는 타입 체크 기능을 사용할 수 없어 런타임 예외가 발생할 수 있다.
  • 내부를 노출해서 추상화를 파괴하고 불변성을 보장할 수 없게 된다.

  • 따라서 가능한 사용을 최소화하고, 성능에 민감한 애플리케이션에서는 사용을 지양하는 것이 좋다.


리플렉션을 언제 활용할 수 있을까요?

  • 많은 프레임워크나 라이브러리에서 개발자가 생성한 객체를 동적으로 알아내고 처리하기 위해 사용한다.

  • 스프링이나 JPA가 지원하는 다양한 애노테이션들의 기능이 동작하기 위해서는 리플렉션이 필요하다.


static class와 static method

  • static class

    • 자바에서는 내부 클래스에 대해서만 static 키워드를 사용할 수 있다.

    • 이렇게 만들어진 내부 클래스는 외부 클래스에 대한 참조를 갖고 있지 않는 완전히 분리된 클래스이다.

  • static method

    • 특정 인스턴스가 아닌 클래스 자체에 속하는 정적 메서드

    • 오버라이드 할 수 없고, 클래스 내의 인스턴스 변수나 인스턴스 메서드에 접근할 수 없다.


static 을 사용하면 어떤 이점을 얻을 수 있나요? 어떤 제약이 걸릴까요?

  • 장점

    • class - 외부 클래스에 대한 참조가 없기에 내부 클래스만 사용하더라도 외부 클래스가 GC되지 못하는 메모리 누수가 발생하지 않는다.

    • method - 클래스 인스턴스 없이 메서드를 호출하여 메모리를 절약할 수 있다.

  • 단점

    • class - 내부 클래스에서 외부 클래스의 인스턴스 멤버에 접근할 수 없다.

    • method - 클래스 내부의 인스턴스 변수나 메서드에 접근할 수 없다.


컴파일 과정에서 static 이 어떻게 처리되는지 설명해 주세요.

  • static 키워드가 붙은 메서드는 컴파일 타임에 호출할 정확한 메서드가 결정된다.

    • 반면 인스턴스 메서드는 런타임에 호출할 메서드가 결정된다.
  • static 변수의 경우 method area 영역에 저장되어 모든 인스턴스가 사용할 수 있다.

  • 중첩 static 클래스는 컴파일 시 외부 클래스에 대한 참조 없이 별도의 클래스 파일로 컴파일된다.


Java Exception

  • 시스템에 비정상적인 상황이 생겼을 때 발생하는 오류는 개발자가 처리할 수 없다.

  • 반면, 예외는 개발자가 처리할 수 있는 비정상적인 상황이다.

  • 개발자는 자신이 구현한 코드에서 예외를 예측하고 그에 따른 예외 처리를 해주어야 한다.


예외처리를 하는 세 방법에 대해 설명해 주세요.

  • 예외 복구

    • 예외 상황을 파악하고 문제를 해결해서 정상 상태로 돌려놓는 방법

    • try - catch 블록에서 예외 해결, Exception Handler

  • 예외 처리 회피

    • 예외 처리를 직접 담당하지 않고 호출한 쪽으로 책임을 던져 회피하는 방법

    • 메서드 선언부에 throws

  • 예외 전환

    • 조금 더 명확한 의미를 전달하기 위해 예외를 변경하거나 wrap하는 것

    • 개발자가 thorw 키워드를 사용하여 직접 예외 발생


CheckedException, UncheckedException 의 차이에 대해 설명해 주세요.

  • CheckedException

    • 컴파일 시점에 검사된다.

    • throws 또는 try - catch 블록으로 반드시 처리해야 한다.

  • Unchecked Exception

    • 컴파일 시점에 검사되지 않는다.

예외처리가 성능에 큰 영향을 미치나요? 만약 그렇다면, 어떻게 하면 부하를 줄일 수 있을까요?

  • 예외가 발생할 때마다 예외 객체를 생성하고 호출 스택을 탐색하며 정보를 수집하고 stack trace를 만드는 데 비용이 발생한다.

  • 따라서 예외를 흐름 제어를 위한 용도로 사용하는 것은 좋지 않고, 가급적 예외가 발생하지 않도록 사전 체크를 통해 처리하는 것이 좋다.

  • 또는 stack trace를 제거한 커스텀 예외로 전환함으로써 부하를 줄일 수 있다.


Synchronized

  • 여러 스레드가 동시에 접근할 수 있는 코드 블럭이나 메서드를 한 번에 하나의 스레드에서만 실행할 수 있도록 보장한다.

  • 임계 영역의 크기는 프로그램 성능에 영향을 미치기 때문에 가능하면 메서드 전체에 락을 거는 것보다 임계 영역을 최소화하는 것이 좋다.


Synchronized 키워드가 어디에 붙는지에 따라 의미가 약간씩 변화하는데, 각각 어떤 의미를 갖게 되는지 설명해 주세요.

  • 인스턴스 메서드에 거는 경우

    • 해당 메서드에 대한 동기화를 보장한다.

    • 메서드 호출 시점부터 영역 내의 모든 객체에 락을 얻어 작업을 수행하게 되며, 메서드 종료 시 락을 반환한다.

  • static methods에 거는 경우

    • 클래스 메서드 자체에 대한 동기화를 보장한다.
  • object에 거는 경우

    • 특정 객체에 대한 동기화를 보장한다.

    • 작업이 종료되면 (동기화 코드 블럭을 벗어나면) 락을 반납한다.


효율적인 코드 작성 측면에서, Synchronized는 좋은 키워드일까요?

  • 성능면에서 과도한 동기화는 스레드 경쟁으로 인한 성능 저하를 일으킬 수 있다.

  • 한 스레드가 락을 반납하지 않으면 다른 스레드가 무한정 대기하게 되는 상황이 발생할 수 있다.


Synchronized 를 대체할 수 있는 자바의 다른 동기화 기법에 대해 설명해 주세요.

  • synchronized 블럭 내에서 wait(), notify() 메서드를 통해서 좀 더 복잡한 자원 관리가 가능하다.

  • java.util.concurrent.locks 패키지가 제공하는 Lock 클래스들을 사용하여 동기화 처리를 할 수도 있다.


Thread Local에 대해 설명해 주세요.

  • 각 스레드마다 가지는 독립적인 저장공간

    • 각 스레드가 독립적으로 값을 설정하고 읽음으로써 Thread-Safe한 코드를 작성할 수 있다.
  • 스레드를 key값으로 ThreadLocalMap을 불러올 수 있다.

    • ThreadLocalMap에 자신만의 데이터를 저장하고 사용할 수 있다.
  • 스레드 사용이 끝나는 시점에 스레드를 스레드 풀에 반환하기 전에 ThreadLocal을 초기화하는 것이 안전하다.


Java Stream

  • 일련의 데이터 흐름을 표준화된 방법으로 쉽게 처리할 수 있도록 지원하는 클래스의 집합

  • 일련의 데이터를 함수형 연산을 통해 표준화된 방법으로 쉽게 가공하거나 처리할 수 있다.

  • 가독성이 좋고, 불변성을 활용하여 처리하므로 병렬 처리가 쉽다.

Stream과 for ~ loop의 성능 차이를 비교해 주세요.

  • 단순한 반복문 처리는 for ~ loop가 빠르다.

    • 단순 인덱스 기반 메모리 접근 작업이기에, 추가적인 처리가 필요한 Stream보다 빠르다.

Stream은 병렬처리 할 수 있나요?

  • 병렬 스트림을 사용할 때 사용 가능한 프로세서 코어 수에 맞춰 설정된 Fork/Join 풀을 사용한다.

  • 스트림이 병렬 모드로 설정되면 데이터 소스가 여러 청크로 분할되고, 여러 스레드에서 병렬로 처리된다.

  • 각 스레드는 자체 작업 큐를 유지하고, 큐가 비어있다면 다른 스레드의 큐에서 작업을 가져와서 처리함으로써 작업 분포를 고르게 유지하고 병렬 처리 효율을 높일 수 있다.

  • 각 작업의 결과는 재귀적으로 병합된다.

  • 작은 데이터 세트나 간단한 작업엔 권장되지 않는다.


Stream에서 사용할 수 있는 함수형 인터페이스에 대해 설명해 주세요.

  • 함수형 인터페이스는 1개의 추상 메서드를 갖는 인터페이스이다.

    • 메서드의 이름과 반환값을 생략하고 함수 식으로 표현하는 람다 표현식으로도 나타낼 수 있다.

    • 람다 표현식은 익명 구현 객체를 짧게 표현한 것으로 함수가 아니라 객체이다.

  • 스트림에서 사용할 수 있는 함수형 인터페이스는 다음과 같다.

    • Function<T, R>

      • map 메서드에서 사용한다.
    • Consumer

      • forEach 메서드에서 사용한다.
    • Prediate

      • filter 메서드에서 사용한다.
    • Supplier

      • generate 메서드에서 사용한다.

가끔 외부 변수를 사용할 때, final 키워드를 붙여서 사용하는데 왜 그럴까요? 꼭 그래야 할까요?

  • 지역 변수는 스택 영역에 저장된다.

    • 스택 영역은 각 쓰레드끼리 공유되지 않는 영역이다.
  • 람다 표현식은 다른 쓰레드에서도 실행 가능해야 한다.

    • 다른 쓰레드에서 실행되는 람다 표현식이 현재 쓰레드에서 사용하는 지역 변수를 참조하기 위해서 지역 변수 복사본을 사용한다. (람다 캡처링)

    • 지역 변수가 바뀌면 다른 쓰레드에 있는 지역 변수 복사본 값과 불일치가 일어나기에 동기화가 보장되지 않는 동시성 문제가 발생할 수 있다.

    • 따라서 지역 변수 값이 변하지 않도록 final 키워드를 사용하거나, effectively final 상태여야 한다.


Java의 GC

  • 힙 메모리에서 더 이상 참조되지 않는 객체를 자동으로 정리하여 메모리 누수를 방지하고 개발 생산성을 향상 시키는 메모리 관리 기법

  • GC는 특정 객체가 수거 대상인지 아닌지 판단하기 위해 Reachability 라는 개념을 적용한다.

    • 객체에 대한 참조가 있다면 Reachable

    • 유효한 참조가 없다면 Unreachable로 수거대상


finalize() 를 수동으로 호출하는 것은 왜 문제가 될 수 있을까요?

  • Object 클래스가 제공하는 메서드로 GC가 객체를 회수하기 전에 필요한 정리 작업을 수행하기 위해 호출하는 메서드

  • 해당 메서드 호출 시점은 GC의 스케줄링에 따라 결정되기 때문에 명시적으로 호출하면 문제가 생길 수 있다.

    • 여전히 사용중인 자원을 해제하거나, 이미 해제된 자원을 해제하려고 할 수 있다.

    • 별도의 스레드에서도 동시 호출되어 동시성 문제가 발생할 수 있다.

    • 예외가 발생하여 자원이 제대로 회수되지 않거나 오류를 발생시킬 수 있다.

  • 따라서 finalize( )를 호출하는 것은 바람직하지 않다.


어떤 변수의 값이 null이 되었다면, 이 값은 GC가 될 가능성이 있을까요?

  • 루트 (스택, 전역 변수, 정적 변수) 에서 더 이상 도달할 수 없을 때 GC의 대상이 된다.

  • 따라서 값이 null인 변수가 다른 변수나 객체에 의해 참조된다면 GC의 대상이 되지 않는다.

  • 어떤 객체가 GC의 대상이 되려면 GC가 수행되는 시점에 루트에 존재하는 변수 누구에게도 참조되지 않아야 한다.


equals( ), hashCode( )

  • 두 메서드는 객체의 동등성을 판단하는 데 사용된다.

  • equals 메서드는 객체의 값 자체를 비교하는데 사용된다.

    • Object 클래스에서 제공하는 equals 메서드는 객체의 주소값을 비교한다.

    • 객체의 실제 값을 비교하고 싶다면, 개발자가 equals 메서드를 재정의해야 한다.

  • hashCode 메서드는 객체의 해시 코드를 생성하는데 사용된다.

    • Object 클래스에서 제공하는 hashCode 메서드는 객체의 주소값을 기반으로 해시값을 생성한다.

    • 따라서 해시 기반 컬렉션에서 객체를 올바르게 검색하기 위해서 개발자가 hashCode 메서드를 재정의해야 한다.


본인이 hashcode() 를 정의해야 한다면, 어떤 점을 염두에 두고 구현할 것 같으세요?

  • 해당 객체를 식별하기 위해 어떤 필드를 사용하는지, 검색을 위해 어떤 필드 값을 사용할 것인지를 생각해보고, 해당 필드를 사용하여 해시값을 생성하도록 구현할 것이다.

그렇다면 equals() 를 재정의 해야 할 때, 어떤 점을 염두에 두어야 하는지 설명해 주세요.

  • 객체를 식별하기 위해 어떤 필드를 사용하는지, 어떤 기준으로 두 객체를 동일하게 여길것인지를 고려해보고, 해당 필드를 사용하여 동등 비교를 하도록 구현할 것이다.

IoC와 DI

  • IoC (Inversion Of Control)

    • 기존 프로그램은 클라이언트 구현 객체가 스스로 필요한 서버 구현 객체를 생성하고, 연결하고 실행한다.

    • 즉, 개발자가 프로그램의 제어 흐름을 조종하였다.

    • 반면 스프링부트와 같은 프레임워크는 구현 객체는 자신의 로직을 실행하는 역할에만 집중하고, 프로그램의 제어 흐름은 DI 컨테이너와 같은 설정 정보 클래스가 가져간다.

    • 이를 제어의 역전이라 한다.

  • DI (Dependency Injection)

    • 구현 객체가 동작하기 위해 필요한 의존 객체들을 외부에서 주입해주는 것을 의미한다.

      • 이러한 객체의 합성을 통해 코드의 재사용성을 높이고 객체간의 결합도를 감소시킬 수 있다.
    • DI가 없는 경우, 객체의 추상화를 잘 했더라도 구현 객체를 변경하면 객체를 생성하는 클라이언트의 코드 변경이 필요하다. (OCP 원칙 위반)

    • 또한 클라이언트 코드는 구현 클래스를 생성해야하기 때문에 추상 클래스뿐만 아니라 구현 클래스에도 의존한다. (DIP 원칙 위반)

    • 이러한 문제점들을 해결하기 위해 DI 컨테이너를 사용하고 DI를 수행한다.


후보 없이 특정 기능을 하는 클래스가 딱 한 개라면, 구체 클래스를 그냥 사용해도 되지 않나요? 그럼에도 불구하고 왜 Spring에선 Bean을 사용 할까요?

  • 만약 클라이언트 코드가 new 생성자를 사용해 구체 클래스를 직접 생성하여 사용한다면 클라이언트 코드의 단위 테스트가 어렵다.

    • Spring Bean으로 만들고 의존성을 주입한다면 구체 클래스를 Mocking 할 수 있는데, 그렇지 않은 경우엔 Mocking 할 수 없다.
  • 또한 Spring은 Bean을 싱글톤으로 관리하기 때문에 클라이언트 요청 시마다 이미 만들어진 객체를 공유해 효율적인 재사용을 할 수 있다.


Spring의 Bean 생명 주기에 대해 설명해 주세요.

싱글톤 빈의 생명 주기

  • 스프링 컨테이너 생성

  • 스프링 빈 생성

  • 의존 관계 주입

    • 생성자 주입의 경우 스프링 빈 생성과 의존 관계 주입이 동시에 일어남
  • 초기화 콜백 메서드 호출

  • 스프링 빈 사용

  • 소멸전 콜백 메서드 호출

  • 스프링 컨테이너 종료


프로토타입 빈은 무엇인가요?

  • 싱글톤 빈과 다르게 DI 컨테이너에서 조회시마다 항상 새로운 인스턴스를 생성해서 반환한다.

  • 컨테이너가 빈의 생성, 의존관계 주입, 초기화까지만 처리한다.

  • 싱글톤 빈이 프로토타입 빈을 주입받는 경우, 싱글톤 빈 생성 시점에 단 한 번만 프로토타입 빈을 생성해서 사용한다.

    • 따라서 프로토타입 빈 내부의 필드값들이 싱글톤 빈을 사용하는 여러 클라이언트들과 공유되어 원하는대로 동작이 되지 않을 수 있다.

    • 따라서 프로토타입 빈의 경우, 요청 처리시마다 새로 생성하고 싶다면 Provider를 사용하여 사용 시점에 주입받아야 한다.


AOP

  • Aspect-Oriented Programming, 관점 지향 프로그래밍

  • 횡단 관심사를 깔끔하게 처리하기 위한 프로그래밍 패러다임

  • 애플리케이션의 핵심 로직 코드는 수정하지 않고 부가기능을 추가하기 위해 사용한다.

  • 스프링의 경우 프록시를 사용해 런타임 시점에 AOP를 적용한다.


@Aspect는 어떻게 동작하나요?

  • 포인트 컷

    • 프록시를 적용할 위치를 정한다.
  • 어드바이스

    • 프록시가 수행하는 부가 기능
  • 어드바이저

    • 포인트 컷 + 어드바이스
  • @Aspect 애노테이션을 사용하여 어드바이저들을 모듈화하여 관리할 수 있다.


동작과정

  • 스프링이 시작되면, 프록시 생성기가 모든 @Aspect 빈을 조회한다.

  • @Aspect 어드바이저 빌더를 통해 @Aspect 기반 어드바이저들을 생성 및 저장

  • 스프링 빈으로 객체가 등록될 때, 어드바이저들을 조회하여 매칭되는 경우 객체 대신 객체를 상속받는 프록시로 바꿔치기하여 스프링 컨테이너에 등록한다.

  • 이후 객체를 사용할 땐, 대신 등록된 프록시 빈이 호출되어 횡단관심사 로직을 수행한 뒤 실제 객체를 호출하여 실제 비즈니스 로직을 실행한다.


Spring Interceptor, Servlet Filter

  • 웹과 관련된 공통 관심사는 AOP 대신 서블릿 필터나 스프링 인터셉터를 사용하여 해결한다.

    • 필터나 인터셉터가 Http Request 객체를 제공하기 때문이다.
  • 필터

    • 서블릿을 제공하는 자바 WAS에서 제공하는 기술이다.

    • 디스패처 서블릿 전에 위치하여 요청을 가로채 처리한다.

    • Request, Response 객체 자체를 조작할 수 있다.

  • 인터셉터

    • 스프링 MVC가 제공하는 기술이다.

    • 디스패처 서블릿 이후에 위치하여 컨트롤러로 가는 요청을 가로채 처리한다.

      • 컨트롤러로 넘겨주는 데이터를 가공할 때 많이 사용한다.
    • 스프링의 예외처리 기능을 사용할 수 있다.


설명만 들어보면 인터셉터만 쓰는게 나아보이는데, 아닌가요? 필터는 어떤 상황에 사용 해야 하나요?

  • 스프링 MVC와 분리되어야 하는 기능에 사용하는 것이 좋다.

  • 이미지/데이터 압축이나 문자열 인코딩, 모든 요청에 대한 로깅등과 같은 작업을 할 때 사용할 수 있다.


DispatcherServlet

  • 과거에는 서블릿 컨테이너에 URL 별로 서블릿을 직접 만들어 등록하였다.

    • 공통 작업 처리 코드가 중복되고, web.xml에 서블릿을 직접 등록해야 하기에 불편했다.
  • 스프링 MVC 구조에서는 서블릿 컨테이너에 제일 앞에서 요청에 대한 공통 작업 처리를 수행하고, 적합한 컨트롤러를 찾아 위임해주는 프론트 컨트롤러 하나만 등록하여 사용하고 있다.

    • 이 프론트 컨트롤러가 바로 디스패처 서블릿이다.
  • 디스패처 서블릿은 클라이언트의 요청을 받아 요청을 처리할 핸들러를 찾는다.

    • 핸들러의 종류로는 애노테이션 기반 핸들러와 스프링 빈 이름으로 등록된 핸들러 등이 있다.
  • 핸들러를 찾으면 핸들러를 처리할 수 있는 핸들러 어댑터를 찾아 핸들러를 넘겨주어 실행한다.

    • 이를 통해 다양한 방식으로 구현된 핸들러를 사용할 수 있다.
  • 핸들러 어댑터가 핸들러(컨트롤러)를 처리하기 위해 공통적인 전처리 작업을 수행한다.

    • ArgumentResolver는 요청값들을 적절하게 변환한다.
  • 이후 핸들러의 비즈니스 로직을 수행한다.

  • 핸들러가 반환한 정보는 핸들러 어댑터가 후처리한 뒤 반환한다.

    • 응답 내용을 직렬화하거나 응답 상태를 설정하는 등의 후처리를 수행한다.
  • 디스패처 서블릿은 핸들러 어댑터의 반환값을 클라이언트로 반환한다.


여러 요청이 들어온다고 가정할 때, DispatcherServlet은 한번에 여러 요청을 모두 받을 수 있나요?

  • 받을 수 있다.

  • 서블릿 컨테이너는 스레드 풀을 관리하고 있으며, 각 HTTP 요청은 별도의 스레드에서 처리된다.

  • 따라서 디스패처 서블릿 역시, 멀티 스레딩을 통해 여러개의 요청을 동시 처리할 수 있다.


수많은 @Controller 를 DispatcherServlet은 어떻게 구분 할까요?

  • 클라이언트 요청으로부터 Http Method와 URI를 얻어낸 뒤, 핸들러들을 보관하고 있는 HashMap에서 요청을 처리할 수 있는 핸들러를 찾아낸다.

  • 즉, 컨트롤러에 애노테이션으로 붙은 Http Method와 URI를 통해 각 컨트롤러들을 구분할 수 있다.


JPA와 같은 ORM을 사용하는 이유

  • 객체와 RDB는 다르다.

    • RDB는 상속관계를 지원하지 않는다.

    • RDB는 외래키를 사용해 연관관계를 표현하나, 객체는 참조를 사용한다.

    • RDB에서 같은 로우를 가져오더라도, 객체의 동일성 비교에서는 참이되지 않는다.

  • 그래서 이전에는 SQL 중심으로 개발하였는데, 패러다임의 불일치를 해결하여 객체 중심으로 개발하기 위해서 ORM을 사용한다.

    • 이를 통해 생산성을 증가시킬 수 있다.

영속성은 어떤 기능을 하나요? 이게 진짜 성능 향상에 큰 도움이 되나요?

  • 영속성 컨텍스트란 DB에서 가져온 엔티티를 저장하는 환경으로 1차 캐시라고도 부른다.

    • @Id가 key값이고, 엔티티 객체가 value가 된다.
  • 캐시 역할을 하여 조회 성능을 높인다.

    • 영속성 컨텍스트에 값이 존재하지 않는 경우에만 DB에서 조회하고, 영속성 컨텍스트에 저장한다.
  • 트랜잭션 내에서 같은 엔티티 조회 시, 두 엔티티의 동일성을 보장한다.

  • 쓰기 쿼리들을 바로 날리지 않고 모아두어 성능을 높인다.

  • 엔티티가 최초로 영속성 컨텍스트에 저장될 때 스냅샷을 만들어둔 후, 영속성 컨텍스트 flush가 일어나는 시점에 엔티티의 스냅샷과 다른 부분들을 찾아 update 쿼리를 날린다.


N + 1 문제에 대해 설명해 주세요.

  • JPA 사용 시 빈번하게 발생하는 성능 관련 문제

  • Team 엔티티와 Member 엔티티가 1대다 관계라고 가정

  • Team 목록을 조회한 후, 각 Team 엔티티 내부의 Member 엔티티들에 접근한다면

    • Team 목록 조회 쿼리 1번, Member 엔티티 총 개수만큼 쿼리 N번 발생한다.

    • 이를 N + 1 문제라고 한다.

  • 연관관계 대상이 단일 객체인 경우 페치 조인을 통해 해결할 수 있다.

  • 연관관계 대상이 컬렉션인 경우, default_batch_fetch_size 설정을 통해 해결할 수 있다.


@Transactional 은 어떤 기능을 하나요?

  • 트랜잭션 코드와 비즈니스 코드가 혼합되는걸 피하기 위해 스프링 AOP를 통해 트랜잭션을 처리하는 프록시 객체와 비즈니스 로직을 처리하는 서비스 객체를 분리한다.

트랜잭션을 처리하는 프록시 객체의 동작들은 다음과 같다.

  • 트랜잭션 매니저는 트랜잭션을 시작하기 위해 커넥션을 생성한 뒤, 트랜잭션 동기화 매니저에 커넥션을 보관한다.

    • 쓰레드 로컬을 사용해 커넥션을 보관하므로 멀티 쓰레드에서도 안전하게 커넥션을 보관할 수 있다.
  • 실제 서비스 객체를 호출하여, 비즈니스 로직을 실행하며 리포지토리의 메서드들을 호출한다.

    • 커넥션을 파라미터로 전달하지 않아도 된다.
  • 트랜잭션의 커넥션이 필요한 리포지토리 메서드들은 트랜잭션 동기화 매니저에 보관된 커넥션을 사용함으로써 트랜잭션이 유지된다.

  • 획득한 커넥션을 사용해 SQL을 실행한다.

  • 비즈니스 로직이 끝나면 트랜잭션 매니저는 트랜잭션을 종료하기 위해 트랜잭션 동기화 매니저에 보관된 커넥션을 가져온다.

  • 가져온 커넥션을 사용해 커밋 혹은 롤백한 후 전체 리소스를 정리한다.


@Transactional(readonly=true) 는 어떤 기능인가요? 이게 도움이 되나요?

  • 기본적으로 트랜잭션은 읽기 쓰기 모두 가능하다.

  • 그러나 readOnly=true 옵션 사용 시, 읽기 전용 트랜잭션이 생성되어 쓰기 작업이 불가능해진다.

  • 해당 옵션을 사용하면 스냅샷을 생성하지 않아 성능상 이점이 있다.

  • 특정 메서드가 조회용 메서드임을 단번에 알 수 있어 가독성 측면에서 도움이 된다.


그런데, 읽기에 트랜잭션을 걸 필요가 있나요? @Transactional을 안 붙이면 되는거 아닐까요?

  • 만약 트랜잭션 범위내에서 수행해야 하는 동작이 있는 경우엔 애노테이션을 붙여야 한다.

    • 트랜잭션 내에서 동일한 읽기 결과가 필요할 때

    • 영속성 컨텍스트의 관리를 받으며 지연 로딩이 필요한 경우

  • 그 외에는 @Transactional을 안 붙이는 것이 커넥션을 오래들고있지 않아 성능적으로 유리하다.


Annotation

  • 프로그램 소스코드 안에 추가 정보를 제공하는 주석

  • 컴파일러에게 유용한 정보를 제공하는 표준 애너테이션과

  • 애너테이션을 정의하는데 사용하는 메타 에너테이션이 있다.

    • @Target

      • 애너테이션이 적용가능한 대상을 지정한다.
    • @Retention

      • 애너테이션이 유지되는 기간을 지정한다.
    • @Documented

      • 내가 만든 자바 doc에 해당 애노테이션을 추가시킨다.
  • 애너테이션 구성요소

    • 기본형, String, enum, 애노테이션, Class

    • 예외, 파라미터, 타입 변수 선언은 불가능

    • 각 요소는 기본값을 가질 수 있다.


별 기능이 없는 것 같은데, 어떻게 Spring 에서는 Annotation 이 그렇게 많은 기능을 하는 걸까요?

  • 리플렉션은 구체적인 클래스 타입을 알지 못해도, 클래스의 메소드, 타입, 변수들에 접근할 수 있도록 해주는 자바 API로 Class 타입을 통해 사용할 수 있다.

  • 프레임워크나 라이브러리는 컴파일 시점엔 개발자가 생성한 객체의 타입을 알 수 없다.

  • Spring은 리플렉션을 사용하여 런타임에 클래스나 메서드, 필드, 파라미터 정보를 가져온다.

    • 이후 리플렉션을 통해 원하는 애노테이션이 붙어 있는지 확인한다.

    • 원하는 애노테이션이 붙어 있는 경우 필요한 로직을 수행한다.


Lombok의 @Data를 잘 사용하지 않는 이유는 무엇일까요?

  • @Data는 toString, getter, setter, equalsAndHashCode, final 필드 생성자를 만들어주는 애노테이션이다.

  • 너무나 많은 보일러 플레이트 코드를 자동으로 만들어주기 때문에 여러 부분에서 문제가 발생할 수 있다.

  • 순환 참조 시, toString에 의해 stackOverFlow문제

  • setter 열기

  • 불변객체가 아닌데 equalAndHashCode 열기

  • 리플렉션을 위한 기본 생성자가 없어 발생하는 런타임 에러


Tomcat

  • 서블릿 컨테이너

  • 클라이언트의 요청을 처리하고 동적인 웹 컨텐츠를 생성하여 제공하는 서블릿들을 관리한다.

  • 클라이언트 요청에 맞는 적절한 서블릿 메서드를 제공한다.

  • 클라이언트 요청에 대한 동적 처리를 수행하지만, 분산 트랜잭션과 같은 기능들은 없기에 완벽한 WAS로 분류하기는 어렵다.

  • 서블릿 컨테이너의 오버헤드 때문에 정적인 응답을 처리할 땐 웹 서버가 더 유리하다.


혹시 Netty에 대해 들어보셨나요? 왜 이런 것을 사용할까요?

  • 기존 소켓 프로그래밍은 블로킹 I/O 방식이라 많은 문제가 있었다.

    • 동시 접속이 많으면 많은 리소스가 필요해지고, 무한 대기 현상이 발생할 수 있음
  • 따라서 자바에 NIO(New IO) 방식이 추가되었다.

    • 버퍼 기반, 채널을 사용한 양방향 입출력

    • 작업 준비가 완료된 채널은 인터럽트를 통해 작업 쓰레드에게 통지한다.

      • 작업 준비가 완료된 채널만 처리하므로 작업 쓰레드가 블로킹되지 않는다.

      • 이로써 하나의 쓰레드가 여러개의 채널을 관리할 수 있다.

  • Netty는 NIO 클라이언트 서버 프레임워크로, NIO 방식의 네트워크 프로그래밍을 크게 단순화하고 간소화한다.

    • 톰캣 서버는 하나의 이벤트 큐를 여러개의 스레드가 공유하는데 반해 Netty는 각각의 스레드가 자신만의 이벤트 큐를 갖는 구조이다.

    • Netty는 고성능이 요구되는 네트워크 서버나 실시간 애플리케이션에 많이 사용된다.


출처

https://f-lab.kr/insight/importance-of-overriding-equals-and-hashcode-in-java-20240701?gad_source=1&gclid=CjwKCAjwtNi0BhA1EiwAWZaANNeIbjW8YwqQ2Egjggsrn203vRpXA7y-teQuGGWmHzOMIeKrQxG_QhoC-yUQAvD_BwE
equals(), hashCode()

https://mangkyu.tistory.com/18
디스패처 서블릿

https://hungseong.tistory.com/74
@Transactional(readOnly = true)

https://www.youtube.com/watch?v=67YdHbPZJn4
https://velog.io/@yeon/Reflection%EC%9D%B4%EB%9E%80
스프링과 리플렉션

https://tecoble.techcourse.co.kr/post/2021-05-24-apache-tomcat/?source=post_page-----91fbebf0eb67--------------------------------
톰캣

https://dev-cool.tistory.com/11
NIO

https://narup.tistory.com/118
https://internet-craft.tistory.com/99
Netty

0개의 댓글