As part of the JSR-299 “Contexts and Dependency Injection for the Java EE platform” (formerly WebBeans), a specification was developed describing the implementation of the dependency injection pattern included in the Java EE 6. The reference implementation is the Weld framework, which will be discussed in this article .
Unfortunately, there is not much Russian-language information about it on the network. Most likely this is due to the fact that Spring IOC is synonymous with dependency injection in Java Enterprise applications. Of course, there is Google Guice, but it is not so popular either.
The article would like to talk about the main advantages and disadvantages of Weld.
')
A 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 for 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; official documentation describes detailed configuration instructions.
In Contexts and Dependency Injection (CDI), it is impossible to inject a bin through its name as a string, as is done for example in Spring through
Qualifier . Instead, use the template qualifier annotations (about which below). From the point of view of the creators of CDI, this is a more typesafe approach, avoiding some errors and providing flexibility to DI.
In order to enable CDI, you need to create the beans.xml file in the WEB-INF directory for the web application (or in META-INF for Java SE):
<beans xmlns = "
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 ">
</ beans>
As in Spring, bins in the context of CDI have their own skup during which they exist. The scop 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 binds directly to the client's bin and exists for as long as the “parent” lives.
@ConversationScoped is a specific amount of time a user interacts with a particular 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 the “session” is set manually. For this, the javax.enterprise.context.Conversation interface has been declared, which defines the start (), end (), and setTimeout (long) methods to close the session after a timeout.
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 scopa, there is one feature: during the injection process, the client receives a link to the real object created by the container, and not the proxy. As a result, there may be data ambiguity problems if the state of a singleton changes, and the bins using it, for example, have been or will be serialized.
To create your own scop, you need to write an annotation and mark it with @ScopeType, and also implement the javax.enterprise.context.spi.Context interface.
A slight confusion may arise with the fact that the javax.faces.bean package also contains annotations for managing the scoped bins of JSF. This is due to the fact that in JSF applications the use of CDI is not necessary: indeed, because you can do with standard injections using @EJB, @PersistenceContext, etc. However, if we want to use advanced things from DI, it is more convenient to use annotations from JSR-299 and 330.
Try on
Suppose there is a service verifying username and password.
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"; }
As you can see from the example, in this case, for injection with 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 associated with the controller's scop. Injections to the method and constructor are naturally supported. The example also uses the
Named annotation; it serves to enable the bean to be addressed to the EL by name.
In the development process, we would like to have our own implementation of the service, something like this:
public class StubLoginService implements ILoginService { @Override public boolean login(String name, String password) { return true; } }
Now after the application redeploy in the console, an error will occur:
WELD-001409 Ambiguous dependencies for type [ILoginService] with qualifiers [@Default] at injection point [[field] @Inject private com.sample.controller.LoginController.loginService].
If several implementations are at the injection point, then Weld throws an exception. CDI developers have provided a solution to this problem. To do this, we note the StubLoginService annotation
Alternative :
@Alternative public class StubLoginService implements ILoginService { … }
Now this implementation is not available for injection and after the redeploy the error will not occur, but now Weld does not quite what we need. Add the following to beans.xml:
<alternatives>
<class> com.sample.service.StubLoginService </ class>
</ alternatives>
Thus, we changed the implementation at the start of the application. To return the working version it is enough to comment out the line in the bins configuration file.
As is usually the case, the specification was updated and the service should now check the hash password using the MD5 algorithm. Add another implementation:
public class Md5LoginService implements ILoginService { @Override public boolean login(String name, String password) {
Now you need to tell Weld that you need to substitute Md5LoginService at the injection point. For this we use qualifier annotations. The idea itself is very simple: when the container decides which implementation to implement, it checks the annotations at the point of injection and the annotations of possible implementations. Checked annotations are called qualifiers. The
specifier is a regular java annotation, which is additionally annotated with
javax .inject.Qualifier:
@Retention(RetentionPolicy.RUNTIME) @Target({FIELD, PARAMETER, TYPE, METHOD}) @Qualifier public @interface Hash { }
Now in the controller, let's proannatiruem the field in which the substitution will be made, 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 to write a new annotation? In most cases, this will have to be done, but this is a typesafe fee. In addition, annotations can be aggregated, for example:
@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 extend the 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;
Those. for the example under consideration, we added a new field with an enumeration type indicating what kind of hashing algorithm it is. Although I had to write a new annotation and enumeration, now it is almost impossible to make a mistake with the choice of the desired implementation, and the readability of the code has also increased. You should also always remember that if an implementation does not have a specifier, then it is added by default to
javax .enterprise.inject.Default.
True, after the done manipulations, StubLoginService ceased to be substituted into the field. This is due to the fact that it does not have the
Hash specifier, so Weld does not even consider it as a possible implementation of the interface. To solve this problem, there is one trick: the @Specializes annotation, which replaces the implementation of another bean. To tell Weld what kind of implementation you need 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 you try to enter a user in the system, you need to try to verify the password with all possible algorithms implemented in the system. Those. we need to go through all the interface implementations. In Spring, this problem is solved through the substitution into the collection generalized to the desired interface. In Weld, you can use the javax.enterprise.inject.Instance <?> Interface and the built-in qualifier
Any . For now, disable the alternative implementations and see what happens:
@Named @RequestScoped public class LoginController { @Inject @Any private Instance<ILoginService> 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"; } }
The
Any annotation says that we do not care what specifiers can be in implementations. The Instance interface implements Iterable, so you can do such beautiful things with it through foreach. In general, this interface is not only for this. It contains an overloaded select () method that allows you to select the desired implementation at runtime. It takes annotation instances as parameters. In general, this is now implemented somewhat “unusually”, since you have to create anonymous classes (or create them separately, just to use them in one place). This is partially solved by the abstract class AnnotationLiteral <?> From which you can expand and generalize to the required annotation. In addition, Instance has special methods isUnsatisfied and isAmbiguous, with which you can check at runtime if there is a suitable implementation and only then get its instance through the get () method. It looks like this:
@Inject @Any Instance<ILoginService> loginServiceInstance; public String doLogin() { Instance<ILoginService> tempInstance = isUltimateVersion ? loginServiceInstance.select(new AnnotationLiteral<Hash>(){}) : loginServiceInstance.select(new AnnotationLiteral<Default>(){}); 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 loop through loginServiceInstance, as was done in the example above, and find the desired implementation by getClass (). Equals (), but then when changing implementations, we had to edit the code in this place too. Weld represents a more flexible and secure approach, albeit adding a little new abstractions to learn.
As noted above, Weld is guided by both the type and specifier when choosing the desired 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 create an instance of a class in some tricky way? Again, Spring in the xml context offers a rich set of tags for initializing properties on objects, creating lists, maps, etc. Weld has for this only one annotation @Produces, which marks methods and fields generating objects (including scalar types). Rewrite our previous example:
@ApplicationScoped public class LoginServiceFactory implements Serializable {
Now we specify through the specifier from where we want to get the implementation:
@Inject @Factory private ILoginService loginService;
That's all. The source can be a regular field. Substitution rules are the same. Moreover, you can also inject bins 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 objects generated by buildLoginService are not bound to the bin in which it is declared, so in this case it will be
Dependent . To change this, simply add an annotation to the method, for example:
@Produces @Factory @SessionScoped private ILoginService buildLoginService( @Hash(HashType.MD5) ILoginService md5LoginService, ILoginService defaultLoginService) { return isUltimateVersion ? md5LoginService : defaultLoginService; }
In addition, you can manually release the resources generated by @Produces. For this, you will not believe, there is another annotation @Disposed, which 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 looks for methods that satisfy the type and specifier of the generators method, as well as those marked @Disposed, and call it.
Conclusion
We have considered far from all the possibilities of JSR-299. In addition, there are a number of additional specifiers, mechanisms for managing the life cycle of bins inside the container (interceptors, decorators), stereotypes, an event model, with which it is convenient to organize complex business logic and many more pleasant trifles.
This article did not want to oppose Weld to other dependency injection frameworks, which were mentioned at the beginning. Weld is self-sufficient and has an interesting implementation worthy of the attention of Java Enterprise developers.
Sources
JSR-299Official Weld DocumentationExcellent JSR-299 introductory article from Oracle engineerA series of articles on CDI support in NetBeans in Russian (
1 ,
2 ,
3 ,
4 )