2010-05-18 3 views
11

일부 개체에 주입해야하는 필드가있는 기존 개체 계층 구조가 있습니다. 또한 Google Guice을 사용하여 생성되고 앞서 설명한 개체 계층 구조의 일부 개체에 대한 참조를 주입해야하는 다른 개체가 있습니다. Guice와 같은 종류의 주사를 어떻게해야합니까?Guice를 사용하여 @ 기존 객체 계층에 삽입하는 방법은 무엇입니까?

기존 계층의 개체는 Guice를 사용하여 생성되지 않으므로 기본적으로 주입 프로세스가 적용되지 않습니다. 물론 injector.injectMembers() 메서드는 기존 개체 인스턴스에 삽입 할 수 있지만 개체 계층 구조에서는 작동하지 않습니다.

Guice를 사용하여 언급 된 개체 계층 구조를 작성할 수없는 이유가 궁금한 사람들에게. 이 계층 구조는 GUI 객체를 나타내며 선언적 GUI 설명에서 GUI 프레임 워크 (Apache Pivot)로 작성됩니다 (사실이 프로세스는 객체 직렬화로 설명 될 수 있음). 그런 식의 인터페이스 구축은 다소 단순하며, 특정 서비스 레퍼런스를 인터페이스 객체에 주입하거나 콜백 (callback)을 위해 역 참조하기 만하면됩니다.

접근 방식 현재 취하려고하고 있습니다. 아래에서 설명합니다.

단지 같은 특정 인터페이스를 구현 주입에 관심이있는 모든 개체를 수 있도록 기존의 오브젝트 계층 구조로 주입하기위한

: 다음

public void injectAll(Injector injector) { 
    injector.injectMembers(this); 
    for (Injectable child : children) 
    child.injectAll(injector); 
} 

: 다음과 같이이 인터페이스를 구현하는 것이

public interface Injectable { 
    void injectAll(Injector injector); 
} 

그 객체를 나는 계층 구조의 루트 객체에 대해 mainWindow.injectAll(injector)을 호출하고 관심있는 모든 객체가 주입됩니다.

별로 좋은 솔루션은 아니지만 한쪽에서만 작업이 완료됩니다. 다른면에서이 계층 구조의 객체를 주입해야합니다. 그런 개체에 대한 사용자 지정 공급자를 구현하여이 작업을 수행 할 수 있다고 생각합니다.

내 문제는 더 나은 해결책이 있습니까? 어쩌면 내 접근 방식에도 문제가있을 수 있습니까?

답변

12

이 솔루션을 사용할 수는 있지만 약간 다른 제안을하고 싶습니다.

특히 깊은 개체 구조를 트래버스하기 때문에 Visitor 패턴의 작업과 비슷합니다. 또한 설명하는 내용은 2 단계 인젝터를 호출하는 것으로 보입니다. 피벗로 만든 계층 구조에서 필요한 요소를 주입 할 수있는 "부트 스트랩"단계 (그러나 피벗 생성 요소는 주입 할 수 없음)와 두 번째 단계 그것은 앱에서 사용하는 실제 인젝터입니다 (아무 것도 주입 할 수 있음).

나는이 기본 패턴을 제안합니다. 방문객이 계층 구조를 가로 지르는 방문자를 만들어야합니다. 필요로하는 것들을 주입하고 다른 곳에 주입해야하는 것들을 기록합니다. 그런 다음 모든 것이 완료되면 Injector.createChildInjector을 사용하여 원래 Injector의 항목을 주입 할 수있는 새로운 Injector을 만들고 피벗 생성 계층 구조의 항목을 만듭니다.

public interface InjectionVisitor { 
    void needsInjection(Object obj); 
    <T> void makeInjectable(Key<T> key, T instance); 
} 

그런 다음 모든 피벗 만든 요소에 대한 인터페이스를 정의 :

먼저이 계층 구조에 모든 것을 칠 수 방문자 정의 당신은이 인터페이스를 구현하는 것

public interface InjectionVisitable { 
    void acceptInjectionVisitor(InjectionVisitor visitor); 
} 

당신의 피벗 생성 클래스 (FooContainer 클래스의이 코드로 가정) :

public void acceptInjectionVisitor(InjectionVisitor visitor) { 
    visitor.needsInjection(this); 
    visitor.makeInjectable(Key.get(FooContainer.class), this); 
    for (InjectionVisitable child : children) { 
    child.acceptInjectionVisitor(visitor); 
    } 
} 

처음 두 문장은 선택 사항입니다. 피벗 계층 구조의 일부 객체는 주입이 필요하지 않을 수도 있으며 나중에 주입 가능성을 원하지 않을 수도 있습니다.

이제
visitor.makeInjectable(Key.get(Foo.class, Names.named(this.getName())), this); 

, 어떻게 InjectionVisitor을 구현합니까 : - 또한, Key의 사용에 주목이 당신이 원하는 경우 일부 클래스는 당신이 뭔가를 할 수있는 특정 주석을 주사 할 것을 의미?

