Files
java-examples/docs/Guice.md

8.9 KiB

Guice의 주요 어노테이션과 사용 예시

Google Guice는 의존성 주입(Dependency Injection, DI)을 간편하게 구현할 수 있는 경량 프레임워크로, 다양한 어노테이션을 통해 의존성을 정의하고 관리합니다. 아래에서는 Guice에서 자주 사용되는 주요 어노테이션들을 표로 정리하고, 각 어노테이션의 역할과 사용 예시를 설명하겠습니다.


Guice 주요 어노테이션 표

어노테이션 설명 사용 위치 주요 특징 및 용도
@Inject 의존성을 주입할 위치를 지정합니다. 생성자, 필드, 메서드에 사용 가능합니다. 생성자, 필드, Setter 메서드 Guice가 자동으로 의존성을 주입하도록 지시. 선택적 주입 가능 (@Inject(optional=true)).
@Named 동일한 타입의 여러 구현체 중 특정 구현체를 선택하기 위해 사용됩니다. 필드, 매개변수 문자열 키로 구체적인 바인딩을 식별.
@Singleton 클래스의 인스턴스가 단일 객체로 유지되도록 지정합니다. 클래스 싱글턴 패턴을 구현하여 메모리 효율성 향상.
@Provides 모듈 내에서 의존성을 제공하는 메서드를 정의합니다. 모듈의 메서드 복잡한 객체 생성 로직을 직접 작성 가능.
@ImplementedBy 인터페이스의 기본 구현체를 지정합니다. 인터페이스 모듈 없이 기본 바인딩을 설정.
@ProvidedBy 동적으로 의존성을 제공하는 클래스를 지정합니다. 인터페이스 Provider 클래스를 통해 의존성 공급.
@Qualifier 사용자 정의 qualifier를 생성하기 위한 메타 어노테이션입니다. 사용자 정의 어노테이션 @Named보다 더 세밀한 의존성 구분 가능.

어노테이션별 설명 및 예시

1. @Inject

  • 설명: Guice가 의존성을 주입할 위치를 나타냅니다. 생성자 주입, 필드 주입, Setter 주입에 사용됩니다.
  • 예시:
import com.google.inject.Inject;

class UserService {
    private final Database database;

    @Inject
    public UserService(Database database) {  // 생성자 주입
        this.database = database;
    }

    @Inject
    private Logger logger;  // 필드 주입

    @Inject
    public void setConfig(Config config) {  // Setter 주입
        // 설정 로직
    }
}
  • 특징: 생성자 주입이 가장 권장되며, 불변성을 보장합니다. 필드 주입은 간단하지만 테스트 시 불편할 수 있습니다.

2. @Named

  • 설명: 동일한 인터페이스를 구현한 여러 클래스가 있을 때, 특정 구현체를 선택할 때 사용됩니다.
  • 예시:
import com.google.inject.Inject;
import com.google.inject.name.Named;

interface Database {
    void connect();
}

class MySQLDatabase implements Database {
    public void connect() { System.out.println("MySQL 연결"); }
}

class PostgresDatabase implements Database {
    public void connect() { System.out.println("Postgres 연결"); }
}

class MyModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(Database.class).annotatedWith(Names.named("mysql")).to(MySQLDatabase.class);
        bind(Database.class).annotatedWith(Names.named("postgres")).to(PostgresDatabase.class);
    }
}

class App {
    @Inject @Named("mysql")
    private Database database;

    public void run() {
        database.connect();  // 출력: MySQL 연결
    }
}
  • 특징: 문자열 기반으로 동작하며, 오타에 주의해야 합니다.

3. @Singleton

  • 설명: 클래스의 인스턴스를 싱글턴으로 관리하도록 지정합니다. Guice 인젝터가 단일 인스턴스를 재사용합니다.
  • 예시:
import com.google.inject.Singleton;
import com.google.inject.Inject;

@Singleton
class Cache {
    public void store(String key, String value) {
        System.out.println("캐시에 저장: " + key + " -> " + value);
    }
}

class App {
    @Inject
    private Cache cache;

    public void run() {
        cache.store("user", "data");  // 동일한 인스턴스 사용
    }
}
  • 특징: 메모리 효율성을 높이지만, 상태를 공유하므로 주의가 필요합니다.

4. @Provides

  • 설명: 모듈 내에서 복잡한 객체 생성 로직을 정의할 때 사용됩니다.
  • 예시:
import com.google.inject.Provides;
import com.google.inject.AbstractModule;

interface Client {
    void send(String msg);
}

class HttpClient implements Client {
    private final String url;
    public HttpClient(String url) { this.url = url; }
    public void send(String msg) { System.out.println("Sending to " + url + ": " + msg); }
}

class MyModule extends AbstractModule {
    @Provides
    public Client provideClient() {
        return new HttpClient("https://api.example.com");
    }
}

class App {
    @Inject
    private Client client;

    public void run() {
        client.send("Hello");  // 출력: Sending to https://api.example.com: Hello
    }
}
  • 특징: 동적 생성 로직을 커스터마이징할 수 있어 유연성이 높습니다.

5. @ImplementedBy

  • 설명: 인터페이스의 기본 구현체를 지정하여 모듈 설정 없이도 의존성을 주입할 수 있습니다.
  • 예시:
import com.google.inject.ImplementedBy;
import com.google.inject.Inject;

@ImplementedBy(DefaultService.class)
interface Service {
    void execute();
}

class DefaultService implements Service {
    public void execute() { System.out.println("기본 서비스 실행"); }
}

class App {
    @Inject
    private Service service;

    public void run() {
        service.execute();  // 출력: 기본 서비스 실행
    }
}
  • 특징: 모듈 설정이 필요 없어 간단하지만, 유연성이 제한적입니다.

6. @ProvidedBy

  • 설명: Provider 클래스를 통해 동적으로 의존성을 제공합니다.
  • 예시:
import com.google.inject.ProvidedBy;
import com.google.inject.Provider;
import com.google.inject.Inject;

@ProvidedBy(DynamicServiceProvider.class)
interface Service {
    void execute();
}

class DynamicService implements Service {
    public void execute() { System.out.println("동적 서비스 실행"); }
}

class DynamicServiceProvider implements Provider<Service> {
    @Override
    public Service get() {
        return new DynamicService();
    }
}

class App {
    @Inject
    private Service service;

    public void run() {
        service.execute();  // 출력: 동적 서비스 실행
    }
}
  • 특징: Provider를 통해 런타임에 객체를 생성할 수 있어 동적 처리가 가능합니다.

7. @Qualifier

  • 설명: 사용자 정의 어노테이션을 만들어 더 세밀한 의존성 구분을 가능하게 합니다.
  • 예시:
import com.google.inject.Qualifier;
import com.google.inject.Inject;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@interface PrimaryDB {}

class MyModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(Database.class).annotatedWith(PrimaryDB.class).to(MySQLDatabase.class);
    }
}

class App {
    @Inject @PrimaryDB
    private Database database;

    public void run() {
        database.connect();  // 출력: MySQL 연결
    }
}
  • 특징: @Named보다 타입 안전성이 높고, 오타 문제를 방지할 수 있습니다.

Guice 어노테이션 사용 팁

  • 생성자 주입 우선: @Inject를 생성자에 사용해 불변성과 명확성을 유지하세요.
  • Qualifier 활용: @Named 대신 사용자 정의 @Qualifier를 사용하면 더 안전합니다.
  • 싱글턴 주의: @Singleton은 상태를 공유하므로 스레드 안전성을 고려해야 합니다.
  • 모듈 분리: 복잡한 프로젝트에서는 @Provides를 활용해 모듈을 깔끔하게 관리하세요.

결론

Guice의 어노테이션들은 의존성 주입을 간단하고 유연하게 만들어줍니다. 위 예시를 통해 각 어노테이션의 사용법과 장점을 이해할 수 있으며, 프로젝트 요구사항에 맞게 적절히 선택해 사용하면 됩니다. 추가 질문이 있다면 언제든 물어보세요!