Thursday, February 21, 2013

Becoming unaware of the Dependency Injection Framework. Encouraging standarization

Intro

The previous post showed up how to combine Spring Framework, Mockito and Lombok in TDD fashion. But it's far from be the simplest and portable solution if such thing even exists. Let's try to make the whole example more unaware of the DI Framework, in this case Spring, but still use it for testing purposes.

Java 6 brought us several improvements, CDI among them. This refcardz is self-explanatory. IMO the main advantage of CDI is standardization, but CDI is pure specification, not an implementation, and its RI is Weld.

Results that Spring honors CDI since version 3.0, at least in Java Injection Standard (JSR-330). There is a bunch of posts and articles talking about the Spring support for CDI specifications, and which one is better to use. But this is not the goal of this post.

Goals

  • Become unaware of the DI Framework at design time
  • Support Spring at test time
  • Support Spring at runtime
  • Support others DI Framework at runtime

How to

  • By replacing @Autowired and using injection by name with @Resource
  • By naming beans using @Name and scanning packages for candidates instead of explicitly create the bean in application context configuration file.

Let's do it

As I said before, it's just an example. Real world scenarios are more complex, and you will end for sure combining many APIs from different sources.

  1. Use the example from the previous post.
  2. Include a couple of dependencies in your pom.xml.
  3. 
      javax.annotation
      jsr250-api
      1.0
    
    
      javax.inject
      javax.inject
      1
    
    
    JSR-250 provide us various standard annotations: @Resource, @PostConstruct, @PreDestroy , etc; these annotations are honored by Spring. In the other hand  with have JSR-333 (javax.inject) that give us @Inject, @Named, @Provider, @Qualifier, @Scope and @Singleton.
    Lets explain some of these annotations:
    • @Resource
      • Matching: First matches by name, then matches by type and filters by qualifiers if not name-based match gets found.
      • Applicable to: Fields, Classes and Methods.
      • Package: javax.annotation
    • @PostConstruct
      • Runs: After the bean construction and wiring
      • Applicable to: Methods.
      • Package: javax.annotation
    • @PreDestroy
      • Runs: Before the release of the bean
      • Applicable to: Methods.
      • Package: javax.annotation
    • @Inject
      • Matching:  First matches by type, filters by qualifiers, then matches be name.
      • Applicable to: Methods, Constructors and Fields.
      • Package: javax.inject
    • @Named
      • Description:  Its a qualifier that denotes a named bean. Annotated classes are considered for auto-detection on classpath scanning. It's similar to Spring's @Component
      • Applicable to: Classes and Fields.
      • Package: javax.inject
  4. Spring dependencies are now on test scope, since they are only required on test time. But in a real world scenario you will need a DI Framework, so don't forget to include Spring or Weld dependencies for production stage.
  5. 
      ...
     
      org.springframework
      spring-test
      ${spring.version}
      test
     
     
      org.springframework
      spring-beans
      ${spring.version}
      test
     
     
      org.springframework
      spring-context
      ${spring.version}
      test
     
      ...
    
    
  6. The test is an hybrid between Spring and more standard stuff, that is:
  7. package demos.sf.editor.test;
    
    import ... ;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = { "/test-applicationContext.xml" })
    @TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
      DirtiesContextTestExecutionListener.class })
    @DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
    public class EditorTest {
    
     @Resource
     private Editor editor;
    
     @Resource
     private SpellChecker spellChecker;
    
     @Test
     public void testPasteSuccess() {
      editor.paste("Hello everybody!");
    
      String expected = "Hello everybody!";
      String actual = editor.getText();
      assertEquals(expected, actual);
     }
    
     @Test
     public void testAddParagraphSpellIsChecked() {
      editor.paste("Hello everybody!");
      verify(spellChecker, only()).check("Hello everybody!");
     }
    }
    
  8. The SUT (Editor) is now DI Framework unaware:
  9. package demos.sf.editor.impl;
    
    import ...;
    
    @Named
    public class Editor {
    
     @Getter
     protected String text;
    
     @Resource
     private SpellChecker spellChecker;
    
     public void paste(String cut) {
      if (text == null) {
       text = "";
      }
      text += cut;
      spellChecker.check(cut);
     }
    }
    
  10. The application context configuration file for testing stage file is now simplified. Only package scanning is needed to create the SUT (Editor):
  11. 
    
     
    
     
      
     
    
    
    
    NOTE:Observe that some minor package relocation were made to organize tests, interfaces and classes.
The final snapshot can be found here under the spring-hello-word-cdi directory. Enjoy it!

1 comment: