Please note: Although this question specifically calls out Swing, I believe this to be a pure Guice (4.0) question at heart and which highlights a potential generic issue with Guice and other opinionated frameworks.
In Swing, you have your application UI, which extends JFrame
:
// Pseudo-code
class MyApp extends JFrame {
// ...
}
Your app (JFrame
) needs a menubar:
// 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 updated your app's 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 so there is intrinsically a circular dependency:
JFrame
needs a JMenuBar
instanceJMenuBar
needs 0+ JMenus
and JMenuItems
JMenuItems
(well, the ones that will update your JFrame
's contentPane
') need action listenersJFrame
's contentPane
, these action listeners need to reference your JFrame
Take the following module:
// 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)
}
}
}
}
This will produce circular dependency errors at runtime, such as:
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
So I ask: what's the way of circumventing this? Does Guice have an API or extension library for dealing with this sort of problem? Is there a way to refactor the code to break the circular dependency? Some other solution?
Please see my guice-swing-example project on GitHub for a SSCCE.
But circular dependencies in software are solvable because the dependencies are always self-imposed by the developers. To break the circle, all you have to do is break one of the links. One option might simply be to come up with another way to produce one of the dependencies, in order to bootstrap the process.
Circular dependency in Spring happens when two or more beans require instance of each other through constructor dependency injections. For example: There is a ClassA that requires an instance of ClassB through constructor injection and ClassB requires an instance of class A through constructor injection.
A cyclic dependency exists when a dependency of a service directly or indirectly depends on the service itself. For example, if UserService depends on EmployeeService , which also depends on UserService . Angular will have to instantiate EmployeeService to create UserService , which depends on UserService , itself.
Using GuiceIn each of your constructors that need to have something injected in them, you just add an @Inject annotation and that tells Guice to do it's thing. Guice figures out how to give you an Emailer based on the type. If it's a simple object, it'll instantiate it and pass it in.
There are several techniques available to refactor a circular dependency so that it is no longer circular, and Guice docs recommend doing that whenever possible. For when that is not possible, the error message Guice provided hints at the Guice way to resolve this in general: separate your API from your implementation by using an interface.
Strictly speaking it is only necessary to do this for one class in the circle, but it may improve modularity and ease of writing test code to do it for most or every class. Create an interface, MyAppInterface
, declare in it every method that your action listeners need to call directly (getContentPane()
seems to be the only one in the code you posted), and have MyApp
implement it. Then bind MyAppInterface
to MyApp
in your module configuration (in your case simply changing the return type of providesMyApp
should do it), declare the action listener provider to take a MyAppInterface
, and run it. You might also need to switch some other places in your code from MyApp
to MyAppInterface
, depends on the details.
Doing this will allow Guice itself to break the circular dependency for you. When it sees the circular dependency, it will generate a new zero-dependency implementing class of MyAppInterface
that acts as a proxy wrapper, pass an instance of that into the action listener provider, fill out the dependency chain from there until it can make a real MyApp
object, and then it will stick the MyApp
object inside Guice's generated object which will forward all method calls to it.
Though I used MyAppInterface
above, a more common naming pattern would be to actually use the MyApp
name for the interface and rename the existing MyApp
class to MyAppImpl
.
Note that Guice's method of automatic circular dependency breaking requires that you not call any methods on the generated proxy object until after initialization is complete, because it won't have a wrapped object to forward them to until then.
Edit: I don't have Groovy or Gradle ready to try to run your SSCCE for testing, but I think you are very close to breaking the circle already. Annotate the DefaultFizzClient
class and each of your @Provides
methods with @Singleton
, and remove the menuBar
parameter from provideExampleApp
, and I think that should make it work.
@Singleton
: A class or provider method should be marked @Singleton
when only a single instance of it should be made. This is important when it is injected in multiple different places or requested multiple times. With @Singleton
, Guice makes one instance, saves a reference to it, and uses that one instance every time. Without, it makes a new separate instance for each reference. Conceptually, it's a matter of whether you are defining "how to make an X" or "this here is the X". It seems to me that the UI elements you're creating fall in the latter category - the singular menu bar for your app, not any arbitrary menu bar, etc.
Removing menuBar
: You've already commented out the one and only line in that method that uses it. Simply delete the parameter from the method declaration. As for how to get the menu bar into the app anyway, that is already handled by the addMenuToFrame
method combined with the requestInjection(this)
call.
Perhaps it might help to do a run through of the logic Guice will go through if these alterations are made:
ExampleAppModule
, it calls your module's configure()
method. This sets up some bindings and tells Guice that, whenever it's done with all the bindings setup, it should scan the ExampleAppModule
instance for fields and methods annotated with @Inject
and fill them in.configure()
returns and, with bindings setup complete, Guice honors the requestInjection(this)
call. It scans and finds that addMenuToFrame
is annotated with @Inject
. Inspecting that method, Guice finds that an ExampleApp
instance and a JMenuBar
instance are needed in order to call it.ExampleApp
instance and finds the provideExampleApp
method. Guice examines that method and finds that a FizzClient
instance is needed to call it.FizzClient
instance and finds the class binding to DefaultFizzClient
. DefaultFizzClient
has a default no-args constructor, so Guice just calls that to get an instance.FizzClient
instance, Guice is now able to call provideExampleApp
. It does so, thereby acquiring an ExampleApp
instance.JMenuBar
in order to call addMenuToFrame
, so it looks for a way to make one and finds the providesMenuBar
method. Guice examines that method and notes that it needs an ActionListener
named "widgetsMenuActionListener".ActionListener
named "widgetsMenuActionListener" and finds the providesWidgetsMenuActionListener
method. Guice examines that method and finds that it needs an ExampleApp
instance and a JPanel
named "widgetsPanel". It already has an ExampleApp
instance, acquired in step 5, and it got that from something marked as @Singleton
, so it reuses the same instance rather than calling provideExampleApp
again.JPanel
named "widgetsPanel" and finds the providesWidgetPanel
method. This method has no parameters, so Guice just calls it and acquires the JPanel
it needs.JPanel
with the right name in addition to the already-made ExampleApp
, Guice calls providesWidgetsMenuActionListener
, thereby acquiring a named ActionListener
instance.ActionListener
with the right name, Guice calls providesMenuBar
, thereby acquiring a JMenuBar
instance.ExampleApp
instance and a JMenuBar
instance, Guice calls addMenuToFrame
, which adds the menu to the frame.getInstance(ExampleApp)
call in main
gets executed. Guice checks, finds that it already has an ExampleApp
instance from a source marked @Singleton
, and returns that instance.Having looked through all of that, it seems @Singleton
is strictly necessary only on provideExampleApp
, but I think makes sense for everything else too.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With