/ CDI

Further into CDI

In one of my latest articles on CDI, one of the lessons I learned was to use factory methods in order to reduce the number of classes. In this article, I will go into the detail since I think it is of some importance. More basic informations on CDI can be found in some previous posts: overview part 1 and overview part 2.

The naive approach

During school, you were probably taught to create a class for each component. For example, if your application has 4 buttons, North, East, South and West, you’ll have 4 classes. Each class configures its label, its icon and its action, either in the constructor or in some other method.

In CDI, your can annotate such method with @PostConstruct in order for the container to call it just after the constructor.

This produces the following code, when taking Swing as an example:

public class NorthButton extends JButton {

  public NorthButton() {
    // Initializes label
    // Initializes icon
    // Initializes action
  }
  ...
}

Common behaviour and attributes may be factorized into one superclass, thus you end up with 5 classes.

Using the right number of classes is very important in any object-oriented language:

  • If you use too few classes, you run the risk that each class you develop has too many responsabilities and are too big.
  • On the contrary, if you use too much classes, you’ll explode code into many places and it will be a maintenance nightmare. Moreover, in the Sun (Oracle ?) JVM, classes are loaded into the PermGen space which has a fixed size. Who has never seen the dreaded java.lang.OutOfMemoryError: PermGen space?

IMHO, both are good reasons not to create a single class for each instancied component.

The factory approach

The second logical step is to create methods that produce the right instance. In our previous example, that would mean a factory class, with 4 methods to produce buttons, one for each button and probably a 5th method with common behaviour:

public class ComponentFactory {

  @North
  @Produces
  public JButton createJButton() {
  JButton button = new JButton();
    // Set label
    // Set icon
    // Set action
    return button;
  }
  ...
}

Notice that you’ll still need an annotation for each single component:

@Retention(RetentionPolicy.RUNTIME)
@Target({FIELD,METHOD,PARAMETER,TYPE})
@Qualifier
public @interface North {}

In our case, that makes @North, @East, @South and @West. If these components are found throughout your application, fine (such as a Cancel button). If not, you still have a class for each component (annotations are transformed into classes at compile-time).

The "generic" annotations approach

CDI let you qualify injection not only from the annotation type but also from some (or all) their elements. We can thus greatly diminish the number of needed annotations. In order to do this, just create an annotation on this model:

@Retention(RetentionPolicy.RUNTIME)
@Target({FIELD,METHOD,PARAMETER,TYPE})
@Qualifier public @interface TypeName {

  Class value();
  String name();
}

Now the factory method becomes:

public class ComponentFactory {

  @TypeName(value = JButton.class, name = "North")
  @Produces public JButton createJButton() {

    JButton button = new JButton();
    // Set label
    // Set icon
    // Set action
    return button;
  }
  ...
}

Although this method meets our requirements regarding the number of classes (and annotations), it still has one major drawback: now, it’s the number of methods that is vastly increased. Though not nearly as great a disadvantage as a great number of classes, it makes the code less readable and thus less maintainable.

The fully "generic" approach

CDI let you use an injection point parameter for producer method. This parameter has much information about, guess what…​ the injection point. Such informations include:

  • the reflection object, depending on injection’s type, respectively Field, Method and Constructor for field, method parameter and constructor parameter injection
  • the required type to inject
  • annotations relative to the injection point

The latter point is what drives what comes next. It means you can annotate your injection point, then get those annotations during producer method execution. In our case, we could have an annotation for each attribute we want to set (text, icon and action). Let’s create such an annotation:

@Retention(RetentionPolicy.RUNTIME)
@Target({FIELD,METHOD,PARAMETER,TYPE})
public @interface Text { String value(); }

Such annotation could be used in the producer method in the following manner:

public class ComponentFactory {

  @Produces
  @TypeName(JButton.class)
  public JButton createJButton(InjectionPoint ip) {
    JButton button = new JButton();
    Annotated annotated = ip.getAnnotated();

    if (annotated.isAnnotationPresent(Text.class)) {
      Text textAnnotation = (Text) annotated.getAnnotation(Text.class);
      String text = textAnnotation.value();
      button.setText(text);
    }

    // Set icon
    // Set action
    return button;
  }
}

Now, the only thing you have to do is annotate your button field like so:

public class Xxx {

  @Inject
  @TypeName(JButton.class)
  @Text("North")
  private JButton northButton;
  ...
}

This will get us a button with "North" as label. And nothing prevents you to do the same with icon and action.

Morevover, with just text, you could go further and manage internationalization:

@Retention(RetentionPolicy.RUNTIME)
@Target({FIELD,METHOD,PARAMETER,TYPE})
public @interface Text {

  // Key in the properties file
  String value();

  // Path to the resource bundle
  String resourceBundle() default "path/to/resource/bundle";
}

This annotation could also be reused of other components that manage text, such as labels.

Conclusion

There’s no right or wrong approach in using CDI. However, the latter approach has the merit of being the most powerful. Additionally, it also let you write code relative to a component type in one single place. This is the road I’ve taken so far, and I’m very satisfied with it.

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
Further into CDI
Share this