JavaRush /Java Blog /Random EN /A brief digression into dependency injection or "What els...
Viacheslav
Level 3

A brief digression 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 propose to see what the CDI specification says about this, what basic capabilities we have and how we can use them.
A brief digression 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. See http://cdi-spec.org for information . Since CDI is a specification (a description of how it should work, a set of interfaces), we also need an implementation to use it. One such implementation 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 right away, so as not to understand the abstract. To do this, we will create a project using Maven. Let's open a command prompt (on Windows, you can use Win+R to bring up the "Run" window and execute cmd) and ask Maven to do everything for us. To do this, Maven has such a thing as an archetype: Maven Archetype .
A brief digression 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 " just press Enter. Next, we enter project identifiers, the so-called GAV (see Naming Convention Guide ).
A brief digression into dependency injection, or
After the 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 site - http://www.cdi-spec.org/ . There is a download section which has a table that contains the data we need:
A brief digression into dependency injection, or
Here we can peep how Maven describes the fact that we use the API for CDI 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 works behind this interface. The API is some jar archive that we will start using in our project, that is, our project will begin to depend on this jar. Therefore, the CDI API for our project is dependency. In Maven, a project is described in POM.xml ( POM - Project Object Model ) files. Dependencies are described in the dependencies block, where 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 provided value. Why such a difference? Such a scope means that someone will provide us with a dependency. When an application runs on a Java EE server, this means that the server will provide the application with all the necessary JEE technologies. For the sake of simplicity, this review will work in a Java SE environment, so 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, 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>
Importantly, third-line versions of Weld support CDI 2.0. Therefore, we can rely on the API of this version. Now we are ready to write the code.
A brief digression into dependency injection, or

CDI container initialization

CDI is a mechanism. Someone has to manage this mechanism. As we have already read above, the container is such a manager. Therefore, we need to create it, it will not appear in the SE environment itself. 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 have created the CDI container manually. working in SE environment. In normal combat projects, the code runs on a server that exposes the code to various technologies. Accordingly, if the server provides CDI, then this means that the server already has a CDI container and we will not need to add anything. But for the purposes of the lesson, we will take the SE environment. In addition, the container is here, clearly and clearly. Why do we need a container? The container inside contains beans (CDI beans).
A brief digression into dependency injection, or

CDI Beans

So, bins. What is a CDI bean? This is a Java class that conforms to some rules. These rules are described in the specification, in chapter " 2.2. What kinds of classes are beans? ". Let's add the 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 a bean using the new keyword. We asked the CDI container: "CDI container. I really need an instance of the Logger class, please give it to me." This method is called " Dependency lookup ", that is, the search 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 a 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 chapter " 4.1. Injection points " of the cdi weld documentation, with this annotation we define an Injection Point. In Russian, this can be read as "points of introduction". They are used by the CDI container to inject dependencies at the time of bean instantiation. As you can see, we are not assigning any values ​​to the dateSource field (date source). The reason for this is the fact that the CDI container allows inside CDI beans (only those beans that it itself instantiated, i.e., which it manages) to use " Dependency Injection ". This is another way Inversion of Control, an approach where the dependency is managed by someone else, and not by us explicitly creating objects. Dependency injection can be done through a method, constructor, or field. See the CDI specification chapter " 5.5. Dependency injection " for details. The procedure for determining what needs to be implemented is called typesafe resolution, which is what we should talk about.
A brief digression into dependency injection, or

Name resolution or Typesafe resolution

Usually, an interface is used as the type of the injected object, 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 him a message and he will complete his task - logging. How and where - in this case will not be of interest. Now let's 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 receive the logger. And the beauty is that it is enough for us 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 from them: org.jboss.weld.exceptions.AmbiguousResolutionException: WELD-001335: Ambiguous dependencies for type Logger What to do? There are several variations available. The simplest is the @Vetoed annotation to give us 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 "alternative" using the annotation @Alternativedescribed in chapter " 4.7. Alternatives"." of the Weld CDI documentation. What does this mean? It means that unless we explicitly say that we need to use it, it will not be selected. This is an alternative bean option. Let's mark the NetworkLogger bean as @Alternative and we will see that the code is executed again and using SystemOutLogger.To enable the alternative we need to have a beans.xml file.The question might be: " beans.xml, where do I put you? ". Therefore, let's place the file correctly:
A brief digression 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: program and xml. The problem is that they will load the same data. For example, the definition of the DataSource bean will be loaded 2 times and our program will crash when executed. 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). There are 2 options to avoid this:
  • 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.codegym.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 a CDI container knows several alternatives for the same interface, then we can prompt it by specifying the priority using an annotation @Priority(Since CDI 1.1).
A brief digression 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 in more detail. 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 @Anyone more qualifier to the qualifier - @Default. If we specify at least something (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 annotations, because in fact, this is just an annotation written in a special way. For example, you can enter an 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 definition of the qualifier. Now we need to specify the qualifier. It is specified above the bean type (so that CDI knows how to determine it) and above the Injection Point (with the @Inject annotation to understand which bean to look for to inject in this place). For example, we can add some class with a qualifier. For simplicity, for this article, we will make 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 execute Inject, we will specify a qualifier that will affect which class will be used:
@Inject
@Protocol(ProtocolType.HTTPS)
private Sender sender;
It's great, isn't it?) It seems that it's beautiful, but it's not clear 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);
Thus, we can redefine getting the value of 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 a lot more interesting, doesn't it? )
A brief digression into dependency injection, or

Producers

Producers are another useful feature of CDI. These are special methods (they are marked with a special annotation) that are called when some bean has requested dependency injection. It is described in more detail 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 it is worth immediately understanding that when we see the new keyword, 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 digression into dependency injection, or

Interceptors

Interceptors are such interceptors that "wedged" into the work. CDI makes this pretty clear. Let's see how we can do logging with interceptors (or interceptors). First, we need to describe the binding to the spoiler. 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 when 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, we just have to turn on the inerceptor. To do this, we specify the binding annotation above the executed method:
@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.codegym.LogInterceptor</class>
</interceptors>
As you can see, it's pretty simple.
A brief digression into dependency injection, or

Event & Observers

CDI also provides an event model and observers. It's not as obvious as with spoilers. So, in this case, absolutely any class can be an Event, nothing special is needed to describe it. For example:
public class LogEvent {
    Date date = new Date();
    public String getDate() {
        return date.toString();
    }
}
Now the event should be expected by someone:
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 observe:
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 a LogEvent event type. Now it remains only to use the observer. For example, in NetworkLogger we can inject 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 here that events can be processed in one thread and in several. For asynchronous processing, there is 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 falls with an Exception, then the rest 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 digression into dependency injection, or

Decorators

As we saw above, various design patterns are collected under the wing of CDI. And here is another 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 implementation of the Logger is used, this "add-on" will be used, which knows the real implementation, which is stored in the delegate field (because it is marked with the annotation ) @Delegate. Decorators can only be associated with a CDI bean that is neither an interceptor nor a decorator itself. An example can also be seen in the specification: " 1.3.7. Decorator example ". The decorator, like the interceptor, must be included. For example, in beans.xml :
<decorators>
	<class>ru.codegym.LoggerDecorator</class>
</decorators>
See weld reference: " Chapter 10. Decorators " for details.

Life cycle

Beans have their own life cycle. It looks something like this:
A brief digression 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 point in the bean's life cycle. For example:
@PostConstruct
public void init() {
	System.out.println("Inited");
}
Such a method will be called when the CDI bean is instantiated by the container. It will be similar with @PreDestroy when the bean is destroyed when it is no longer needed. In the abbreviation CDI, it is not in vain that there is a letter C - Context. Beans in CDI are contextual, that is, their life cycle depends on the context in which they exist inside the CDI container. To better understand this, it is worth reading the section of the specification " 7. Lifecycle of contextual instances ". It's also worth knowing that the container itself has a lifecycle, which you can read about in " Container lifecycle events ".
A brief digression 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 readjust. Given 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