Thursday, February 18, 2010

Overriding a component in JBoss Seam

A few days ago, I was asked to explain how to change/override/mock a component in JBoss Seam. So, here is it.

To override a component in Seam, all you need to do is to create a second component that satisfies the following conditions:

  1. It must have the same name as the component you are overriding.
  2. It must be of the same type than the component you are overriding. Or you will end up with a ClassCastException on injection.
  3. It must have a higher precedence than the component you are overriding.

The first two points need no further explanation. However, the third one raises the following questions: What is the precedence of a component? How do I change it?

The precedence of a component is a numeric attribute all components have, which by default is 20. You can change the precedence of a component using the @Install annotation:

@Name("myBean")
@Install(precedence=40)
public class MyBean {
 ...
}

On startup, if Seam finds more than one component with the same name, it will choose the one with the higher precedence value. If it finds two components with the same name and the same precedence, an Exception is thrown.

Let’s take a look at an example. Suppose that you have an interface called CodeGenerator with two implementations: DefaultCodeGenerator and an alternate MockCodeGenerator.

public interface CodeGenerator {

 public String generateCode();
 
}
@Name("codeGenerator")
public class DefaultCodeGenerator implements CodeGenerator {

 public String generateCode() {
  ...
 }
}
DefaultCodeGenerator. No @Install annotation; it will have a precedence of 20 (the default value).
@Name("codeGenerator")
@Install(precedence=Install.MOCK)
public class MockCodeGenerator implements CodeGenerator {
 
 public String generateCode() {
  ...
 }
}
MockCodeGenerator. Notice that it has the @Install annotation with a precedence of 40 (see the table below).

Now, suppose both DefaultCodeGenerator and MockCodeGenerator are on the classpath and that we have the following injection point:

@Name("testCodeGenerator")
public class TestCodeGenerator {
 ...
 @In
 private CodeGenerator codeGenerator;
 ...
}

Which component will be injected? Seam will choose the one with the higher precedence. In this case: MockCodeGenerator. So, a common usage is to have a separate JAR with the alternate implementation that can be included on the classpath only when needed (i.e. added to the WAR or EAR archive).

Seam defines some predefined precedence values:

Constant Value Description
Install.BUILT_IN 0 Built in Seam components have this precedence.
Install.FRAMEWORK 10  
Install.APPLICATION 20 The default precedence
Install.DEPLOYMENT 30  
Install.MOCK 40 For mock objects used in testing

Note: It is recommended to use one of this constants for the precedence. However, nothing stops you to set a different value.

Using components.xml

Another way of overriding a component in Seam is to define the component in the components.xml descriptor file instead of using the @Name annotation. XML-based configuration is discouraged by Seam but valid anyway. This way you can change the class directly in the XML without re-compiling.

What I usually do is to have a default implementation annotated with the @Name annotation which has the default precedence. When I need to override it, I define a second component in the components.xml descriptor with a higher precedence (this way, I’m only using the XML file to install the alternate implementations). For example, instead of annotating the MockCodeGenerator with @Name and @Install, we could have added it to the components.xml:

<components>
 ...
 <component name="codeGenerator" class="org.test.MockCodeGenerator" precedence="40" />
 ...
</components>

Other options

The @Install annotation is much more powerful than what I’ve just shown you. It allows you to “install” a component only when certain conditions are satisfied. For example:

  • When you are working on debug mode.
  • When a class or group of classes are present on the classpath.
  • When a component or group of components are present on the classpath.

When you are working on your development environment, you might want to mock some components to: simulate external systems you don’t have access to, simulate emails, sms, etc., disable some functionality, etc. For these cases, you can have some components annotated with @Install(debug=true) that override the default ones. They will be installed only when working on debug mode (this setting is configured in components.xml).

More advanced options allow you to install a component if some classes or other components are present on the classpath. You can do some complex stuff here but that is out of the scope of this post.

Finally, you can always rely on @Install(false) which won’t install the component by default. You will have to install it manually in the components.xml descriptor file. Al lot of built-in components of Seam are configured this way so you can install them only when needed.

Conclusion

Seam offers you a variety of ways in which you can override components, or, how Seam calls it, conditionally install components. It offers you so many options, it can sometimes get tricky. However, once understood, it is a very powerful feature.