Home > Java > Lessons learned from CDI in Swing

Lessons learned from CDI in Swing

Sinced I dived into CDI, I explored classical Java EE uses. Then, I used CDI into a pet project of mine to see how it could be used in Swing applications. This article sums up what lessons I learned from itthis far.

Note: this article assumes you have some familiarity with CDI; if not, please read my previous articles on CDI (CDI an overview part 1 and part 2) or read the documentation.

In my last attempts at Swing (I’m more of a web developer), I figured it could be nice to configure most of the UI components in Spring rather than coding the parameters by hand. In doing this, I used the traditional XML configuration approach, since using annotations would couple my code to Spring JAR, something I loathe to do. With CDI, however, I can live with having my code tied to a standard API: I can change the implementation. This is akin to developing a servlet where you reference the Servlet API, then run it in any container you want.

I coded and coded this way, looked at things made, refactored, then code some more, just like when it’s a personal project and you have no deadlines to meet. Then, I realized I could learn some lessons I could put to good use for the future. Here they are, in no particular order.

The right JAR

Getting all the right CDI JARs for your Java SE project can be a rather daunting task. In order to ease your pain, Weld provides you with a all-in-one JAR aptly named weld-se. Don’t argue that it’s bloated and repackaged, just use it, you will save yourself a lot of pain. For Maven users, this is the configuration:

<project>
...
  <dependencies>
    <dependency>
      <groupId>org.jboss.weld</groupId>
      <artifactId>weld-se</artifactId>
      <version>1.0.1-Final</version>
    </dependency>
  </dependencies>
</project>

She’s an easy logger

All the log frameworks I know of (JDK, Commons Logging, Log4J and SLF4J) let you declare your class logger rather easily:

private static final Logger LOGGER = LoggerFactory.getLogger(MyClass.class);

Anyway, most of the time, in order to use this, you either:

  • create a template in your IDE. Yet configuring templates in each of your IDE can be rather tedious. Note to self: propose a feature where such templates (and preferences) could be saved on the web and shared across all of my IDE instances
  • copy the line from somewhere then paste it where you need it. Then you change the class to be logged. To be frank, and though it does not add to my legend (quite the contrary) this is what I do all the time

And doing the latter all the time, it happens I forget to change the logged class. Stupid, isn’t it? Perhaps, but what’s even more stupid is that I have to write it in the first place: the class is my context, it should be inferred. Guess what, Weld does it, with a little help from the weld-logger JAR (it uses SLF4J so beware or use the code as a template to use another framework):

@Inject
private Logger logger;

I truly don’t think it could be easier! The only drawback is that you lose the constant: for the projects I handle now, I can live with it. To use this feature, just add the following dependency to your POM:

<project>
...
  <dependencies>
    <dependency>
      <groupId>org.jboss.weld</groupId>
      <artifactId>weld-logger</artifactId>
      <version>1.0.1-Final</version>
    </dependency>
  </dependencies>
</project>

Remember initialization

CDI can of course inject your dependencies. But injecting your dependencies does not assemble them on the screen. Luckily, CDI also provides you with a hook in order to do it: this is the realm of the @PostConstruct annotation. Let’s take a very simple example:

public class MyPanel extend JPanel {

  @Inject
  private JCheckBox checkbox;

  @PostConstruct
  public void afterPropertiesSet() {

    setLayout(new FlowLayout());

    add(checkbox);
  }
}

The afterPropertiesSet() method is called after having injected all dependencies, and plays the role previously done in the constructor.

Note: yes, the method is named after an old Spring method defined in the InitializingBean interface, dating from version 1. It can now be replaced by specifying the init-method attribute of the bean in the XML beans definition file or using the @PostConstruct annotation.

(Don’t) grow in numbers

With CDI, your classes are injected into one another. However, this means that each component should have its own class. Tthis can be rather cumbersome: for buttons, for example, each should be attached its action on a specific basis. With time, even a moderately size application will grow in term of classs to be much bigger than a standard application. IMHO, this is not desirable since:

  • it makes your codebase bigger, and thus increase your development complexity
  • in Sun’s JVM, loading more classes mean you will use more PermGen space and you’re more likely to run into OutOfMemoryError

