JavaRush /Java Blog /Random EN /A short excursion into dependency injection or "What else...
Viacheslav
Level 3

A short excursion into dependency injection or "What else is CDI?"

Published in the Random EN group
The foundation on which the most popular frameworks are now built is dependency injection. I suggest looking at what the CDI specification says about this, what basic capabilities we have and how we can use them.
A brief excursion into dependency injection or

Introduction

I would like to devote this short review to such a thing as CDI. What is this? CDI stands for Contexts and Dependency Injection. This is a Java EE specification that describes Dependency Injection and contexts. For information, you can look at the website http://cdi-spec.org . Since CDI is a specification (a description of how it should work, a set of interfaces), we will also need an implementation to use it. One of such implementations is Weld - http://weld.cdi-spec.org/ To manage dependencies and create a project, we will use Maven - https://maven.apache.org So, we have Maven installed, now we will understand it in practice , so as not to understand the abstract. To do this, we will create a project using Maven. Let's open the command line (in Windows, you can use Win+R to open the "Run" window and execute cmd) and ask Maven to do everything for us. For this, Maven has a concept called an archetype: Maven Archetype .
A brief excursion into dependency injection or
After that, on the questions “ Choose a number or apply filter ” and “ Choose org.apache.maven.archetypes:maven-archetype-quickstart version ” simply press Enter. Next, enter the project identifiers, the so-called GAV (see Naming Convention Guide ).
A brief excursion into dependency injection or
After successful creation of the project, we will see the inscription “BUILD SUCCESS”. Now we can open our project in our favorite IDE.

Adding CDI to a Project

In the introduction, we saw that CDI has an interesting website - http://www.cdi-spec.org/ . There is a download section, which contains a table that contains the data we need:
A brief excursion into dependency injection or
Here we can see how Maven describes the fact that we use the CDI API in the project. API is an application programming interface, that is, some programming interface. We work with the interface without worrying about what and how it works behind this interface. The API is a jar archive that we will begin to use in our project, that is, our project begins to depend on this jar. Therefore, the CDI API for our project is a dependency. In Maven, a project is described in POM.xml files ( POM - Project Object Model ). Dependencies are described in the dependencies block, to which we need to add a new entry:
<dependency>
	<groupId>javax.enterprise</groupId>
	<artifactId>cdi-api</artifactId>
	<version>2.0</version>
</dependency>
As you may have noticed, we do not specify scope with the value provided. Why is there such a difference? This scope means that someone will provide us with the dependency. When an application runs on a Java EE server, it means that the server will provide the application with all the necessary JEE technologies. For the sake of simplicity of this review, we will work in a Java SE environment, therefore no one will provide us with this dependency. You can read more about Dependency Scope here: " Dependency Scope ". Okay, we now have the ability to work with interfaces. But we also need implementation. As we remember, we will use Weld. It is interesting that different dependencies are given everywhere. But we will follow the documentation. Therefore, let's read " 18.4.5. Setting the Classpath " and do as it says:
<dependency>
	<groupId>org.jboss.weld.se</groupId>
	<artifactId>weld-se-core</artifactId>
	<version>3.0.5.Final</version>
</dependency>
It is important that the third line versions of Weld support CDI 2.0. Therefore, we can count on the API of this version. Now we are ready to write code.
A brief excursion into dependency injection or

Initializing a CDI container

CDI is a mechanism. Someone must control this mechanism. As we have already read above, such a manager is a container. Therefore, we need to create it; it itself will not appear in the SE environment. Let's add the following to our main method:
public static void main(String[] args) {
	SeContainerInitializer initializer = SeContainerInitializer.newInstance();
	initializer.addPackages(App.class.getPackage());
	SeContainer container = initializer.initialize();
}
We created the CDI container manually because... We work in an SE environment. In typical combat projects, the code runs on a server, which provides various technologies to the code. Accordingly, if the server provides CDI, this means that the server already has a CDI container and we will not need to add anything. But for the purposes of this tutorial, we'll take the SE environment. In addition, the container is here, clearly and understandably. Why do we need a container? The container inside contains beans (CDI beans).
A brief excursion into dependency injection or

CDI Beans