public class InjectionVisitorImpl implements InjectionVisitor { 
    private static class BindRecord<T> { 
    Key<T> key; 
    T value; 
    } 

    private final List<BindRecord<?>> bindings = new ArrayList<BindRecord<?>>(); 
    private final Injector injector; 

    public InjectionVisitorImpl(Injector injector) { 
    this.injector = injector; 
    } 

    public void needsInjection(Object obj) { 
    injector.injectMemebers(obj); 
    } 

    public <T> void makeInjectable(Key<T> key, T instance) { 
    BindRecord<T> record = new BindRecord<T>(); 
    record.key = key; 
    record.value = instance; 
    bindings.add(record); 
    } 

    public Injector createFullInjector(final Module otherModules...) { 
    return injector.createChildInjector(new AbstractModule() { 
     protected void configure() { 
     for (Module m : otherModules) { install(m); } 
     for (BindRecord<?> record : bindings) { handleBinding(record); } 
     } 
     private <T> handleBinding(BindRecord<T> record) { 
     bind(record.key).toInstance(record.value); 
     } 
    }); 
    } 
} 

당신은 다음과 같이 main 방법이 사용 방법은 다음과 같습니다

PivotHierarchyTopElement top = ...; // whatever you need to do to make that 
Injector firstStageInjector = Guice.createInjector(
    // here put all the modules needed to define bindings for stuff injected into the 
    // pivot hierarchy. However, don't put anything for stuff that needs pivot 
    // created things injected into it. 
); 
InjectionVisitorImpl visitor = new InjectionVisitorImpl(firstStageInjector); 
top.acceptInjectionVisitor(visitor); 
Injector fullInjector = visitor.createFullInjector(
    // here put all your other modules, including stuff that needs pivot-created things 
    // injected into it. 
); 
RealMainClass realMain = fullInjector.getInstance(RealMainClass.class); 
realMain.doWhatever(); 

createChildInjector 작품은 당신이 물건에 바인딩 어떤 @Singleton 일이있는 경우 피벗 계층 구조에 주입되도록하는 방식이 , 실제 인젝터에서 동일한 인스턴스를 주입하게됩니다. firstStageInjector이 주사를 처리 할 수있는 한 fullInjectorfirstStageInjector에 인젝션을 위임합니다.

추가 편집 : InjectionImpl을 수정하여 makeInjectable이라는 소스 코드의 위치를 ​​기록하도록 수정하는 것이 좋습니다 (Guice의 깊은 마법을 탐구하려는 경우). 그러면 Guice에서 실수로 코드가 방문자에게 동일한 키에 바인딩 된 두 가지 점에 대해 알릴 때 더 나은 오류 메시지를 얻을 수 있습니다. 당신은 중첩 된 필드를 주입하는 MembersInjectors를 주입 할 수

private <T> handleBinding(BindRecord<T> record) { 
    binder().withSource(record.stackTraceElem).bind(record.key).toInstance(record.value); 
} 
+0

와우! 이것은 매우 철저하고 정교한 대답입니다. 나는 그것을 많이 고맙다. 그리고 Guice 마법을 공유 주셔서 감사합니다 :) 나는 제안 된 접근 방식을 시도합니다. (답변은 내일 허용됩니다.) – dragonfly

0

이렇게하려면, 당신은 StackTraceElementBindRecord에 추가하는 방법 makeInjectable 내부 new RuntimeException().getStackTrace()[1]의 결과를 기록하고 handleBinding에 다음 변경하려는 것입니다. 예를 들어, 기존 자동차 인스턴스를 심층적으로 삽입합니다.

public class Car { 
    Radio radio; 
    List<Seat> seats; 
    Engine engine; 

    public Car(...) {...} 

    @Inject void inject(RadioStation radioStation, 
     MembersInjector<Seat> seatInjector, 
     MembersInjector<Engine> engineInjector) { 
    this.radio.setStation(radioStation); 
    for (Seat seat : seats) { 
     seatInjector.injectMembers(seat); 
    } 
    engineInjector.injectMembers(engine); 
    } 
} 

public class Engine { 
    SparkPlug sparkPlug; 
    Turbo turbo 

    public Engine(...) {...} 

    @Inject void inject(SparkPlug sparkplug, 
     MembersInjector<Turbo> turboInjector) { 
    this.sparkPlug = sparkPlug; 
    turboInjector.injectMembers(turbo); 
    } 
} 
+1

이 솔루션의 문제점은 계층 구조의 모든 개체가 Guice를 사용하여 생성된다는 것을 의미합니다. 그렇지 않습니다. 또한 Gu 인스턴스를 사용하여 Car 인스턴스를 주입하면 엔진 인스턴스가 정상적으로 주입되므로'MembersInjector '을 사용하면 과도한 것으로 판단됩니다. 또한 객체 트리를 통해 'Injector'(또는 그 문제에 대해서는'MembersInjector')를 전달하는 것은 일반적인 사용 사례가 아닙니다. – dragonfly

관련 문제