In order to limit the number of classes I produce, I used a component factory filled with producer methods. These methods are identified by CDI as injection providers.

public class ComponentFactory {

  @Produces
  public JButton getButton() {

    return new JButton();
  }
}

Use the injection point

This is good but not enough, since action (or text and icon) association will have to be done in the injected class. I would like to annotate the injected attribute with informations like the text of the button and get that information in my producer method. This is the goal of the InjectionPoint optional parameter. CDI provides it to you free of charge if you reference it in your method as a parameter.

public class ComponentFactory {

  @Produces
  public JButton getButton(InjectionPoint ip) {

    JButton button = new JButton();

    // Do something based on annotations on the parameter
    ...

    return button;
  }
}

This is exactly the way that Weld loggers (see above) are created.

Respect the Higlander rule

The Highlander rule is: “There can be only one”. Apart from being taken from a movie that young people mostly don’t know, it also enunciate a basic truth. Since injection must be deterministic, there cannot be two candidates for an injection point. Using producer methods as previously stated will run you into this problem: CDI will have both the JButton class and the producer method and will loudly complains about it in its usual way.

Exception in thread "main" org.jboss.weld.exceptions.DeploymentException: WELD-001409 Injection point has ambiguous dependencies.....
 at org.jboss.weld.bootstrap.Validator.validateInjectionPoint(Validator.java:280)
 at org.jboss.weld.bootstrap.Validator.validateBean(Validator.java:122)
 at org.jboss.weld.bootstrap.Validator.validateRIBean(Validator.java:141)
 at org.jboss.weld.bootstrap.Validator.validateBeans(Validator.java:331)
 at org.jboss.weld.bootstrap.Validator.validateDeployment(Validator.java:317)
 at org.jboss.weld.bootstrap.WeldBootstrap.validateBeans(WeldBootstrap.java:399)
 at org.jboss.weld.environment.se.Weld.initialize(Weld.java:81)
 at org.jboss.weld.environment.se.StartMain.go(StartMain.java:45)
 at org.jboss.weld.environment.se.StartMain.main(StartMain.java:57)

To be compliant with the Highlander rule, you’ll have to discard the JButton class as a candidate for injection. In order to do this, I used qualifiers, both on the injection point and on the producer method. Since I did not want a qualifier per injection point / producer method pair, I made it parameterizable:

@Qualifier
@Target( { FIELD, METHOD })
@Retention(RUNTIME)
public @interface JType {

  public Class<? extends JComponent> value();
}

Annotating both the injected attribute and the producer method with the JType annotation made my code compliant with the Highlander rule!

Spread the word

A good practice I can recommend is to create a special Properties class tasked with initializing your labels (and another for your preferences, etc.), then inject it in all the client classes you need. Now, all classes have access to your internationalized labels. Truly terrific!

Forget Java WebStart

Weld JAR analyze process is incompatible with Java WebStart. You will likely have such nice error messages:

122 [javawsApplicationMain] WARN org.jboss.weld.environment.se.discovery.URLScanner - could not read entries
java.io.FileNotFoundException: http:xxxxxxxxxxweld-logger-1.0.0-CR2.jar (La syntaxe du nom de fichier, de répertoire ou de volume est incorrecte)
  at java.util.zip.ZipFile.open(Native Method)
  at java.util.zip.ZipFile.<init>(Unknown Source)
  at java.util.zip.ZipFile.<init>(Unknown Source)
  at org.jboss.weld.environment.se.discovery.URLScanner.handleArchiveByFile(URLScanner.java:142)
  at org.jboss.weld.environment.se.discovery.URLScanner.handle(URLScanner.java:126)
  at org.jboss.weld.environment.se.discovery.URLScanner.scanResources(URLScanner.java:107)
  at org.jboss.weld.environment.se.discovery.SEWeldDiscovery.scan(SEWeldDiscovery.java:71)
  at org.jboss.weld.environment.se.discovery.SEWeldDiscovery.<init>(SEWeldDiscovery.java:45)
  at org.jboss.weld.environment.se.discovery.SEBeanDeploymentArchive$1.<init>(SEBeanDeploymentArchive.java:45)
  at org.jboss.weld.environment.se.discovery.SEBeanDeploymentArchive.<init>(SEBeanDeploymentArchive.java:44)
  at org.jboss.weld.environment.se.discovery.SEWeldDeployment.<init>(SEWeldDeployment.java:37)
  ...
  at com.sun.javaws.Launcher.run(Unknown Source)
  at java.lang.Thread.run(Unknown Source)