So, beans. What is a CDI bin? This is a Java class that follows some rules. These rules are described in the specification, in the chapter " 2.2. What kinds of classes are beans? ". Let's add a CDI bean to the same package as the App class:
public class Logger {
    public void print(String message) {
        System.out.println(message);
    }
}
Now we can call this bean from our mainmethod:
Logger logger = container.select(Logger.class).get();
logger.print("Hello, World!");
As you can see, we did not create the bean using the new keyword. We asked the CDI container: “CDI container. I really need an instance of the Logger class, give it to me please.” This method is called " Dependency lookup ", that is, searching for dependencies. Now let's create a new class:
public class DateSource {
    public String getDate() {
        return new Date().toString();
    }
}
A primitive class that returns a text representation of a date. Let's now add the date output to the message:
public class Logger {
    @Inject
    private DateSource dateSource;

    public void print(String message) {
        System.out.println(dateSource.getDate() + " : " + message);
    }
}
An interesting @Inject annotation has appeared. As stated in the chapter " 4.1. Injection points " of the cdi weld documentation, using this annotation we define the Injection Point. In Russian, this can be read as “implementation points”. They are used by the CDI container to inject dependencies when instantiating beans. As you can see, we are not assigning any values ​​to the dateSource field. The reason for this is the fact that the CDI container allows inside CDI beans (only those beans that it itself instantiated, i.e. that it manages) to use “ Dependency Injection ”. This is another way of Inversion of Control , an approach where the dependency is controlled by someone else rather than us explicitly creating the objects. Dependency injection can be done through a method, constructor, or field. For more details, see the CDI specification chapter " 5.5. Dependency injection ". The procedure for determining what needs to be implemented is called typesafe resolution, which is what we need to talk about.
A brief excursion into dependency injection or

Name resolution or Typesafe resolution

Typically, an interface is used as the type of object to be implemented, and the CDI container itself determines which implementation to choose. This is useful for many reasons, which we will discuss. So we have a logger interface:
public interface Logger {
    void print(String message);
}
He says that if we have some logger, we can send a message to it and it will complete its task - log. How and where will not be of interest in this case. Let's now create an implementation for the logger:
public class SystemOutLogger implements Logger {
    @Inject
    private DateSource dateSource;

    public void print(String message) {
        System.out.println(message);
    }
}
As you can see, this is a logger that writes to System.out. Wonderful. Now, our main method will work as before. Logger logger = container.select(Logger.class).get(); This line will still be received by the logger. And the beauty is that we only need to know the interface, and the CDI container already thinks about the implementation for us. Let's say we have a second implementation that should send the log somewhere to a remote storage:
public class NetworkLogger implements Logger {
    @Override
    public void print(String message) {
        System.out.println("Send log message to remote log system");
    }
}
If we now run our code without changes, we will get an error, because The CDI container sees two implementations of the interface and cannot choose between them: org.jboss.weld.exceptions.AmbiguousResolutionException: WELD-001335: Ambiguous dependencies for type Logger What to do? There are several variations available. The simplest one is the @Vetoed annotation for a CDI bean so that the CDI container does not perceive this class as a CDI bean. But there is a much more interesting approach. A CDI bean can be marked as an "alternative" using the annotation @Alternativedescribed in the " 4.7. Alternatives " chapter of the Weld CDI documentation. What does it mean? This means that unless we explicitly say to use it, it will not be selected. This is an alternative version of the bean. Let's mark the NetworkLogger bean as @Alternative and we can see that the code is executed again and used by SystemOutLogger. To enable the alternative, we must have a beans.xml file . The question may arise: " beans.xml, where do I put you? " Therefore, let's place the file correctly:
A brief excursion into dependency injection or
As soon as we have this file, the artifact with our code will be called “ Explicit bean archive ”. Now we have 2 separate configurations: software and xml. The problem is that they will load the same data. For example, the DataSource bean definition will be loaded 2 times and our program will crash when executed, because The CDI container will think of them as 2 separate beans (although in fact they are the same class, which the CDI container learned about twice). To avoid this there are 2 options:
  • remove the line initializer.addPackages(App.class.getPackage())and add an indication of the alternative to the xml file:
<beans
    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://xmlns.jcp.org/xml/ns/javaee
        http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd">
    <alternatives>
        <class>ru.javarush.NetworkLogger</class>
    </alternatives>
</beans>
  • add an attribute bean-discovery-modewith the value " none " to the beans root element and specify an alternative programmatically:
initializer.addPackages(App.class.getPackage());
initializer.selectAlternatives(NetworkLogger.class);
Thus, using the CDI alternative, the container can determine which bean to select. Interestingly, if the CDI container knows several alternatives for the same interface, then we can tell it by indicating the priority using an annotation @Priority(Since CDI 1.1).
A brief excursion into dependency injection or

Qualifiers

Separately, it is worth discussing such a thing as qualifiers. The qualifier is indicated by an annotation above the bean and refines the search for the bean. And now more details. Interestingly, any CDI bean in any case has at least one qualifier - @Any. If we do not specify ANY qualifier above the bean, but then the CDI container itself adds @Anyanother qualifier to the qualifier - @Default. If we specify anything (for example, explicitly specify @Any), then the @Default qualifier will not be automatically added. But the beauty of qualifiers is that you can make your own qualifiers. The qualifier is almost no different from the annotations, because in essence, this is just an annotation written in a special way. For example, you can enter Enum for the protocol type:
public enum ProtocolType {
    HTTP, HTTPS
}
Next we can make a qualifier that will take this type into account:
@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface Protocol {
    ProtocolType value();
    @Nonbinding String comment() default "";
}
It is worth noting that fields marked as @Nonbindingdo not affect the determination of the qualifier. Now you need to specify the qualifier. It is indicated above the bean type (so that CDI knows how to define it) and above the Injection Point (with the @Inject annotation so that you understand which bean to look for for injection in this place). For example, we can add some class with a qualifier. For simplicity, for this article we will do them inside the NetworkLogger:
public interface Sender {
	void send(byte[] data);
}

@Protocol(ProtocolType.HTTP)
public static class HTTPSender implements Sender{
	public void send(byte[] data) {
		System.out.println("sended via HTTP");
	}
}

@Protocol(ProtocolType.HTTPS)
public static class HTTPSSender implements Sender{
	public void send(byte[] data) {
		System.out.println("sended via HTTPS");
	}
}
And then when we perform Inject, we will specify a qualifier that will influence which class will be used:
@Inject
@Protocol(ProtocolType.HTTPS)
private Sender sender;
Great, isn't it?) It seems beautiful, but it's unclear why. Now imagine the following:
Protocol protocol = new Protocol() {
	@Override
	public Class<? extends Annotation> annotationType() {
		return Protocol.class;
	}
	@Override
	public ProtocolType value() {
		String value = "HTTP";
		return ProtocolType.valueOf(value);
	}
};
container.select(NetworkLogger.Sender.class, protocol).get().send(null);
This way we can override getting value so that it can be calculated dynamically. For example, it can be taken from some settings. Then we can change the implementation even on the fly, without recompiling or restarting the program/server. It gets much more interesting, doesn't it? )
A brief excursion into dependency injection or

Producers

Another useful feature of CDI is producers. These are special methods (they are marked with a special annotation) that are called when some bean has requested dependency injection. More details are described in the documentation, in section " 2.2.3. Producer methods ". The simplest example:
@Produces
public Integer getRandomNumber() {
	return new Random().nextInt(100);
}
Now, when Injecting into fields of type Integer, this method will be called and a value will be obtained from it. Here we should immediately understand that when we see the keyword new, we must immediately understand that this is NOT a CDI bean. That is, an instance of the Random class will not become a CDI bean just because it is derived from something that controls the CDI container (in this case, the producer).
A brief excursion into dependency injection or

Interceptors

Interceptors are interceptors that “interfere” into the work. In CDI this is done quite clearly. Let's see how we can do logging using interpreters (or interceptors). First, we need to describe the binding to the interceptor. Like many things, this is done using annotations:
@Inherited
@InterceptorBinding
@Target({TYPE, METHOD})
@Retention(RUNTIME)
public @interface ConsoleLog {
}
The main thing here is that this is a binding for the interceptor ( @InterceptorBinding), which will be inherited by extends ( @InterceptorBinding). Now let's write the interceptor itself:
@Interceptor
@ConsoleLog
public class LogInterceptor {
    @AroundInvoke
    public Object log(InvocationContext ic) throws Exception {
        System.out.println("Invocation method: " + ic.getMethod().getName());
        return ic.proceed();
    }
}
You can read more about how interceptors are written in the example from the specification: " 1.3.6. Interceptor example ". Well, all we have to do is turn on the inerceptor. To do this, specify the binding annotation above the method being executed:
@ConsoleLog
public void print(String message) {
And now another very important detail. Interceptors are disabled by default and must be enabled in the same way as the alternatives. For example, in the beans.xml file :
<interceptors>
	<class>ru.javarush.LogInterceptor</class>
</interceptors>
As you can see, it's quite simple.
A brief excursion into dependency injection or

Event & Observers

CDI also provides a model of events and observers. Here everything is not as obvious as with interceptors. So, the Event in this case can be absolutely any class; nothing special is needed for the description. For example:
public class LogEvent {
    Date date = new Date();
    public String getDate() {
        return date.toString();
    }
}
Now someone should wait for the event:
public class LogEventListener {
    public void logEvent(@Observes LogEvent event){
        System.out.println("Message Date: " + event.getDate());
    }
}
The main thing here is to specify the @Observes annotation, which indicates that this is not just a method, but a method that should be called as a result of observing events of the LogEvent type. Well, now we need someone who will watch:
public class LogObserver {
    @Inject
    private Event<LogEvent> event;
    public void observe(LogEvent logEvent) {
        event.fire(logEvent);
    }
}
We have a single method that will tell the container that an Event event has occurred for the event type LogEvent. Now all that remains is to use the observer. For example, in NetworkLogger we can add an injection of our observer:
@Inject
private LogObserver observer;
And in the print method we can notify the observer that we have a new event:
public void print(String message) {
	observer.observe(new LogEvent());
It is important to know that events can be processed in one thread or in several. For asynchronous processing, use a method .fireAsync(instead of .fire) and an annotation @ObservesAsync(instead of @Observes). For example, if all events are executed in different threads, then if 1 thread throws an Exception, then the others will be able to do their work for other events. You can read more about events in CDI, as usual, in the specification, in the chapter " 10. Events ".
A brief excursion into dependency injection or

Decorators

As we saw above, various design patterns are collected under the CDI wing. And here's another one - a decorator. This is a very interesting thing. Let's take a look at this class:
@Decorator
public abstract class LoggerDecorator implements Logger {
    public final static String ANSI_GREEN = "\u001B[32m";
    public static final String ANSI_RESET = "\u001B[0m";

    @Inject
    @Delegate
    private Logger delegate;

    @Override
    public void print(String message) {
        delegate.print(ANSI_GREEN + message + ANSI_RESET);
    }
}
By declaring it a decorator, we say that when any Logger implementation is used, this “add-on” will be used, which knows the real implementation, which is stored in the delegate field (since it is marked with the annotation @Delegate). Decorators can only be associated with a CDI bean, which itself is neither an interceptor nor a decorator. An example can also be seen in the specification: " 1.3.7. Decorator example ". The decorator, like the interceptor, must be turned on. For example, in beans.xml :
<decorators>
	<class>ru.javarush.LoggerDecorator</class>
</decorators>
For more details, see weld reference: " Chapter 10. Decorators ".

Life cycle

Beans have their own life cycle. It looks something like this:
A brief excursion into dependency injection or
As you can see from the picture, we have so-called lifecycle callbacks. These are annotations that will tell the CDI container to call certain methods at a certain stage in the bean's lifecycle. For example:
@PostConstruct
public void init() {
	System.out.println("Inited");
}
This method will be called when a CDI bean is instantiated by a container. The same will happen with @PreDestroy when the bean is destroyed when it is no longer needed. It’s not for nothing that the acronym CDI contains the letter C - Context. Beans in CDI are contextual, meaning their life cycle depends on the context in which they exist within the CDI container. To understand this better, you should read the specification section “ 7. Lifecycle of contextual instances ”. It’s also worth knowing that the container itself has a life cycle, which you can read about in “ Container lifecycle events ”.
A brief excursion into dependency injection or

Total

Above we looked at the very tip of the iceberg called CDI. CDI is part of the JEE specification and is used in the JavaEE environment. Those who use Spring do not use CDI, but DI, that is, these are slightly different specifications. But knowing and understanding the above, you can easily change your mind. Considering that Spring supports annotations from the CDI world (the same Inject). Additional materials: #Viacheslav
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION