/ CDI, 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.

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.

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:\xxxxxxxxxx\weld-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 😅️

Nicolas Fränkel

Nicolas Fränkel

Developer Advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). Usually working on Java/Java EE and Spring technologies, but with focused interests like Rich Internet Applications, Testing, CI/CD and DevOps. Also double as a trainer and triples as a book author.

Read More
Lessons learned from CDI in Swing
Share this