Avoiding wireframe related circular dependencies in Guice

Note: . Although this question specifically calls Swing, I believe this question is pure Guice ( 4.0 ), and it highlights the potential common problem with Guice and other strong frameworks.


In Swing, you have an application user interface that extends JFrame:

// Pseudo-code
class MyApp extends JFrame {
    // ...
}

Your application ( JFrame) needs a menu:

// Pseudo-code
JMenuBar menuBar = new JMenuBar()

JMenu fileMenu = new JMenu('File')

JMenu manageMenu = new JMenu('Manage')
JMenuItem widgetsSubmenu = new JMenuItem('Widgets')
manageMenu.add(widgetsSubmenu)

menuBar.add(fileMenu)
menuBar.add(manageMenu)

return menuBar

And your menu items need action listeners, many of which will update your application contentPane:

widgetsSubmenu.addActionListener(new ActionListener() {
    @Override
    void actionPerformed(ActionEvent actionEvent) {
        // Remove all the elements from the main contentPane
        yourApp.contentPane.removeAll()

        // Now add a new panel to the contentPane
        yourApp.contentPane.add(someNewPanel)
    }
})

And therefore, there is an internal cyclic relationship:

  • Your application / JFrameneeds an instanceJMenuBar
  • For your JMenuBarneed 0+ JMenusandJMenuItems
  • JMenuItems (, , JFrame contentPane), .
  • , JFrame contentPane, JFrame

:

// Pseudo-code
class MyModule extends AbstractModule {
    @Override
    void configure() {
        // ...
    }

    @Provides
    MyApp providesMyApp(JMenuBar menuBar) {
        // Remember MyApp extends JFrame...
        return new MyApp(menuBar, ...)
    }

    @Provides
    JMenuBar providesMenuBar(@Named('widgetsListener') ActionListener widgetsMenuActionListener) {
        JMenuBar menuBar = new JMenuBar()

        JMenu fileMenu = new JMenu('File')

        JMenu manageMenu = new JMenu('Manage')
        JMenuItem widgetsSubmenu = new JMenuItem('Widgets')
        widgetsSubmenu.addActionListener(widgetsMenuActionListener)
        manageMenu.add(widgetsSubmenu)

        menuBar.add(fileMenu)
        menuBar.add(manageMenu)

        return menuBar
    }

    @Provides
    @Named('widgetsListener')
    ActionListener providesWidgetsActionListener(Myapp myApp, @Named('widgetsPanel') JPanel widgetsPanel) {
        new ActionListener() {
            @Override
            void actionPerformed(ActionEvent actionEvent) {
                // Here is the circular dependency. MyApp needs an instance of this listener to
                // to be instantiated, but this listener depends on a MyApp instance in order to
                // properly update the main content pane...
                myApp.contentPane.removeAll()
                myApp.contentPane.add(widgetsPanel)
            }
        }
    }
}

, :

Exception in thread "AWT-EventDispatcher" com.google.inject.ProvisionException: Unable to provision, see the following errors:

1) Tried proxying com.me.myapp.MyApp to support a circular dependency, but it is not an interface.
    while locating com.me.myapp.MyApp

, : , ? Guice API ? , ? ?


Update

guice-swing-example GitHub SSCCE.

+4
3

, , , Guice docs . , Guice Guice : API .

, , , . , MyAppInterface, , (getContentPane() , ) MyApp . MyAppInterface MyApp ( providesMyApp ), MyAppInterface . MyApp MyAppInterface, .

Guice . , MyAppInterface , -, , , MyApp, MyApp , Guice, .

MyAppInterface , , MyApp MyApp MyAppImpl.

, Guice , - - , , .

: Groovy Gradle, SSCCE , , . DefaultFizzClient @Provides @Singleton menuBar provideExampleApp, , .

@Singleton: @Singleton, . , . @Singleton, Guice , . . , " X" " X". , , , - , ..

menuBar: , . . , , addMenuToFrame requestInjection(this).

, , Guice, :

  • ExampleAppModule, configure(). Guice, , , ExampleAppModule , @Inject .
  • configure() , , Guice requestInjection(this). , addMenuToFrame @Inject. , , ExampleApp JMenuBar.
  • Guice ExampleApp provideExampleApp. Guice , FizzClient.
  • Guice FizzClient DefaultFizzClient. DefaultFizzClient no-args , Guice , .
  • FizzClient, Guice provideExampleApp. , ExampleApp.
  • addMenuToFrame - JMenuBar, providesMenuBar. Guice , ActionListener "widgetsMenuActionListener".
  • Guice ActionListener "widgetsMenuActionListener" providesWidgetsMenuActionListener. Guice , ExampleApp JPanel "widgetsPanel". ExampleApp, 5, -, @Singleton, , provideExampleApp .
  • Guice JPanel "widgetsPanel" providesWidgetPanel. , Guice JPanel, .
  • JPanel ExampleApp, Guice providesWidgetsMenuActionListener, ActionListener.
  • ActionListener , Guice providesMenuBar, JMenuBar.
  • , , ExampleApp JMenuBar, Guice addMenuToFrame, .
  • , , getInstance(ExampleApp) main . Guice , ExampleApp , @Singleton, .

, , @Singleton provideExampleApp, , .

+2

, , , .

, . , , JMenuBar JFrame:

class MyModule extends AbstractModule {
  @Override void configure() {
    requestInjection(this);
  }

  @Inject void addMenuToFrame(JFrame frame, JMenuBar menu) {
    frame.setMenuBar(menu);
  }
}

, , ( ), , , , , - .


, - , , , , .


, , , JFrame JMenuBar , , , JFrame JMenuBar (, ).

+1

I answer this, not being able to check the answer to your code, because I do not know (and I do not want to learn) Groovy.

But basically, when you have circular dependencies, the best way to break it is to use Provider.

package so36042838;
import com.google.common.base.*;
import com.google.inject.Guice;
import javax.inject.*;
public class Question {

  @Singleton public final static class A {
    B b;
    @Inject A(B b) { this.b = b; }
    void use() { System.out.println("Going through"); }
  }

  @Singleton public final static class B {
    A a;
    @Inject B(A a) { this.a = a; }
    void makeUseOfA() { a.use(); }
  }

  public static void main(String[] args) {
    Guice.createInjector().getInstance(B.class).makeUseOfA(); // Produces a circular dependency error.
  }
}

This can then be rewritten as follows:

package so36042838;
import com.google.common.base.*;
import com.google.inject.Guice;
import javax.inject.*;
public class Question {

  @Singleton public final static class A {
    B b;
    @Inject A(B b) { this.b = b; }
    void use() { System.out.println("Going through"); }
  }

  @Singleton public final static class B {
    Supplier<A> a;
    @Inject B(Provider<A> a) { this.a = Suppliers.memoize(a::get); } // prints "Going through" as expected
    void makeUseOfA() { a.get().use(); }
  }

  public static void main(String[] args) {
    Guice.createInjector().getInstance(B.class).makeUseOfA();
  }
}
0
source

All Articles