2015-02-26 11 views
5

Tôi mới sử dụng Google Guice và hiểu Dependency Injection một cách khái niệm, nhưng tôi đang gặp phải các vấn đề đang cố gắng kết hợp nó vào ứng dụng của mình. Câu hỏi cụ thể của tôi là về các vật thể Singleton. Dưới đây là ví dụ:Guice Singleton Static Injection Pattern

Đầu tiên, lớp Mô-đun của tôi, liên kết với giao diện Singleton Connection nặng để triển khai.

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

Bây giờ, trong phương pháp chính của tôi, tôi nhanh chóng máy chủ ứng dụng của tôi và tiêm 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... 
    } 
} 

Tất cả những gì tốt cho đến nay ... Bây giờ, tôi có một số lớp DAO mà tận dụng đối tượng Connection của tôi , nhưng được lấy ra bằng các phương pháp tĩnh như vậy:

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)); 
    } 
} 

giả định đầu tiên của tôi là tôi chỉ đơn giản là sẽ thêm @Inject để cxn nhưng điều này không w ork bởi vì tôi không nhận được ví dụ từ Guice; nó chỉ cho tôi một NPE. Con đường tôi nhìn thấy nó, tôi có 2 lựa chọn để khai thác đối tượng Connection:

  1. Expose một phương pháp getConnection() trong MyApplication cơ bản sau Service Locator Pattern
  2. Thêm requestStaticInjection(MyConfiguration) để MyModule

tôi chọn cho # 2 , tuy nhiên, docs say:

API này không được khuyến nghị sử dụng chung

Thực hành tốt nhất để cung cấp Singleton cho các lớp học cần có mà không cần phải trải qua Injector.getInstance mỗi lần? Tôi đang thiếu gì?

+1

Bạn nói điều đó cho mình, thay đổi phương thức để có không tĩnh;) – alfasin

+0

Tôi đồng ý với @alfasin lý do bạn mất một trong những lý do chính để làm DI nếu bạn có các cuộc gọi tĩnh mà không thể được tiêm khác nhau. Tại sao bạn không sử dụng một mẫu đơn DI không cho MyConfiguration? – JasonOfEarth

+0

Ví dụ của tôi có phần bị giải thích. Trong ứng dụng của tôi, "Cấu hình" thực sự là một kết nối cơ sở dữ liệu đồ thị. Cxn.getConfig thực sự là một quá trình truyền tải đồ thị tìm thấy một nút có tên đã cho. Đối tượng MyConfiguration là một trình bao bọc xung quanh nút này cung cấp thông tin tên miền cụ thể. Tôi dự định sử dụng nó theo cách thông thạo: MyConfiguration.getConfig ("x"). GetName(), v.v. Hãy nói rằng tôi đã loại bỏ đối tượng tĩnh instantiation, tôi sẽ phải sử dụng Injector.getInstance trên tất cả các mã của tôi bất cứ khi nào tôi cần một ? Làm thế nào để xử lý các tham số constructor mà không nên được tiêm? – lamarvannoy

Trả lời

11

Bạn đang suy nghĩ về việc tiêm phụ thuộc không chính xác. Dependency Injection và Service Locator là các hình ảnh phản chiếu của nhau: với một bộ định vị dịch vụ, bạn hỏi nó cho một đối tượng. Với việc tiêm phụ thuộc, bạn không tìm kiếm các phụ thuộc, chúng chỉ được trao cho bạn.

Về cơ bản, "rùa là tất cả các con đường xuống"! Mỗi phụ thuộc vào lớp học của bạn nên được tiêm. Nếu MyApplication cần một đối tượng MyConfiguration, nó chỉ cần chấp nhận một đối tượng MyConfiguration làm tham số hàm tạo và không lo lắng về cách nó được xây dựng.

Bây giờ, điều này không có nghĩa là bạn không bao giờ có thể sử dụng new theo cách thủ công - nhưng bạn nên đặt trước cho các đối tượng kiểu giá trị không có phụ thuộc bên ngoài. (Và trong những trường hợp đó, tôi cho rằng bạn thường tốt hơn với phương pháp nhà máy tĩnh so với một nhà xây dựng công cộng, nhưng đó là bên cạnh vấn đề.)

Bây giờ có một vài cách để thực hiện việc này. Một cách là phân mảnh MyConfiguration thành nhiều mảnh nhỏ, để thay vì làm myConfiguration.getConfig("x") bạn sẽ làm @Inject @Configuration("x") String hoặc một cái gì đó tương tự. Ngoài ra, bạn có thể tự tạo MyConfiguration và sau đó cung cấp các phương thức truy cập trên đó cho các phần.Câu trả lời đúng tùy thuộc phần nào vào loại dữ liệu bạn đang cố gắng lập mô hình - làm cho các phụ thuộc quá chi tiết và các ràng buộc của bạn có thể trở nên khó duy trì (mặc dù có nhiều cách để làm tốt hơn); làm cho các phụ thuộc quá thô và bạn khó kiểm tra hơn (ví dụ: dễ hơn, chỉ cung cấp cấu hình "x" mà lớp bạn đang kiểm tra cần, hoặc xây dựng cấu hình của toàn bộ ứng dụng?).

Bạn thậm chí có thể làm cả hai:

/** 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; 
    } 

    // ... 
} 

Bạn có thể tham gia một cách tiếp cận tương tự trong các thử nghiệm đơn vị:

/** 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 
    } 
} 
tiêm

phụ thuộc cũng khuyến khích khác thực hành tốt nhất mà tôi khuyên bạn nên, đó là thiết kế hệ thống kiểu của bạn sao cho các trạng thái không hợp lệ không thể đại diện (đến mức tối đa có thể). Nếu tất cả các nhu cầu cấu hình MyApplication được truyền trong hàm dựng của nó, thì không thể có một đối tượng MyApplication không có cấu hình hợp lệ. Điều này cho phép bạn "tải trước" các biến thể lớp của bạn, điều này khiến cho việc xử lý các đối tượng của bạn dễ dàng hơn nhiều.

Cuối cùng, ghi chú về Injector.getInstance(). Lý tưởng nhất là bạn sử dụng Injector chính xác một lần trong chương trình của bạn: ngay lập tức sau khi nó được xây dựng. Tức là, bạn sẽ có thể thực hiện Guice.createInjector(...).getInstance(MyApplication.class).start() và không bao giờ lưu trữ một tham chiếu đến Injector ở bất kỳ đâu. Tôi có xu hướng để xây dựng các ứng dụng sử dụng ServiceManager trừu tượng Ổi (xem cũng this question), do đó, điều duy nhất tôi bao giờ cần phải làm là:

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