📜 ⬆️ ⬇️

The book "Learning Java EE. Modern programming for large enterprises »

image Hi Habr!

This book describes the new generation of Java EE. You will go on a journey through Java EE in the context of the modern world of microservices and containers. Rather, it is not an API syntax guide — the concepts and methodologies outlined here reflect the real experience of the person who himself recently walked this path, paying close attention to the obstacles encountered and is willing to share his knowledge. In various situations, starting with creating a package for testing and cloud use, this book will be an ideal companion for both beginners and experienced developers looking to understand more than just an API, and help them rebuild their thinking to create an architecture of modern applications in Java EE .

Sequence of execution


Business processes implemented in enterprise applications describe specific process flows. For the involved business scenarios, this is either a process of synchronous requests and responses, or asynchronous processing of the initiated process.

Business scripts are called in separate threads, one by one for a request or a call. Flows are created by the container and placed in the drive for reuse after the call has been successfully processed. By default, business processes defined in application classes, as well as end-to-end tasks, such as transactions, are performed sequentially.
')

Synchronous execution


A typical scenario where an HTTP request for a response from a database is implemented as follows. One thread processes the request coming into the loop, for example, JAX-RS UsersResource, by inverting the control principle; The JAX-RS resource method is called by the container. A resource embeds and uses an UserManagement EJB object, which is also implicitly called by the container. All operations are performed by intermediaries synchronously. The EJB will use the entity manager to store the User entity, and as soon as the business method that initiated the current active transaction is finished, the container will attempt to commit the transaction to the database. Depending on the result of the transaction, the resource method of the circuit resumes operation and generates a response to the client. Everything happens synchronously, at this time the client is blocked and waiting for a response.

Synchronous execution involves processing synchronous CDI events. They separate the triggering of domain events from their processing, however, events are processed synchronously. There are several transaction monitoring methods. If a transaction stage is specified, the event can be processed at this stage — while the transaction is committed, before it is completed, after completion, in the event of a unsuccessful or successful transaction. By default or when a transaction is inactive, CDI events are processed as soon as they occur. This allows engineers to implement complex solutions — for example, using events that occur only after successfully adding entities to the database. Be that as it may, in all cases the processing is performed synchronously.

Asynchronous execution


Synchronous execution of tasks meets the requirements of many business scenarios, but there are times when asynchronous behavior is needed. In the Java EE environment, there are a number of restrictions on the application's use of threads. The container manages resources and streams and puts them into the drive. External concurrency control utilities are outside the container and they know nothing about these threads. Therefore, the application code should not run and manage its threads. To do this, it uses the Java EE functions. There are several APIs with built-in asynchronous support.

Asynchronous EJB Methods

The easiest way to implement asynchronous behavior is to use the @Asynchronous annotation for an EJB business method or an EJB class. Calls to these methods are immediately returned, sometimes with a response like Future. They are executed in a separate thread, managed by the container. This works well for simple scripts, but is limited to EJB objects:

@Asynchronous @Stateless public class Calculator { public void calculatePi(long decimalPlaces) { //      } } 

Performance Management Service

For asynchronous execution of tasks in managed CDI objects or using Java SE concurrency control utilities, Java EE includes container-managed versions of the ExecutorService and ScheduledExecutorService functions. They are used to implement asynchronous tasks in container-managed threads. Instances of the ManagedExecutorService and the ManagedScheduledExecutorService are embedded in the application code. They can be used to execute custom logic, but are most effective when combined with Java SE concurrency control utilities, such as complemented future values. The following example shows the creation of padded future values ​​using container-managed streams:

 import javax.annotation.Resource; import javax.enterprise.concurrent.ManagedExecutorService; import java.util.Random; import java.util.concurrent.CompletableFuture; @Stateless public class Calculator { @Resource ManagedExecutorService mes; public CompletableFuture<Double> calculateRandomPi(int maxDecimalPlaces) { return CompletableFuture.supplyAsync(() -> new Random().nextInt(maxDecimalPlaces) + 1, mes) .thenApply(this::calculatePi); } private double calculatePi(long decimalPlaces) { … } } 

The Calculator object returns a complemented future double value, which can still be calculated when the calling context resumes. It can be queried when the calculations are completed, as well as merged with subsequent calculations. Regardless of where new threads are required in a corporate application, you should use Java EE functionality to manage them.

Asynchronous CDI events

CDI events can also be processed asynchronously. In this case, the container also provides a stream for handling events. To describe an asynchronous event handler, the method is annotated with @ObservesAsync, and the event is activated using the fireAsync () method. The following code snippets demonstrate asynchronous CDI events:

 @Stateless public class CarManufacturer { @Inject CarFactory carFactory; @Inject Event<CarCreated> carCreated; public Car manufactureCar(Specification spec) { Car car = carFactory.createCar(spec); carCreated.fireAsync(new CarCreated(spec)); return car; } } 

The event handler is called in its own container-managed thread:

 import javax.enterprise.event.ObservesAsync; public class CreatedCarListener { public void onCarCreated(@ObservesAsync CarCreated event) { //    } } 

For backward compatibility reasons, synchronous CDI events can also be handled in an asynchronous EJB method. Thus, events and handlers are defined as synchronous, and the handler method is an EJB business method with the @Asynchronous annotation. Before asynchronous events were added to the CDI standard for Java EE 8, this was the only way to implement this function. To avoid confusion in Java EE 8 and later versions of this implementation, it is better to avoid.

Visibility areas for asynchronous processing

Since the container does not have information on how long asynchronous tasks can run, the use of scopes in this case is restricted. Objects with a scope within a request or session that were available at the start of an asynchronous task will not necessarily be active throughout its implementation - the request and session may end well before its completion. Thus, threads that perform asynchronous tasks, such as those provided by the service of scheduled executors or asynchronous events, may not have access to instances of managed objects with a scope within a request or session that were active during a call. The same applies to accessing links to embedded instances, for example, in lambda methods that are part of synchronous execution.

This must be taken into account when modeling asynchronous tasks. All information about a specific call must be provided at the time of launching the task. However, an asynchronous task can have its own instances of managed objects with limited scope.

Execution at a specified time

Business scripts can be invoked not only from the outside, for example, via an HTTP request, but also from within the application — a task that runs at a specific time.

In the Unix world, the functionality for running periodic tasks is popular - these are the tasks of the scheduler. EJB objects provide similar capabilities using EJB timers. Timers invoke business methods at specified intervals or after a specified time. The following example describes a cyclic timer that runs every ten minutes:

 import javax.ejb.Schedule; import javax.ejb.Startup; @Singleton @Startup public class PeriodicJob { @Schedule(minute = "*/10", hour = "*", persistent = false) public void executeJob() { //   10  } } 

Any EJB objects — singletones, managed objects with or without state preservation — can create timers. However, in most scenarios it makes sense to create timers only for singletons. The delay is set for all active objects. Usually it is needed in order to launch scheduled tasks on time, which is why it is used in singleton. For the same reason, in this example, the EJB object must be active when the application starts. This ensures that the timer starts working immediately.

If the timer is described as permanent, its lifetime will extend over the entire life cycle of the JVM. The container is responsible for maintaining persistent timers, usually in the database. Permanent timers, which should work while the application is not available, are enabled at startup. It also allows you to use the same timers with multiple instances of an object. Constant timers with the appropriate server configuration is the right solution if you need to run a business process exactly once on multiple servers.

Timers that are created automatically using Schedule annotation are described using Unix-like cron expressions. For greater flexibility, EJB timers are described programmatically using the container provided by the timer service, which creates callback methods Timers and Timeout .

Periodic and deferred tasks can also be described outside of the EJB components using the container-managed service of scheduled implementers. An instance of the ManagedScheduledExecutorService that performs tasks after a specified delay or at a specified interval is embedded in the managed components. These tasks will be implemented in streams managed by containers:

 @ApplicationScoped public class Periodic { @Resource ManagedScheduledExecutorService mses; public void startAsyncJobs() { mses.schedule(this::execute, 10, TimeUnit.SECONDS); mses.scheduleAtFixedRate(this::execute, 60, 10, TimeUnit.SECONDS); } private void execute() { … } } 

Calling the startAsyncJobs () method will launch the execute () function in a controlled stream ten seconds after the call and then every ten seconds after the first minute.

Asynchrony and reactivity in JAX-RS

JAX-RS supports asynchronous behavior, so as not to unnecessarily block request flows on the server side. Even if the HTTP connection is waiting for a response, the request flow may continue to process other requests while the server is running a long process. Query streams are combined in a container, and this query store has a specific size. In order not to waste the request flow, JAX-RS asynchronous resource methods create tasks that run when the request flow returns and can be reused. The HTTP connection resumes and responds when the asynchronous task completes or expires. The following example shows the asynchronous JAX-RS resource method:

 @Path("users") @Consumes(MediaType.APPLICATION_JSON) public class UsersResource { @Resource ManagedExecutorService mes; … @POST public CompletionStage<Response> createUserAsync(User user) { return CompletableFuture.supplyAsync(() -> createUser(user), mes); } private Response createUser(User user) { userStore.create(user); return Response.accepted().build(); } } 

In order for the request flow not to be busy for too long, the JAX-RS method must end quickly. This is due to the fact that the resource method is called from the container by means of the inversion control. The result obtained at the completion stage will be used to resume the client connection at the end of processing.

Returning completion stages is a relatively new technology in the JAX-RS API. If you need to describe the delay and at the same time provide greater flexibility in the asynchronous response, then you can include the AsyncResponse type in the method. This approach is demonstrated in the following example:

 import javax.ws.rs.container.AsyncResponse; import javax.ws.rs.container.Suspended; @Path("users") @Consumes(MediaType.APPLICATION_JSON) public class UsersResource { @Resource ManagedExecutorService mes; … @POST public void createUserAsync(User user, @Suspended AsyncResponse response) { response.setTimeout(5, TimeUnit.SECONDS); response.setTimeoutHandler(r -> r.resume(Response.status(Response.Status.SERVICE_UNAVAILABLE).build())); mes.execute(() -> response.resume(createUser(user))); } } 

Thanks to the created timeouts, the client request will not wait indefinitely, but only until the result is received or the call has timed out. However, the calculations will continue as they are performed asynchronously. For JAX-RS resources that are implemented as EJB objects, you can use the @Asynchronous annotation in order not to invoke asynchronous business methods explicitly through the service provider.

The JAX-RS client also supports asynchronous behavior. Depending on the requirements, it makes sense not to block it during HTTP calls. The previous example shows how to set delays for client requests. For long-running and especially parallel external system calls, it is better to use asynchronous and reactive behavior.

Consider a few server applications that provide weather information. The client component refers to all of these applications and calculates the average weather forecast. Ideally, access to the systems could be parallel:

 import java.util.stream.Collectors; @ApplicationScoped public class WeatherForecast { private Client client; private List<WebTarget> targets; @Resource ManagedExecutorService mes; @PostConstruct private void initClient() { client = ClientBuilder.newClient(); targets = … } public Forecast getAverageForecast() { return invokeTargetsAsync() .stream() .map(CompletableFuture::join) .reduce(this::calculateAverage) .orElseThrow(() -> new IllegalStateException("   ")); } private List<CompletableFuture<Forecast>> invokeTargetsAsync() { return targets.stream() .map(t -> CompletableFuture.supplyAsync(() -> t .request(MediaType.APPLICATION_JSON_TYPE) .get(Forecast.class), mes)) .collect(Collectors.toList()); } private Forecast calculateAverage(Forecast first, Forecast second) { … } @PreDestroy public void closeClient() { client.close(); } } 

The invokeTargetsAsync () method calls the available objects asynchronously, using the service of scheduled artists. CompletableFuture descriptors are returned and used to calculate averaged results. The start of the join () method will be blocked until the call is completed and the results are received.

Objects invoked asynchronously start and expect a response from several resources at once, possibly slower. In this case, waiting for responses from the weather service resources takes as much time as you have to expect the slowest response, rather than all responses together.

The latest version of JAX-RS has built-in support for the completion steps, which reduces the stereotypical code in applications. As in the case of padding values, the call immediately returns the code of the completion stage for further use. The following example shows JAX-RS reactive client functions using an rx () call:

 public Forecast getAverageForecast() { return invokeTargetsAsync() .stream() .reduce((l, r) -> l.thenCombine(r, this::calculateAverage)) .map(s -> s.toCompletableFuture().join()) .orElseThrow(() -> new IllegalStateException("   ")); } private List<CompletionStage<Forecast>> invokeTargetsAsync() { return targets.stream() .map(t -> t .request(MediaType.APPLICATION_JSON_TYPE) .rx() .get(Forecast.class)) .collect(Collectors.toList()); } 

In the example above, it is not necessary to look for the service of scheduled artists — the JAX-RS client will manage this itself. Before the rx () method appeared, an explicit call to async () was used in clients. This method behaved similarly, but returned only Future objects. The use of a reactive approach in clients is optimal for most projects.
As you can see, in the Java EE environment, a container-managed executor service is involved.

Concepts and design principles in modern Java EE


The Java EE API is based on conventions and design principles that are spelled out in the form of standards. Software engineers will recognize familiar API patterns and approaches to application development. The goal of Java EE is to encourage consistent use of the API.

The main principle of applications focused primarily on the implementation of business scenarios, sounds like this: technology should not interfere. As already mentioned, engineers should be able to focus on the implementation of business logic, without spending most of their time on technological and infrastructure issues. Ideally, the domain logic is implemented in simple Java and is supplemented with annotations and other properties supported by the corporate environment without affecting the code of the domain and without complicating it. This means that the technology does not require much attention of engineers and does not impose too large restrictions. Previously, the J2EE environment required many very complex solutions. To implement interfaces and extend base classes, you had to use managed objects and persistent storage objects. This complicated the domain logic and made testing difficult.

In Java EE, the domain logic is implemented as simple Java classes with annotations, according to which the container solves certain corporate tasks during the execution of the application. The practice of creating clean code often involves writing code more beautiful than convenient to reuse. Java EE supports this approach. If, for some reason, you need to remove the technology and leave the pure domain logic, this is done by simply deleting the corresponding annotations.

As we will see in Chapter 7, such an approach to programming implies the need for testing, since for programmers most of the Java EE specifications are nothing more than annotations.

The entire API adopts the design principle called inversion of control (IoC), in other words, “do not call us, we will call ourselves”. This is especially noticeable in application loops, such as JAX-RS resources. Resource methods are described using Java method annotations, which are later called by the container in the appropriate context. The same is true for dependency injection, for which you have to choose generators or take into account such end-to-end tasks as interceptors. Application developers can focus on bringing logic and relationship description to life, leaving the implementation of technical details in a container. Another example, not so obvious, is the description of converting Java objects to JSON and vice versa using JSON-B annotations. Objects are converted not only in an explicit, programmed form, but also implicitly, in a declarative style.

Another principle that allows engineers to effectively use this technology is programming by convention. By default, Java EE has a specific behavior defined for most use cases. If it is insufficient or does not meet the requirements, the behavior can be redefined, often at several levels.
There are many examples of programming by agreement. One of them is the use of JAX-RS resource methods that transform Java functionality into HTTP responses. If the standard JAX-RS behavior with respect to the responses does not meet the requirements, you can use the return response type Response. Another example is the specification of managed objects, which is usually implemented using annotations. In order to change this behavior, you can use the XML-descriptor beans.xml. For programmers, it’s very convenient that in the modern world of Java EE, corporate applications are developed in a pragmatic and high-performance way, which usually does not require such intensive use of XML as before.

As for the productivity of programmers, another important principle for developing in Java EE is that this platform requires integration into a container of various standards. Because containers support a specific set of APIs — and if they support all of the Java EE APIs — that’s the case, it’s also required that the API implementations allow for easy integration of other APIs. The advantage of this approach is the ability of JAX-RS resources to use JSON-B conversion and Bean Validation technology without additional explicit configuration, with the exception of annotations. In the previous examples, we saw how the functions defined in the individual standards can be used together without additional effort. This is one of the biggest advantages of the Java EE platform. The generic specification guarantees a combination of separate standards among themselves. Programmers can rely on certain functions and implementation provided by the application server.

Convenient, high-quality code


Programmers generally agree that you should strive to write high-quality code. However, not all technologies are equally well suited for this.

As mentioned at the beginning of the book, business logic should be in the spotlight when developing applications. In case of changes in business logic or the emergence of new knowledge, it is necessary to update the domain model, as well as the source code. To create and maintain a high-quality domain model and the source code as a whole, iterative refactoring is required. Efforts aimed at deepening the understanding of the subject area are described in the concept of problem-oriented design.

There is a lot of literature on refactoring at the code level. After the business logic is presented in the form of a code and verified by tests, programmers should spend some time and make efforts to rethink and improve the first option. This applies to identifiers of names, methods and classes. , .

- . , , — , , - . — , , . . , , .

, . , .

, , , . , , - . , , , - , . , . 7.

, . , , , . Java EE : , , . .

. , , , . . 6 , .

In general, it is recommended to constantly reorganize the code and improve its quality. Software projects are often created to introduce new revenue-generating functions, rather than to improve existing functionality. The problem is that refactoring and improving the quality of the code at first glance does not bring benefits to the business. This is certainly not the case. In order to achieve stable speed and integrate new functions with satisfactory quality, it is necessary to revise existing functions. Ideally, the refactoring cycles should be embedded in the project outline. Experience shows that project managers are often unaware of this problem. However, a team of software engineers is responsible for maintaining quality.

about the author


(Sebastian Daschner) — Java-, , Java (EE). JCP, Java EE, JSR 370 374 . Java Java - Oracle.

IT-, JavaLand, JavaOne Jfokus. JavaOne Rockstar JavaOne 2016. Java (Steve Chin) Java, . JOnsen — Java, .


(Melissa McKay) — 15- , . Java-, . , , .

JCrete () JOnsen . IT- , JavaOne4Kids JCrete4Kids. JavaOne 2017 Denver Java User Group.

»More information about the book can be found on the publisher site.
» Table of Contents
» Excerpt

20% — Java EE

Source: https://habr.com/ru/post/418025/


All Articles