Dependency injection in Java EE 6

Within the framework of JSR-299 “Contexts and Dependency Injection for the Java EE platform” (formerly WebBeans), a specification was developed that describes the implementation of the dependency injection pattern included in Java EE 6. The reference implementation is the Weld framework, which will be discussed in this article .

Unfortunately, the network does not have much Russian-language information about him. This is most likely due to the fact that Spring IOC is synonymous with dependency injection in Java Enterprise applications. Of course there is also Google Guice, but it is also not so popular.

In the article I would like to talk about the main advantages and disadvantages of Weld.

Bit of theory


In the beginning, it is worth mentioning the JSR-330 “Dependency Injection for Java” specification, developed by engineers from SpringSource and Google, which defines the basic mechanisms for implementing DI in Java applications. Like Spring and Guice, Weld uses the annotations provided by this specification.

Weld can work not only with Java EE applications, but also with the usual Java SE environment. Naturally there is support for Tomcat, Jetty; the official documentation describes detailed setup instructions.

In Contexts and Dependency Injection (CDI), it is not possible to inject a bin through its name as a string, as is done for example in Spring through Qualifier. Instead, the qualifier annotations template (which is described below) is used. From the point of view of the creators of CDI, this is a more typesafe approach that avoids some errors and provides DI flexibility.

In order to use CDI, you need to create a beans.xml file in the WEB-INF directory for the web application (or in META-INF for Java SE):

java.sun.com/xml/ns/javaee "
xmlns: xsi =" www.w3.org/2001/XMLSchema-instance "
xsi: schemaLocation =" java.sun.com/xml/ns/javaee java.sun.com /xml/ns/javaee/beans_1_0.xsd »>



Like Spring, beans in the CDI context have their own scopes during which they exist. The scope is set using annotations from the javax.enterprise.context package:
  • @RequestScoped
  • @SessionScoped
  • @ApplicationScoped
  • Dependent
  • @ConversationScoped
With the first three scopes, everything is clear. Used by default in CDI, Dependent is bound directly to the client bean and exists all the time while the “parent” lives.

@ConversationScoped represents a certain period of time for a user to interact with a specific tab in a browser. Therefore, it is somewhat similar to the life cycle of a session, but the important difference is that the start of a “session” is set manually. To do this, the javax.enterprise.context.Conversation interface is declared, which defines the start (), end () methods, as well as setTimeout (long), to close the session after a time.

Of course, there is Singleton, which is in the javax.inject package, because is part of the JSR-330 specification. In the CDI implementation of this scope, there is one feature: during the injection process, the client receives a link to the real object created by the container, not the proxy. As a result, there may be problems of data ambiguity if the state of a singleton changes, and the beans using it, for example, are or will be serialized.

To create your own scope, you need to write an annotation and mark it with @ScopeType, as well as implement the javax.enterprise.context.spi.Context interface.

A little confusion may arise with the fact that the javax.faces.bean package also contains annotations for managing scoped managed JSF beans. This is due to the fact that in JSF applications the use of CDI is not necessary: ​​indeed, you can get by with standard injections using @EJB, @PersistenceContext, etc. However, if we want to use advanced pieces from DI, it is more convenient to use annotations from JSR-299 and 330.

Example


Suppose there is a service that checks the username and password of the user.

public interface ILoginService extends Serializable {
    boolean login(String name, String password);
}


Let's write its implementation:

public class LoginService implements ILoginService {
    @Override
    public boolean login(String name, String password) {
        return "bugs".equalsIgnoreCase(name) && "bunny".equalsIgnoreCase(password);
    }
}


Now add a controller that will use the login service:

@Named
@RequestScoped
public class LoginController {
    @Inject
    private ILoginService loginService;
    private String login;
    private String password;
    public String doLogin() {
        return loginService.login(login, password) ? "main.xhtml" : "failed.xhtml";
    }
    // getters and setters will be omitted...
}


As you can see from the example, in this case, for injection using Weld, you need to add the Inject annotation in the required field: the container will find all possible implementations of the interface, select the appropriate one and create an object attached to the controller scope. Injections into the method and constructor are naturally supported. The example also uses the Named annotation ; it serves to ensure that the bean can be accessed in EL by name.

In the development process, we would like to have our own implementation of the service, approximately of the following content:

public class StubLoginService implements ILoginService {
    @Override
    public boolean login(String name, String password) {
        return true;
    }
}


Now, after re-deploying the application, an error will appear in the console:

WELD-001409 Ambiguous dependencies for type [ILoginService] with qualifiers [@Default] at injection point [[field] @Inject private com.sample.controller.LoginController.loginService].


If multiple implementations are suitable at the injection point, Weld throws an exception. CDI developers have provided a solution to this problem. To do this, mark the StubLoginService with the Alternative annotation :

@Alternative
public class StubLoginService implements ILoginService {
  …
}


Now this implementation is not available for injection and after re-deployment there will not be an error, but now Weld is not doing exactly what we need. Add the following to beans.xml:


com.sample.service.StubLoginService


Thus, we replaced the implementation at application startup. To return the working version, it is enough to comment out the line in the bin settings file.

As it usually happens, the specification was updated and now the service should check the password by hash using the MD5 algorithm. Add another implementation:

public class Md5LoginService implements ILoginService {
    @Override
    public boolean login(String name, String password) {
        // делаем проверку...
    }
}


Now we need to tell Weld that it is the Md5LoginService that needs to be substituted at the injection point. We will use qualifier annotations for this. The idea itself is very simple: when the container decides which implementation to implement, it checks the annotations at the injection point and the annotations for possible implementations. Checked annotations are called qualifiers. The specifier is a regular java annotation, which is additionally annotated by javax .inject.Qualifier:

@Retention(RetentionPolicy.RUNTIME)
@Target({FIELD, PARAMETER, TYPE, METHOD})
@Qualifier
public @interface Hash {
}


Now, in the controller, let us annul the field into which the substitution will be performed, as well as the implementation of Md5LoginService:

@Hash
public class Md5LoginService implements ILoginService {
}
@Named
@RequestScoped
public class LoginController {
    @Inject
    @Hash
    private ILoginService loginService;
}


Now Weld will substitute the desired implementation. Of course, the question arises: each time write a new annotation ?! In most cases, you will have to do so, but this fee is for typesafe. In addition, annotations can be aggregated, for example as follows:

@Hash @Fast
public class Md5LoginService implements ILoginService {
}
@Named
@RequestScoped
public class LoginController {
    @Inject
    @Hash @Fast
    private ILoginService loginService;
}


Moreover, in order not to create a large number of identical annotations, you can expand annotations with fields and Weld will take this into account:

@Retention(RetentionPolicy.RUNTIME)
@Target({FIELD, PARAMETER, TYPE, METHOD})
@Qualifier
public @interface Hash {
    HashType value() default HashType.SHA;
    // Поля помеченные аннотацией @Nonbinding при выборе реализации учитываться не будут
    @Nonbinding String desc() default "";
}
@Hash(HashType.MD5)
public class Md5LoginService implements ILoginService {
}
@Named
@RequestScoped
public class LoginController {
    @Inject
    @Hash(HashType.MD5)
    private ILoginService loginService;
}


Those. for the example in question, we added a new field with an enumeration type indicating which particular hashing algorithm. Although it was necessary to write a new annotation and enumeration, now it is almost impossible to make a mistake with the choice of the necessary implementation, and the code readability has also increased. Also, you should always remember that if the implementation does not have a qualifier, then it is added by default javax .enterprise.inject.Default.

True, after the manipulations done, the StubLoginService stopped substituting into the field. This is because it does not have a Hash specifier. , therefore, Weld does not even consider it as a possible implementation of the interface. There is one trick to solving this problem: the @Specializes annotation, which replaces the implementation of another bean. To tell Weld which particular implementation to replace, you just need to expand it:

@Alternative @Specializes 
public class StubLoginService extends Md5LoginService {
    @Override
    public boolean login(String name, String password) {
        return true;
    }
}


Imagine that we have new requirements: when trying to enter a user into the system, you need to try to verify the password with all possible algorithms implemented in the system. Those. we need to iterate over all the implementations of the interface. In Spring, this problem is solved by substituting into a collection generalized by the desired interface. In Weld, you can use the javax.enterprise.inject.Instance interface and the Any built-in specifier for this . For now, disable alternative implementations and see what happens:

@Named
@RequestScoped
public class LoginController {
    @Inject
    @Any
    private Instance loginService;
    private String login;
    private String password;
    public String doLogin() {
        for (ILoginService service : loginService) {
            if (service.login(login, password))
                return "main.xhtml";
        }
        return "failed.xhtml";
    }
}


