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
- Simplify Configuration
- To define requirements
- To enforce local policies
- Avoiding unneeded interfaces (with care)
- Avoiding marker interfaces (no methods)
- Replace Naming Convention Based Development
- To avoid boilerplate code
- To make meta-data about code available at runtime
Avoid These Uses
- Environment Specific Information
- Application configuration
- Database configuration
- JNDI lookups
- A substitute for macros
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.
When using a dependency injection framework, dependencies should follow the standard model.
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:
- Where JBossiane blog - When to use Annotations
- Software Reality - Annotations: Don't Mess with Java
- An early look at JUnit 4
- AspectJ Notebook
- Spring JMX Annotations
- EJB3 Annotation Cheat Sheet
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