** IoC(Inversion of Control)가 무엇인지 설명해주세요. 그리고 이를 사용하는 이유는 무엇인가요?
IoC(Inversion of Control)는 일반적으로 프로그램의 제어 흐름을 직접 제어하지 않고 외부에서 관리하는 것을 말합니다. 이는 프로그램의 유연성을 증가시키고 결합도를 낮추는데 도움이 됩니다.
전통적인 프로그래밍에서는 우리가 작성하는 프로그램이 전체적인 제어 흐름을 가지고 있습니다. 우리가 작성한 클래스가 필요로 하는 다른 클래스를 직접 생성하고, 호출하며, 관리합니다. 이 방식의 문제점은 모든 컴포넌트가 강하게 결합되어 있어서 변경에 대한 유연성이 낮다는 점입니다.
IoC는 이런 제어 흐름을 '뒤집어' 외부(예: 프레임워크, 컨테이너)에서 객체의 생성과 생명주기를 관리하며 필요한 의존성을 주입합니다. 이렇게 하면 각 컴포넌트는 서로를 직접적으로 알 필요가 없으므로 결합도가 낮아지고, 교체나 수정이 용이해집니다. 이는 유지보수와 테스트 작성을 쉽게 만들며, 개발 과정에서의 유연성을 증가시킵니다.
** Dependency Injection(DI)가 무엇인지 설명해주실 수 있나요? 그리고 왜 이를 사용하나요?
DI는 의존성 주입이라고 불리며, 객체의 생성과 의존 관계를 외부에서 정의하는 디자인 패턴입니다. DI를 사용하면 각 클래스 사이에 있는 의존 관계를 느슨하게 만들어서 결합도를 낮출 수 있고, 코드의 유지 보수성을 향상시키고, 테스트를 용이하게 만듭니다. DI는 IoC(Inversion of Control)의 한 형태로, 제어의 역전을 통해 객체의 생명주기를 개발자가 아닌 프레임워크에게 맡기는 것을 가능하게 합니다.
어플리케이션의 다른 부분에서 사용되는 객체를 생성하고 관리하는 방법입니다. 코드의 재사용성, 유지보수성을 향상시키고, 모듈 간의 결합도를 낮추는 데 도움이 됩니다.
**Spring Framework에서는 어떻게 DI를 구현하나요?
Spring Framework에서는 주로 두 가지 방법을 통해 의존성 주입(Dependency Injection, DI)을 구현합니다
1. 생성자 주입 : 객체 생성 시 생성자를 통해 필요한 의존성을 주입하는 방법입니다. 객체는 생성되는 시점에 모든 필요한 의존성을 갖게 되며, 객체의 불변성을 보장할 수 있습니다. 생성자 주입은 필수적인 의존성을 주입하는 경우에 주로 사용됩니다.
ex.
객체가 만들어질 때 필요한 '재료'를 생성자에 전달하여 객체를 만드는 방법입니다. 예를 들어, 자동차를 만들 때 엔진, 타이어, 핸들 등이 필요한데, 이것들이 바로 '의존성'입니다. 자동차를 만드는 사람은 이 '재료'들을 미리 준비하고, 자동차 생성자에게 전달하여 자동차를 만듭니다.
2. 세터 메서드 주입: 세터 메서드를 통해 의존성을 주입하는 방법입니다. 세터 메서드 주입은 선택적인 의존성을 주입할 때 주로 사용됩니다. 이 방식을 사용하면 객체가 생성된 후에 의존성을 변경할 수 있지만, 잘못 사용하면 불변성이 깨질 수 있습니다.
ex.
객체가 만들어진 후 필요한 '재료'를 세팅하는 방법입니다. 여기서도 자동차 예시를 들어보겠습니다. 자동차를 만든 후에 나중에 라디오를 추가하려면 세터 메서드를 사용하여 라디오를 추가할 수 있습니다.
이 외에도 필드 주입(Field Injection) 방법도 제공하지만, 이 방법은 테스트와 다른 상황에서 의존성을 변경하기 어려워 사용을 권장하지 않습니다.
즉, 생성자 주입은 주로 필수적인 '재료'를 주입할 때 사용되고, 세터 주입은 선택적인 '재료'를 주입할 때 사용됩니다.
** Constructor Injection과 Setter Injection의 차이점은 무엇인가요? 어떤 상황에서 어떤 것을 사용하는 것이 좋은가요?
주요 차이점은 객체 생성 시점에 의존성이 주입되는 시점이 다르다는 것
Constructor Injection:
- Constructor Injection은 객체 생성과 동시에 모든 의존성이 주입됩니다. 따라서, 주입받는 객체가 생성되는 시점에 필요한 모든 의존성을 갖게 됩니다.
- 이 방식을 사용하면 객체가 변하지 않는 불변성(Immutability)을 보장할 수 있습니다. 한 번 객체가 생성되면 그 상태를 변경할 수 없습니다.
- 또한 순환 의존성(Circular Dependency) 문제를 컴파일 시점에 쉽게 발견할 수 있습니다.
Setter Injection:
- Setter Injection은 객체 생성 후 필요할 때 의존성을 주입할 수 있습니다. 객체가 생성된 후에도 상태를 변경할 수 있습니다.
- 이 방식은 선택적인 의존성이 있는 경우 유용합니다. 즉, 특정 조건 하에서만 필요한 의존성을 주입할 수 있습니다.
- 하지만 이 방식을 사용하면 객체의 상태가 변할 수 있으며, 순환 의존성 문제를 런타임 시점에야 발견할 수 있습니다.
** DI를 사용하면 어떤 문제점이 발생할 수 있나요? 이런 문제를 어떻게 해결하나요?
- 복잡성 증가: DI는 각 클래스와 객체들 사이의 의존성을 관리하기 위해 추가적인 구성 요소와 코드가 필요합니다. 이로 인해 코드의 복잡성이 증가할 수 있으며, 초기에는 이해하기 어려울 수 있습니다. 이를 해결하기 위해, DI 프레임워크(Spring 등)를 사용하여 복잡성을 줄일 수 있습니다. 또한, 잘 정리된 코드와 문서를 유지하여 복잡성을 관리할 수 있습니다.
- 코드 추적의 어려움: DI를 사용하면 객체 생성 로직이 분산되므로, 어떤 객체가 어떻게 생성되고 어디에서 사용되는지 추적하기가 어려울 수 있습니다. 이 문제는 IDE의 도구나, DI 프레임워크가 제공하는 디버깅 도구를 사용하여 해결할 수 있습니다.
- Over-engineering: 많은 개발자들이 DI가 항상 필요한 것이라고 생각할 수 있지만, 작은 프로젝트나 단순한 코드에서는 DI를 사용하지 않는 것이 더 효과적일 수 있습니다. 너무 복잡하게 생각하지 않고, 실제 필요한 상황에서만 DI를 적용하는 것이 중요합니다.
이런 문제점들에도 불구하고, DI는 객체 간의 의존성을 줄이고, 테스트 용이성을 높이며, 코드의 재사용성을 증가시키는 등 많은 장점을 제공합니다. 따라서, DI의 장점과 단점을 잘 이해하고 적절하게 활용하는 것이 중요합니다.
** DI를 통해 단위 테스트를 작성하고 실행하는 방법에 대해 설명해주세요.
DI를 사용하면 실제 의존성 대신 mock 객체를 주입할 수 있어 테스트를 쉽게 작성하고 실행할 수 있습니다.
이는 코드의 결합도를 낮추고, 테스트의 격리를 보장합니다.
** IoC 컨테이너가 무엇인지, 그리고 그 기능에 대해 설명해주세요.
IoC(Inversion of Control) 컨테이너는 프레임워크나 라이브러리에서 제공하는 컴포넌트를 관리하고 조율하는 역할을 합니다. IoC라는 용어는 컴포넌트의 제어 권한이 사용자가 아니라 프레임워크에게 있음을 의미합니다.
IoC 컨테이너의 주요 기능은 다음과 같습니다:
- 객체의 생명 주기 관리: IoC 컨테이너는 프로그램에서 사용되는 객체의 생성과 소멸, 그리고 그 생명 주기를 관리합니다. 이를 통해 개발자는 객체의 생명 주기를 직접 관리하는 데 신경 쓰지 않고 비즈니스 로직에 집중할 수 있습니다.
- 의존성 주입(Dependency Injection): IoC 컨테이너는 객체가 필요로 하는 의존성을 자동으로 주입합니다. 이를 통해 객체간의 강한 결합도를 줄이고, 코드의 재사용성과 테스트 용이성을 증가시킬 수 있습니다.
- 구성 관리(Configuration Management): IoC 컨테이너는 애플리케이션의 환경 설정과 같은 구성 정보를 관리합니다. 예를 들어, 데이터베이스 연결 정보나 외부 서비스의 URL 같은 정보를 애플리케이션 코드에서 분리하여 IoC 컨테이너에서 관리하게 되면, 이런 설정 정보를 바꿀 때 코드를 바꾸지 않고도 설정만 변경하면 됩니다. 이를 통해 개발 환경, 테스트 환경, 실제 서비스 환경 등에서 각기 다른 설정을 사용할 수 있어 편리합니다.
- 서비스 제공(Service Provisioning): IoC 컨테이너는 필요한 서비스를 제공하는 역할도 합니다.예를 들어, 보안 관련 처리(인증, 권한 체크 등), 트랜잭션 관리, 로깅 등의 서비스를 제공합니다. 이런 기능들은 많은 애플리케이션에서 공통적으로 필요한 기능인데, 이런 것들을 각각의 애플리케이션 코드에서 직접 구현하는 것은 비효율적입니다. 이런 공통 기능들을 IoC 컨테이너가 제공하면 애플리케이션 코드는 이런 기능들을 직접 구현할 필요 없이 IoC 컨테이너가 제공하는 기능을 사용하기만 하면 됩니다. 이로써 개발이 단순화되고 효율이 높아집니다.
Spring Framework의 IoC 컨테이너는 'ApplicationContext'라는 이름으로 제공됩니다. 이 컨테이너는 BeanFactory 인터페이스를 상속받아 구현되며, Bean 객체의 생성, 초기화, 관리 등을 책임지고 있습니다.
** Bean이란 무엇인가요? Spring에서 Bean의 라이프사이클은 어떻게 관리되나요?
Bean이란 Spring Framework에서 관리하는 객체를 뜻합니다. 이 객체들은 Spring IoC(Inversion of Control) 컨테이너에 의해 생성되고, 관리됩니다. 보통 application-context.xml 파일 또는 어노테이션을 통해 Bean을 등록하고, 이들은 컨테이너가 시작될 때 생성되고 컨테이너가 종료될 때 소멸합니다.
Spring에서 Bean의 라이프사이클은 다음과 같이 관리됩니다.
- Bean 객체 생성: Spring 컨테이너가 Bean의 정의를 읽어들이고, Bean 객체를 생성합니다.
- Bean 속성 설정: Spring 컨테이너는 Bean 정의에 따라 속성(Property)을 설정합니다.
- Bean 초기화: Spring 컨테이너는 Bean이 초기화되는 시점에 @PostConstruct 어노테이션이 붙은 메서드를 호출하거나, 설정 파일에 등록된 초기화 메서드를 호출합니다.
- Bean 사용: 응용프로그램은 이제 Bean을 사용하게 됩니다.
- Bean 소멸: Spring 컨테이너는 @PreDestroy 어노테이션이 붙은 메서드를 호출하거나, 설정 파일에 등록된 소멸 메서드를 호출하여 Bean을 소멸시킵니다.
이렇게 Spring은 Bean의 전체 생명주기를 관리함으로써 개발자가 객체 생성과 소멸, 속성 설정 등과 같은 보일러플레이트 코드를 작성할 필요가 없게 해줍니다. 이를 통해 개발자는 핵심 비즈니스 로직에만 집중할 수 있습니다.
** Spring에서는 어떻게 싱글톤 Bean을 관리하나요? 다른 Bean 범위는 무엇이 있나요?
Spring에서는 기본적으로 모든 Bean을 싱글톤(Singleton)으로 관리합니다. 즉, Spring IoC(Inversion of Control) 컨테이너가 Bean을 생성할 때, 각 Bean에 대해 하나의 인스턴스만을 생성하고 이를 컨테이너 전체에서 공유합니다. 이렇게 함으로써 Spring은 애플리케이션 전체에서 공유되는 객체들의 생명주기를 관리하고, 애플리케이션의 효율성과 확장성을 증가시킵니다.
하지만 모든 상황에서 싱글톤 Bean이 적합하지는 않습니다. 따라서 Spring은 다음과 같은 다양한 범위의 Bean을 지원합니다:
- 싱글톤(Singleton): Spring IoC 컨테이너 당 하나의 인스턴스만 생성. 이것은 Spring의 기본 범위입니다.
- 프로토타입(Prototype): 요청할 때마다 새로운 인스턴스를 생성합니다.
- 요청(Request): HTTP 요청당 하나의 Bean 인스턴스를 생성합니다. 이 범위는 웹 어플리케이션 컨텍스트에서만 유효합니다.
- 세션(Session): HTTP 세션당 하나의 Bean 인스턴스를 생성합니다. 이 범위도 웹 어플리케이션 컨텍스트에서만 유효합니다.
- 애플리케이션(Application): ServletContext당 하나의 Bean 인스턴스를 생성합니다. 이 범위는 웹 어플리케이션 컨텍스트에서만 유효합니다.
이러한 범위를 통해 Spring은 다양한 상황에 맞는 객체의 생명주기를 관리할 수 있습니다.
먼저, Spring에서 'Bean'이란 것은 간단히 말해서 우리가 만드는 프로그램의 일부분을 담당하는 객체입니다. 예를 들어, 사용자 정보를 관리하는 'UserService', 상품 정보를 관리하는 'ProductService' 같은 것들이 Bean이 될 수 있습니다.
Spring은 이런 Bean들을 관리하는데, 이때 Bean들을 어떻게 만들고 사용할지에 대한 규칙을 정하는 것을 'Bean의 라이프사이클 관리'라고 합니다.
이제 '싱글톤(Singleton)'에 대해 알아봅시다. '싱글톤'이란 한 번 만들어진 Bean을 계속해서 재사용하는 것을 말합니다. 예를 들어, 'UserService'라는 Bean이 싱글톤으로 설정되어 있다면, 'UserService'가 필요할 때마다 새로 만들지 않고 처음에 만든 하나를 계속해서 쓰게 됩니다.
하지만 모든 경우에 싱글톤이 좋은 것은 아닙니다. 때때로, 요청이 올 때마다 새로운 Bean을 만들어야 할 경우도 있습니다. 예를 들어, 사용자마다 다른 정보를 담고 있는 'UserSession'과 같은 Bean은 싱글톤으로 만들면 안 됩니다. 이럴 때는 '프로토타입(Prototype)'으로 설정해서, 요청이 올 때마다 새로운 Bean을 만들도록 합니다.
마지막으로, '요청(Request)', '세션(Session)', '애플리케이션(Application)' 범위는 웹 어플리케이션에서 사용됩니다. '요청' 범위는 한 번의 웹 요청이 들어올 때마다 새로운 Bean을 만듭니다. '세션' 범위는 웹 브라우저의 한 세션 동안 유지되는 Bean을 만듭니다. '애플리케이션' 범위는 웹 애플리케이션 전체에서 공유하는 하나의 Bean을 만듭니다.
이렇게 Spring은 다양한 상황에 맞게 Bean을 만들고 관리할 수 있도록 도와줍니다.
** 싱글톤 패턴이 무엇인지 설명해주실 수 있나요? 그리고 이를 어떻게 구현하나요?
싱글톤 패턴(Singleton Pattern)은 객체 지향 프로그래밍에서 사용하는 디자인 패턴 중 하나로, 클래스의 인스턴스가 하나만 만들어지고, 어디서든지 그 인스턴스에 접근할 수 있도록 하는 것을 목적으로 합니다. 이 패턴은 전역 변수를 사용하지 않고 객체를 전역적으로 액세스할 수 있게 해줍니다.
싱글톤 패턴은 주로 공유 리소스에 대한 동시 접근을 제어하거나, 한 시스템에서 사용되는 환경 설정과 같은 상황에서 사용됩니다.
자바에서는 주로 private 생성자를 가진 클래스를 만들고, 클래스 내부에 해당 클래스의 static 변수를 만듭니다. 이후 외부에서 접근할 수 있는 public static 메서드를 제공하여 이 메서드가 클래스의 유일한 인스턴스를 반환하도록 합니다.
public class Singleton {
// 자신의 타입인 정적 필드를 가지고 있습니다. 이 필드가 유일한 인스턴스를 저장합니다.
private static Singleton uniqueInstance;
// 생성자를 private로 선언해, 외부에서 인스턴스를 직접 생성할 수 없게 합니다.
private Singleton() {}
// 유일한 인스턴스를 얻는 메서드를 제공합니다.
public static Singleton getInstance() {
// 인스턴스가 없을 때만 생성합니다. 이를 "Lazy Initialization"라고 합니다.
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
// 다른 메서드들...
}
위 예제에서 볼 수 있듯이, 싱글톤 패턴은 private 생성자와 자신의 타입을 반환하는 정적 메서드를 사용하여 구현합니다. 싱글톤 객체가 아직 생성되지 않았을 때에만 객체를 생성하고, 이미 생성된 경우에는 해당 객체를 반환합니다.
단, 이 방식은 멀티스레딩 환경에서 동기화 문제를 야기할 수 있습니다. 따라서 멀티스레딩 환경에서는 추가적인 동기화 처리가 필요할 수 있습니다. 이를 위해 getInstance() 메서드를 synchronized로 선언하거나, "Initialization-on-demand holder idiom"과 같은 다른 방법을 사용할 수 있습니다.
멀티스레딩 환경에서 싱글톤 패턴을 사용하면 동기화 문제가 발생할 수 있습니다. 즉, 동시에 두 개 이상의 스레드가 getInstance() 메소드를 호출하면, 유일해야 하는 인스턴스가 두 개 이상 생성될 수 있습니다.
이를 해결하기 위한 여러 가지 방법이 있습니다. 먼저 getInstance() 메소드를 synchronized로 선언하는 방법이 있습니다. synchronized 키워드를 사용하면 메소드가 한 번에 한 스레드만 접근할 수 있도록 동기화를 보장합니다.
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {}
// synchronized 키워드를 추가했습니다.
public static synchronized Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
// 다른 메서드들...
}
그러나 이 방법은 getInstance() 메소드에 대한 동시 접근을 막아 성능 저하를 일으킬 수 있습니다. 그래서 더 효율적인 방법이 필요한데, 그 중 하나가 "Initialization-on-demand holder idiom"입니다.
public class Singleton {
private Singleton() {}
// 이 내부 클래스는 getInstance() 메서드가 처음 호출될 때 로드됩니다.
private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
// 다른 메서드들...
}
이 방법은 JVM의 클래스 로딩 메커니즘과 클래스가 로딩될 때 한 번만 초기화된다는 점을 이용합니다. Holder 내부 클래스는 getInstance() 메서드가 처음 호출될 때 로드되며, 그 때 Singleton 인스턴스가 생성됩니다. 이후 getInstance() 호출에서는 이미 생성된 인스턴스를 반환하므로 동기화가 필요 없고, "lazy initialization"도 가능합니다.
"Initialization-on-demand holder idiom"은 Singleton 패턴을 구현하는 방법 중 하나입니다. 이 패턴은 Java의 언어 스펙을 최대한 활용하여 thread-safe한 Singleton을 만들고, 동시에 lazy initialization도 가능하게 합니다.
Java의 클래스 로딩 방식과 클래스 초기화에 대한 스펙 때문에 이 방법이 가능합니다. Java는 클래스가 처음으로 참조될 때 해당 클래스를 로딩하고 초기화합니다. 그리고 클래스 초기화는 여러 스레드에 의해 동시에 실행될 수 없습니다. 즉, 클래스 초기화는 내부적으로 동기화가 보장되어 있습니다.
이런 특성을 이용하여, Initialization-on-demand holder idiom은 Singleton 객체를 가지고 있는 private static 내부 클래스(Holder 클래스)를 정의합니다. 이 Holder 클래스는 Singleton 클래스의 getInstance() 메소드가 호출될 때 처음으로 참조되므로, 그때 클래스가 로딩되고 Singleton 인스턴스가 생성됩니다.
위의 코드에서 보면, Holder 클래스는 getInstance() 메소드가 호출될 때 처음으로 참조되므로, 그 시점에서 Singleton 인스턴스가 생성됩니다. 이후 getInstance() 메소드가 호출되면, 이미 생성된 Singleton 인스턴스를 반환합니다. 이렇게 하면 불필요한 동기화 없이 thread-safe하게 Singleton 인스턴스를 생성하고, 동시에 lazy initialization도 가능하게 할 수 있습니다.
'Mockterview' 카테고리의 다른 글
사용자 패스워드를 전송하고 보관하는 방법(일반적인 보안 방법) pt.2 (0) | 2023.05.24 |
---|---|
인덱스(Index), Composite(복합)인덱스 pt.2 (0) | 2023.05.24 |
트리(tree)와 그래프(graph) (0) | 2023.05.23 |
이분탐색(Binary Search)의 시간복잡도 = O(log n) (0) | 2023.05.23 |
Index(인덱스), B-tree, Hash pt.1 (0) | 2023.05.19 |