2015-02-26 3 views
5

Я новичок в Google Guice и понимаю концепцию Dependency Injection концептуально, но я сталкиваюсь с проблемами, пытаясь включить его в мое приложение. Мой конкретный вопрос касается объектов Singleton. Вот пример:Guice Singleton Static Injection Pattern

Во-первых, мой класс модуля, который связывает тяжелый интерфейс Singleton Connection с его реализацией.

public class MyModule extends AbstractModule { 
    @Override 
    protected void configure() { 
     bind(Connection.class).to(MyConnection.class).asEagerSingleton(); 
    } 
} 

Сейчас, на мой основной метод, я создаю экземпляр моего сервера приложений и впрыснуть Connection:

public class MyApplication { 
    @Inject 
    public MyApplication(Connection cxn) { 

    } 

    public static void main(String[] args) { 
     Injector injector = Guice.createInjector(new MyModule()); 
     MyApplication app = injector.getInstance(MyApplication.class); 
     // Start application, add ShutdownHook, etc... 
    } 
} 

Все, что хорошо до сих пор ... Теперь у меня есть некоторые классы DAO, которые используют мой объект Connection , но извлекаются со статическими методами следующим образом:

public class MyConfiguration { 
    private Config conf; 
    private Connection cxn; // Would like to have this injected 

    private MyConfiguration(Config conf) { 
     this.conf = conf; 
    } 

    public static MyConfiguration getConfig(String name) { 
     return new MyConfiguration(cxn.getConfig(name)); 
    } 
} 

Моего первым предположением было то, что я бы просто добавить @Inject к cxn, но это не ж ork, потому что я не получаю экземпляр от Guice; это просто дает мне NPE. Как я понимаю, у меня есть 2 варианта для получения объекта Connection:

  1. Expose метод getConnection() в MyApplication по существу после Service Locator Pattern
  2. Добавить requestStaticInjection(MyConfiguration) в MyModule

Я выбрал # 2 , однако docs say:

Этот API не рекомендуется для общего использования

Какова наилучшая практика для предоставления моего Singleton классам, которые в нем нуждаются, без необходимости проходить через Injector.getInstance каждый раз? Что мне не хватает?

+1

Вы сказали это сам, изменить способ быть нестатическими;) – alfasin

+0

Я согласен с @alfasin причина, по которой вы теряете одну из основных причин делать DI, если у вас есть статические вызовы, которые нельзя вводить по-разному. Почему вы не использовали одиночный шаблон non DI для MyConfiguration? – JasonOfEarth

+0

Мой пример несколько надуман для объяснения. В моем приложении «Конфигурация» на самом деле является соединением базы данных Graph. Cxn.getConfig - это фактически обход графика, который находит узел с заданным именем. Объект MyConfiguration является оберткой вокруг этого узла, предоставляющей информацию о конкретном домене. Я планировал использовать его в бегстве: MyConfiguration.getConfig («x»). GetName() и т. Д. Предположим, я удалил экземпляр статического объекта, мне пришлось бы использовать Injector.getInstance по всему моему коду всякий раз, когда мне нужно было ? Как обрабатывать параметры конструктора, которые нельзя вводить? – lamarvannoy

ответ

11

Вы неправильно думаете об инъекции зависимостей. Инъекция зависимостей и локатор обслуживания являются зеркальными изображениями друг друга: с помощью локатора службы, вы запрашиваете у него для объекта. При инъекции зависимостей вы не ищите зависимостей, они просто переданы вам.

В принципе, «это черепахи полностью вниз»! Каждой зависимости вашего класса следует вводить. Если MyApplication нуждается в объекте MyConfiguration, он должен просто принять объект MyConfiguration в качестве параметра конструктора и не беспокоиться о том, как он был создан.

Теперь это не означает, что вы никогда не сможете использовать new вручную, но вы должны зарезервировать это для объектов типа значения, которые не имеют внешних зависимостей. (И в этих случаях я бы сказал, что вам часто лучше со статическим заводским методом, чем публичный конструктор, но это не относится к делу.)

Теперь есть несколько способов сделать это. Один из способов - обмануть MyConfiguration множеством крошечных предметов, так что вместо myConfiguration.getConfig("x") вы бы сделали @Inject @Configuration("x") String или что-то в этом роде. Альтернативно, вы можете сделать MyConfiguration сам инъекционным, а затем предоставить на нем методы доступа.Правильный ответ в некоторой степени зависит от типа данных, которые вы пытаетесь моделировать, - слишком зависимыми от зависимостей, и ваши привязки могут быть трудно поддерживать (хотя есть способы сделать это лучше); сделать зависимости слишком грубыми, и вам становится труднее протестировать (например: что проще, предоставляя только конфигурацию «x», которую требует тестируемый класс, или создавая конфигурацию всего приложения?).

Вы можете даже сделать так:

/** Annotates a configuration value. */ 
@BindingAnnotation 
@Retention(RetentionPolicy.RUNTIME) 
public @interface Config { 
    String value(); 
} 

/** Installs bindings for {@link MyConfiguration}. */ 
final class MyConfigurationModule extends AbstractModule { 
    @Override protected void configure() {} 

    @Provides 
    @Singleton 
    MyConfiguration provideMyConfiguration() { 
    // read MyConfiguration from disk or somewhere 
    } 

    @Provides 
    @Config("x") 
    String provideX(MyConfiguration config) { 
    return config.getConfig("x").getName(); 
    } 
} 

// elsewhere: 

/** The main application. */ 
final class MyApplication { 
    private final String xConfig; 

    @Inject MyApplication(@Config("x") String xConfig) { 
    this.xConfig = xConfig; 
    } 

    // ... 
} 

Вы можете принять подобный подход в модульных тестах:

/** Tests for {@link MyApplication}. */ 
@RunWith(JUnit4.class) 
public final class MyApplicationTest { 
    // Note that we don't need to construct a full MyConfiguration object here 
    // since we're providing our own binding, not using MyConfigurationModule. 
    // Instead, we just bind the pieces that we need for this test. 
    @Bind @Config("x") String xConfig = "x-configuration-for-test"; 

    @Before public void setUp() { 
    // See https://github.com/google/guice/wiki/BoundFields 
    Guice.createInjector(BoundFieldModule.of(this)).injectMembers(this); 
    } 

    @Inject MyApplication app; 

    @Test public void testMyApp() { 
    // test app here 
    } 
} 

Dependency инъекции также способствует другую наилучшей практике, которую я настоятельно рекомендую, которая должна разработать ваша система типов, так что недопустимые состояния не могут быть представлены (в максимально возможной степени). Если в его конструкторе передается вся конфигурация MyApplication, невозможно иметь объект MyApplication, который не имеет правильной конфигурации. Это позволяет вам «загружать» ваши инварианты класса, что значительно облегчает рассуждение о поведении ваших объектов.

И наконец, примечание о Injector.getInstance(). В идеале вы используете Injector ровно один раз в своей программе: сразу после его создания. То есть вы должны иметь возможность делать Guice.createInjector(...).getInstance(MyApplication.class).start() и никогда не хранить ссылку на Injector в любом месте. Я склонен создавать приложения с использованием ServiceManager абстракции гуавы (см также this question), поэтому единственным, что я когда-либо нужно сделать, это:

public static void main(String[] args) throws Exception { 
    Injector injector = Guice.createInjector(...); 
    ServiceManager manager = injector.getInstance(ServiceManager.class); 
    manager.startAsync().awaitHealthy(); 
}