Comment lines: Loose coupling with Context and Dependency Injection

Loose coupling with the Service Provider pattern enables new classes to be added without the need for any application code changes. This is possible using Context and Dependency Injection (CDI), part of Java™ EE 6 that is simple to implement and provides several advantages including the ability for you to create pluggable content. This content is part of the IBM WebSphere Developer Technical Journal.

Share:

Brian S Paskin (bpaskin@us.ibm.com), Senior WebSphere Consultant, IBM

Brain Paskin is a veteran of IBM for 18 years and has worked for IBM in Germany, Italy and the US. He is currently working for IBM Software Services for WebSphere helping customers with WebSphere MQ, WebSphere Application Server, DataPower implementation, performance, and other issues. Brian has helped write two IBM Redbooks and is certified on several IBM products.



30 July 2014

Also available in Chinese

Introduction

Context and Dependency Injection (CDI) provides an easy method to loosely couple injected classes utilizing the Service Provider pattern. By using the CDI bean manager to return the correct class without the application having to know anything about the class type that will be returned, this pattern is in contrast to the Factory pattern or the Producer pattern. This enables new classes to be added without the need for any coding changes.

There are a number of advantages when utilizing CDI in applications. For example:

  • The application code does not have to know anything about the beans, only that they implement a certain interface. For example, you can use CDI in a banking application that is trying to offer different accounts to customers.
  • The application does not have to know anything about the account types, and so the account types can be added, removed, or changed without having to write any additional code. Using CDI, the account types are available and can be filtered based on need.
  • CDI applications do not have to use factories, or update the factory class anytime a new bean is added, updated or removed. This lends to faster development time and enables you to modularize your applications better.
  • Testing has become easier, as beans can be easily added, removed, or changed based on a the test case.
  • One of the biggest advantages is the potential ability to plug in a new framework without changing any code.

The example code included in this article is a small sample of what can be achieved when using CDI. CDI is part of Java EE 6.


Factory pattern

The Factory pattern provides a simple way to return the correct implementation that is requested. However, in this pattern the code would have to change any time an implementation is added to or removed from the factory. This is loosely coupled when dealing with classes, but is not as effective when modules are involved.

In the example shown in Figure 1, there is a simple interface that is implemented by two classes. The factory’s job is to return the correct implementation based on the name that was passed to the factory. Whenever a new class implements the City interface, or one of the classes is removed, the factory would need to be updated.

Figure 1. Simple interface implemented by two classes
Simple interface implemented by two classes

Listing 1 shows a simple interface that has three methods to be implemented.

Listing 1.
public interface City {
     public String getEnglishName();
     public String getLocalName();
     public int getPopulation();
}

The first class to be implemented is for Rome (Listing 2).

Listing 2.
public class RomeImpl implements City {
     private String englishName = "Rome";
     private String localName = "Roma";
     private int population = 2645907;
     
     @Override
     public String getEnglishName() {
          return this.englishName;
     }
     
     @Override
     public String getLocalName() {
          return this.localName;
     }
     
     @Override
     public int getPopulation() {
          return this.population;
     }
}

The second interface to be implemented is for Cologne (Listing 3).

Listing 3.
public class CologneImpl implements City {
     private String englishName = "Cologne";
     private String localName = "Köln";
     private int population = 1024373;
     
     @Override
     public String getEnglishName() {
          return this.englishName;
     }
     
     @Override
     public String getLocalName() {
          return this.localName;
     }
     
     @Override
     public int getPopulation() {
          return this.population;
     }
}

The factory class has a single method that returns the proper implementation class based on the name passed. The switch statement with a String is only available in Java 7 or higher (Listing 4).

Listing 4.
public class CityFactory {
     
     public static City getCity(String name) {
          City city = null;
     
          switch (name) {
               case "Rome": city = new RomeImpl(); break;
               case "Cologne": city = new CologneImpl(); break;
               default: 
                    throw new IllegalStateException("Could not find 
                    class based on the name '" + name +"'");
          }
          return city;
     }
}

Listing 5 shows a simple servlet that calls the factory to get the correct class correlated to the value passed to the factory method.

Listing 5.
@WebServlet("/FactoryPattern")
public class FactoryPattern extends HttpServlet {
     private static final long serialVersionUID = 1L;
     
     protected void doGet(HttpServletRequest request,
          HttpServletResponse response) throws ServletException, 
          IOException {
     
          PrintWriter out = response.getWriter();
     
          City city = CityFactory.getCity("Rome");
          out.println("The city is " + city.getLocalName() + 
               " or called " + city.getEnglishName() + " and 
               has a population of " + city.getPopulation());
     }
}

Calling the servlet from a browser using http://host:port/context/FactoryPattern returns the results of the lookup, which is the information about Rome (Figure 2).

Figure 2. Lookup results
Lookup results

The Factory pattern is useful and very quick to code; however, when adding another city implementation, the CityFactory class would need to be updated. This is not dynamic at all and must be maintained going forward.


Naming the implementations and injection

The Factory pattern does not need to use CDI. The Producer pattern (not covered here) is similar to the Factory pattern but does not require dependencies on the factory class. A good description and sample code is found here.

In the Service Provider pattern, there is a decoupling between the implementation classes being utilized and the provider that is going to utilize them. This lets you add or remove classes dynamically (Figure 3).

Figure 3. Decoupling between implementation classes and service provider
Decoupling between implementation classes and service provider

The Service Provider pattern enables the implementation classes to be referenced using the @Named annotation (Listing 6).

Listing 6.
@Named("Rome")
public class RomeImpl iimplements City {
     
@Named("Cologne")
public class CologneImpl implements City {

With the help of the BeanManager, which provides access to all beans that are injected using CDI, a specific class can be returned, or all instances of a certain type can be returned. The BeanManager is injected into the code instead of having to instantiate it. Since there is only one BeanManager for the application, a static class can be created to return the BeanManager (Listing 7).

Listing 7.
public class CityProvider {
     @Inject private BeanManager bm;
     