I hope it will be fixed in future releases…

Singletons are NOT evil

Twice already I stumbled upon a strange behaviour: toggle buttons selected when they shouldn’t or state was lost. After debugging like mad, I saw that @PostConstruct methods where called well beyond the initial call. It seems my code called for another injection after that. In order to remedy to this, annotate your class with @Singleton in order to share the instance across multiple calls. I haven’t investigated more than that because:

  • I resolved the bug
  • I don’t know why I shouldn’t use singletons in a Swing application

Conclusion

I’m still in development, so I don’t think I’ve seen all there’s to see. Yet, the previous points can make a good starting point for any project wanting to use CDI in a Java SE context.

And please pardon the puns, I was feeling jolly because of this fine summer of ours :-S

email
Send to Kindle
Categories: Java Tags: ,
  1. Marco
    August 8th, 2010 at 18:15 | #1

    Thanks for posting… I’m still unsure if CDI is appropiate for “normal” SE-Apps. How do you cope with the lack of useful bean-scopes? Do you let the Weld-container instanciate every possible Bean application-scoped at startup? Surely not? How do you get your Views instantiated and injected just when you need them? And so long as you need them?

  2. August 8th, 2010 at 20:20 | #2

    To be frank, I have not enough experience in Swing alone to give you much tips when using CDI. What I know so far:

    • Memory use is a bit high
    • In Spring, your factory instantiate most beans at start time. If you change to CDI, there won’t be much change
    • It’s true that you have no control over your views lifecycle. What works for me is to have singletons, but my application has not so much frames

    My stance on this is “let’s do it to have some feedback”, but it cannot be done on professional projects (yet) so I develop a pet project with CDI. I encourage you to do the same and share your feedback.

  3. Marco
    August 9th, 2010 at 20:50 | #3

    @Nicolas Frankel
    Well Nicolas, to be frank I think that your approach using CDI in Swing is not the path to success. I have really a lot of experiences with Swing-GUIs and implementing MVC-Frameworks around them, and I don’t see the point why I should use CDI for composing my views. As you mentioned in the blog, you still have to write the assembling code. For me a view is nothing more than a class that assembles a hierarchy of visible components, in fact its a tree of components. You can build reusable panels, if you like, but at least it all boils down to a create and assemble chain of methods, resulting in a dispayable GUI-Part. I really don’t need the indirection and loose coupling of CDI here, nore does it fitt well into the lifecycle scopes. Ok, singletons came to the rescue, but this is really a poor solution. Singletons are almost always a bad thing, and chances are high you would run into troubles with them. I don’t know your window techniques, but for me it is very common to have more than one instance of the same view open – for example if I pick more than one item of a list and open a details- or edit-window on each of them.

  4. August 9th, 2010 at 20:54 | #4

    @Marco
    Thanks for your input. For me, ease of development (thus cost) supercedes memory consumption, and I see much added value in using CDI. I understand other have other priorities. Cheers!

  5. April 17th, 2011 at 12:14 | #5

    Thanks Nicolas for this very useful post.
    But I’ve got a question about weld-logger. Is it really a good thing to use it ? In fact, I agree with you that it’s clearly a better way to declare the logger like this because it’s less verbose. But, in unit test context, the logger won’t be injected by default so you’ll have NullPointerException. How do you manage this NPE ? Do you create an Alternative to decorate a mock implementation of Logger (it seems complex…) ?
    Thanks in advance for your answer.

  6. April 17th, 2011 at 19:34 | #6

    @Olivier Catteau
    Don’t understand why but the logger is injected now… I’ve changed dependencies. Strange…

  7. Milan
    January 20th, 2013 at 10:24 | #7

    Injecting atomic components (checkbox, etc.) seems to be an anti-pattern to me….

  1. No trackbacks yet.