Any annotation suggests that we don't care what kind of qualifiers an implementation might have. The Instance interface implements Iterable, so you can do such beautiful things with it through foreach. In general, this interface is intended not only for this. It contains an overloaded select () method, which allows runtime to select the desired implementation. As parameters, it takes instances of annotations. In general, this is now implemented somewhat “unusual”, because you have to create anonymous classes (or create separately, just to use in one place). This is partially solved by the abstract class AnnotationLiteralfrom which you can expand and summarize the desired annotation. In addition to this, Instance has special methods isUnsatisfied and isAmbiguous, with which you can check in runtime if there is a suitable implementation and only then get its instance through the get () method. It looks something like this:

@Inject
@Any
Instance loginServiceInstance;
public String doLogin() {
        Instance tempInstance = isUltimateVersion ? 
                loginServiceInstance.select(new AnnotationLiteral(){}) :
                loginServiceInstance.select(new AnnotationLiteral(){});
        if (tempInstance.isAmbiguous() || tempInstance.isUnsatisfied()) {
            throw new IllegalStateException("Не могу найти подходящую реализацию");
        }
        return tempInstance.get().login(login, password) ? "main.xhtml" : "failed.xhtml";
    }


It is clear that in this case it was possible to go through a loop on loginServiceInstance, as was done in the example above, and find the desired implementation using getClass (). Equals (), but then when changing the implementations, I had to edit the code in this place too. Weld introduces a more flexible and safer approach, albeit adding a bit of new abstractions to explore.

As noted above, Weld is guided by both type and specifier when choosing the right implementation. But in some cases, for example with a complex inheritance hierarchy, we can specify the type of implementation manually, using the @Typed annotation.

This is all well and good, but what to do when we need to instantiate a class in some tricky way? Again, Spring in the xml context offers a rich set of tags for initializing properties of objects, creating lists, maps, etc. Weld has only one @Produces annotation for this, which marks methods and fields that generate objects (including scalar types). We rewrite our previous example:

@ApplicationScoped
public class LoginServiceFactory implements Serializable {
    // @Factory - кастомный спецификатор
    @Produces @Factory
    public ILoginService buildLoginService() {
        return isUltimateVersion ? new Md5LoginService() : new LoginService();
    }
}


Now we indicate through the specifier where we want to get the implementation from:

    @Inject @Factory
    private ILoginService loginService;


That's all. A regular field can also be a source. Substitution rules are the same. Moreover, you can also inject beans into the buildLoginService method:

 @Produces @Factory
    private ILoginService buildLoginService(
            @Hash(HashType.MD5) ILoginService md5LoginService,
            ILoginService defaultLoginService) {
        return isUltimateVersion ? md5LoginService : defaultLoginService;
    }


As you can see, the access modifier has no effect. The scope of objects generated by buildLoginService is not tied to the scope of the bean in which it is declared, so in this case it will be Dependent . To change this, just add an annotation to the method, for example like this:

@Produces @Factory @SessionScoped
    private ILoginService buildLoginService(
            @Hash(HashType.MD5) ILoginService md5LoginService,
            ILoginService defaultLoginService) {
        return isUltimateVersion ? md5LoginService : defaultLoginService;
}


In addition, you can manually release resources generated using @Produces. For this, you will not believe there is another @Disposed annotation that works something like this:

private void dispose(@Disposes @Factory ILoginService service) {
        log.info("LoginService disposed"); 
}


When the object's life cycle comes to an end, Weld searches for methods that match the type and specifier of the generator method, as well as tagged @Disposed and calls it.

Conclusion


We have considered far from all the features of the JSR-299. In addition, there are a number of additional specifiers, mechanisms for managing the life cycle of beans inside the container (interceptors, decorators), stereotypes, an event model, with the help of which it is convenient to organize complex business logic and many, many nice little things.

This article did not want to contrast Weld with the other dependency injection frameworks that were discussed at the beginning. Weld is self-contained and has an interesting implementation worthy of the attention of Java Enterprise developers.

Sources


JSR-299
Official Weld Documentation
An excellent introductory article on JSR-299 from an Oracle engineer
A series of NetBeans CDI support articles in Russian ( 1 , 2 , 3 , 4 )

Also popular now: