Annotations Gotchas and Best Practices

I often find myself getting involved in conversations about annotations and how/where they should be used and avoided. My opinion is basically represented by the following guidelines:

Good Uses

Avoid These Uses

About the Frameworks Mentioned

I mention a few frameworks on this page. My goal is not to promote, denigrate or critque any of them. Whenever one is mentioned, the intention is limited specifically to the use of a particular annotation. My objective is to bring out points that should be taken into consideration when/if adding annotations to your project.

JUnit 4

One example of using annotations to provide data about a class is JUnit4. By using annotations instead of the naming convention (all test methods start with test*) the identification of test methods is more declarative and simultaneously more flexible. This can support a DSL style of programming too.

One of my favorites (and under-used) is @RunWith. This allows you to change the runner used for the test class.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:test-config.xml"})
public class BaseTest {
  @Autowired
  public Properties testProps;

When combined with Spring's @ContextConfiguration and @Autowired test cases can become amazingly simple. Genreally, I'm not a big fan of auto-wiring but, for test cases, I think it is completely reasonable.

Another useful (though underused) annotation in JUnit 4 is @Ignore. Use it instead of temporarily commenting out test cases.

Using Meta-data to Enforce Rules

The use of the @Override attribute, allows the compiler to enforce a coding standard. This can help proctect against mistakes when refactoring code. Of course, it also requires that all over-ridden methods use the annotation. Any that don't lose the protection. This is especially useful when in an environment of Merciless   Refactoring.

Sometimes, annotations can overcome weaknesses in frameworks. For example, the Spring Framework added the @Required annotation to identify required fields in POJOs. If a field is over-looked during configuration, an exception will be thrown (at application start-up instead of receiving a null pointer exception when executing the code).

EJB3 Brings in a Little Good and Bad

In EJB 3 a little too much data ends up inside the java class.

 @Resource(name="myDB", type="javax.sql.DataSource.class")
 @Table(name="CUST", schema="RECORDS")
 public class Customer implements Serializable {
 ...
 @SQL(statement="SELECT * FROM USERS WHERE {sql: where}")

Sorry but, this looks like a java equivalent to a C style macro for JDBC. We don't normally want to put SQL, tables, rows, etc in a java class anyway. Placing it in an annotation is little better. I've seen too many examples where the database username/password is in an annotation too.

JNDI Injection

A little more of a grey area is the use of JNDI injection.

  @Inject(jndi-name="java:comp/env/jdbc/test")
  public void setDataSource(javax.sql.DataSource dataSource)
  {
     .....

I'd prefer to see the target element injected via a DI framework however, if one isn't being used, this is an alternative.

Good Uses

By using annotations to eliminate boilerplate and cookie-cutter code, application designs can support change much better, redundancy can be reduced, and duplication eliminated. The difference in productivity is less obvious, often boilerplate is generated by a tool and never actually written by a person anyway.

Aspects Over Naming Conventions

This issue is brought up in the book Effective Java. A perfect example is how JUnit 4 switched to using annotations to mark tests instead of the naming convention where methods start with the word test. It also eliminated the need for test cases to extend the JUnit TestCase class.

The benefits this provides are pretty large. By removing the requirement to extend a class, the test annotations can be used inside the class being tested. This is useful for really small projects where people often added main methods for testing. By no longer requiring a naming convention, test method names can be more descriptive about what is being tested.

Annotations as Aspects

Caution should be taken is when annotations are used as a substitute for aspects. One of the features (good and bad) of aspects is their transparency. A class with an aspect applied to it doesn't contain knowledge of the aspect's existence. This provides maximum flexibility with minimum effort while also following the DRY principle.

Many annotations are little more than declarative aspects. For example, @TransactionAttribute. User's of Dependency Injection frameworks like Spring are more accustomed to applying aspects to classes to do things like transactions. There are arguments to both sides of this. All-in-all, I think aspects are most appropriate choice most of the time, and the declarative model is fine special cases.

Another controversial use of annotations as Apects is the set brought in by JBoss. JBoss introduced the @Cache annotation for EJB3. While very useful (and an improvement), it behaves like an aspect but, has none of the transparency of an aspect (it's in the class after all).

Those who have not spent time with aspects feel these annotations are great. Those experienced with AOP don't like the need to traverse lot's of classes to find where the annotation is used or to mark other classes. They prefer the transparency and centralized definition of aspects.

If you are creating an annotation, ask yourself, "am I really creating an Aspect?" If the answer is yes, use aspects instead. You'll save many headaches while maintaining your application. And let's face it. In this world of constantly changing requirements, maintenance usually starts before development finishes.

Annotations in AspectJ

The AspectJ project has released a set of annotations that support using AspectJ without the need of the AspectJ compiler. The aspect code can be written in the annotation and gets used by normal java. This has the benefit of reducing the infrastructure for a build and lowering the learning curve for developers not experienced with AOP.

@Aspect
public class EditorialFailNiceAspect {

   @Pointcut("execution(* *..*IEditorialService.*(..))")
   private void editorialApiPointcut() { }

   @Around("editorialApiPointcut()")
   public Object wrapApiExceptions(ProceedingJoinPoint pjp) throws Throwable {
   ...

The AspectJ annotations are basically a method used to extend the language. This kind of thing can hold great promise for DSLs. However, care should always be given when using a technique like this to extend a language.

Google Guice

I hate to pick on a framework, especially a really good one. However, Guice's @ImplementedBy just misses the point altogether.

@ImplementedBy(ServiceImpl.class)
public interface Service {

Most of the problems with this go beyond the actual annotation itself. First, by putting a reference to the implementation in the interface it implements, a circular compile time dependency is created between the two. Let's face it, all good OO developers agree that dependencies should flow in one direction.

Dependencies using @ImplementedBy
Circular dependency introduced by using annotation
Note: Circular dependency introduced by using annotation

When using a dependency injection framework, dependencies should follow the standard model.

Standard Model for dependency injection

The next major issue: this suggests that there will only be one implementation of the interface. In this case, the interface is redundant, a violation of the YAGNI principle.

If creating annotations, one objective should be to encourage (make easier) good coding practices by the users of the annotation.

Generating Boilerplate Code

JAX-RPC 2.0 eliminated the requirement for web service beans to implement an interface. When generating a web service from java source, annotations can be used.

package my.ws;

import javax.jws.WebService;

@WebService
public class GeoLookupImpl {

   public String getCountryCode(String ipAddress) {
     return internalService.lookupCC(ipAddress);
   }
}

The apt tool will generate the WSDL and the required boilerplate code to make this work as a webservice.

Annotations can be an excellent tool for code generation. Especially if transitioning from the world of XDoclet. Though, JAX-RPC 2.0 may not be the best example, If you have experience with web services, you've already suffered the pain caused by generated WSDLs, and you'll want to use the tool to create a class from a WSDL instead.

Killing Boilerplate Code

JMX introduced a very simple method for managing applications. I'm sure anyone who has used it took a trip through the tutorial. The main issue with it is, the requirement to create an *MBean interface that defines the methods to be managed. Basically, it is required boilerplate to help out the framework.

Enter Spring JMX. Just mark a class with @ManagedResource and methods with either @ManagedAttribute, @ManagedNotification or @ManagedOperation and the framework takes care of the rest. These annotations remove the requirement for the boilerplate code, simplifying and accelerating development.

Simplifying Classes ?

Spring MVC introduced a set of annotations eliminate the need to extend classes like AbstractController. By using these annotations, the controllers actually become easier to test because there is no need to mock out a HttpServletRequest or other infra-structure classes. It also means the controllers could be re-used in standard thick client applications.


@Controller
@RequestMapping("/searchCars.do")
@SessionAttributes("lastSearchTerm")
public class SearchCarForm {

  private final SearchService search;

  @Autowired
  public SearchCarForm(SearchService search){
    this.search = search;
  }

  @RequestMapping(method = RequestMethod.GET)
  public String setupForm(@RequestParam("term") String searchTerm , ModelMap model) {
.....

OTOH, this style places so much information in the annotations it can be rather disconcerting to look at. Placing the URL mapping in the class could introduce maintenance later. Although I think the intention was good (removing dependency on J2EE classes), the extra complexity of this style of annotation negates most of the benefits.

I saw a presentation by Rod Johnson at the Spring Experience conference, and he actually recommended against annotations that take Strings as parameters.

Conclusion

Annotations are a very powerful tool in the toolbox (but, it's not a golden hammer). Perhaps the most important thing regarding implementing annotations is recognizing when not to use them. Or, to recognize when there may be a better alternative.

References:

Acknowledgements

Great thanks and kudos go out to Brian Doyle for ideas, information, and feedback while writing this.

Feedback

Want to contribute? Ideas? Questions? Comments? Insults? Send me a twit or post a response on DZone or reddit.

copyright@2023 willcode4beer.All rights reserved

Tech Tags:


Sponsors:

About willCode4Beer