     public Object getBean(String name) {
     
             Iterator<Bean<?>> iter = bm.getBeans(name).iterator();
          if (!iter.hasNext()) {
             throw new IllegalStateException("Cannot find instance of " + name);
          }
     
          Bean<?> bean = iter.next();
          CreationalContext<?> ctx = bm.createCreationalContext(bean);
          Type type = (Type) bean.getTypes().iterator().next();
          return bm.getReference(bean, type, ctx);
     }
     
     public Object getBean(Class<?> clazz) {
     
             Iterator<Bean<?>> iter = bm.getBeans(clazz).iterator();
          if (!iter.hasNext()) {
             throw new IllegalStateException("Cannot find instance of " + clazz.getCanonicalName());
          }
     
          Bean<?> bean = iter.next();
          CreationalContext<?> ctx = bm.createCreationalContext(bean);
          return bm.getReference(bean, clazz, ctx);
     }
}

In the example above, the getBean(String name) method is agnostic to the class or classes that it is searching. The BeanManager is used to return any class that is using that particular name. This makes replacing one class with another as easy as changing or adding the @Named to the class. The second method returns a class based on a class name, if it is found in the BeanManager.

The class is injected into a servlet, and the methods are called normally (Listing 8).

Listing 8.
@WebServlet("/ServiceProviderPattern")
public class ServiceProviderPattern extends HttpServlet {
     private static final long serialVersionUID = 1L;
     
     @Inject private CityProvider provider; 
     
     protected void doGet(HttpServletRequest request, 
          HttpServletResponse response) throws ServletException, IOException {
               PrintWriter out = response.getWriter();
               City city = (City) provider.getBean("Rome");
               out.println("The city is " + city.getLocalName() + " or 
                    called " + city.getEnglishName() + " and has a population of " + 
                    city.getPopulation());
}

Another method of retrieving a specific class or a list of classes is to inject an Instance of a specific interface or abstract class. Afterwards, the classes can be searched for a match or return a combination of information.

The example in Listing 9 is retrieving all instances of anything that implements City. The various methods can retrieve a specific instance or, as in this example, return a list of city names.

Listing 9.
public class CityProvider2 {
     @Inject private BeanManager bm;
     @Inject private Instance<City> instance;
      
      public City getCity(String name) {
           for (City city : instance)
                if (city.getClass().getSimpleName().contains(name)) 
                     return city;
       
           throw new IllegalStateException("Cannot find instance of " + name);
      }
      
      public City getCity(Class<?> clazz) {
            for (City city : instance) 
                 if (city.getClass().equals(clazz)) 
                      return city;
       
            throw new IllegalStateException("Cannot find instance of " + clazz.getCanonicalName());
       }
       
       public List<String> getAllCities() {
            List<String> cities = new ArrayList<String>();
            for (City city: instance)
                 cities.add(city.getEnglishName());
       
            return cities;
       }
}

The servlet (Listing 10) is similar to the previous example and injects the CityProvider2. The provider will retrieve a list of cities and display them on a web page (Figure 4).

Listing 10.
@WebServlet("/ServiceProviderPattern2")
public class ServiceProviderPattern2 extends HttpServlet {
     private static final long serialVersionUID = 1L;
     
     @Inject private CityProvider2 provider;
     
     protected void doGet(HttpServletRequest request, 
          HttpServletResponse response) throws ServletException, IOException {
          PrintWriter out = response.getWriter();
          for (String city : provider.getAllCities())
               out.println("Found : " + city);
     }
}
Figure 4. Decoupling between implementation classes and service provider
Decoupling between implementation classes and service provider

Using the @Named and @Inject annotation together, a specific class can be injected without the need of a BeanManager. The example servlet in Listing 11 shows the two used in conjunction to return the class associated with Cologne. This cannot be changed dynamically, but does provide a quick way to reference a specific class.

Listing 11.
@WebServlet("/ServiceProviderPattern3")
public class ServiceProviderPattern3 extends HttpServlet {
     private static final long serialVersionUID = 1L;
     
     @Inject @Named("Cologne") private City city;
     
     protected void doGet(HttpServletRequest request, 
               HttpServletResponse response) throws ServletException, IOException {
          PrintWriter out = response.getWriter();
          out.println("The city is " + city.getLocalName() + " or 
              called " + city.getEnglishName() + " and has a population of " + 
              city.getPopulation());
     }
}
Figure 5. Decoupling between implementation classes and service provider
Decoupling between implementation classes and service provider

CDI requires a beans.xml file to be placed in the WEB-INF directory for web applications and the META-INF folder for EJB applications. These examples do not make use of any features that need to be placed in the beans.xml file.

Listing 12.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi"schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
</beans>

Conclusion

The use of CDI in combination with the Service Provider pattern offers a real dynamic method to lookup and use concrete implementations. It is simple to implement and provides the ability for developers to create pluggable content.


Download

DescriptionNameSize
Code sampleDWCDIExample.ear13 KB

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into WebSphere on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=WebSphere
ArticleID=978921
ArticleTitle=Comment lines: Loose coupling with Context and Dependency Injection
publish-date=07302014